diff --git a/src/actions/index.js b/src/actions/index.js
index f710136..e2792f9 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -58,6 +58,12 @@ export function toggleAutoZoomIn() {
};
}
+export function toggleOnlineCanvas() {
+ return {
+ type: 'TOGGLE_ONLINE_CANVAS',
+ };
+}
+
export function toggleMute() {
return {
type: 'TOGGLE_MUTE',
diff --git a/src/actions/types.js b/src/actions/types.js
index 8b4057a..c580329 100644
--- a/src/actions/types.js
+++ b/src/actions/types.js
@@ -17,6 +17,7 @@ export type Action =
| { type: 'TOGGLE_GRID' }
| { type: 'TOGGLE_PIXEL_NOTIFY' }
| { type: 'TOGGLE_AUTO_ZOOM_IN' }
+ | { type: 'TOGGLE_ONLINE_CANVAS' }
| { type: 'TOGGLE_MUTE' }
| { type: 'TOGGLE_OPEN_PALETTE' }
| { type: 'TOGGLE_COMPACT_PALETTE' }
diff --git a/src/client.js b/src/client.js
index 0f2820f..b627071 100644
--- a/src/client.js
+++ b/src/client.js
@@ -51,7 +51,7 @@ function init() {
ProtocolClient.on('cooldownPacket', (coolDown) => {
store.dispatch(receiveCoolDown(coolDown));
});
- ProtocolClient.on('onlineCounter', ({ online }) => {
+ ProtocolClient.on('onlineCounter', (online) => {
store.dispatch(receiveOnline(online));
});
ProtocolClient.on('chatMessage', (
diff --git a/src/components/CanvasItem.jsx b/src/components/CanvasItem.jsx
index 56e84ca..7b41479 100644
--- a/src/components/CanvasItem.jsx
+++ b/src/components/CanvasItem.jsx
@@ -9,7 +9,9 @@ import { t } from 'ttag';
import { THREE_CANVAS_HEIGHT } from '../core/constants';
-const CanvasItem = ({ canvasId, canvas, selCanvas }) => (
+const CanvasItem = ({
+ canvasId, canvas, selCanvas, online,
+}) => (
selCanvas(canvasId)}
@@ -23,6 +25,12 @@ const CanvasItem = ({ canvasId, canvas, selCanvas }) => (
/>
{canvas.title}
+ {(online) && (
+ <>
+ {t`Online Users`}:
+ {online}
+ >
+ )}
{canvas.desc}
{t`Cooldown`}:
@@ -33,7 +41,7 @@ const CanvasItem = ({ canvasId, canvas, selCanvas }) => (
{t`Stacking till`}:
{canvas.cds / 1000}s
{t`Ranked`}:
- {(canvas.ranked) ? 'Yes' : 'No'}
+ {(canvas.ranked) ? t`Yes` : t`No`}
{(canvas.req !== -1) ? {t`Requirements`}:
: null}
{(canvas.req !== -1) ? {t`User Account`} : null}
diff --git a/src/components/OnlineBox.jsx b/src/components/OnlineBox.jsx
index d861cc3..66f4a13 100644
--- a/src/components/OnlineBox.jsx
+++ b/src/components/OnlineBox.jsx
@@ -4,9 +4,11 @@
*/
import React from 'react';
-import { useSelector, shallowEqual } from 'react-redux';
-import { FaUser, FaPaintBrush } from 'react-icons/fa';
+import { useSelector, useDispatch, shallowEqual } from 'react-redux';
+import { FaUser, FaPaintBrush, FaFlipboard } from 'react-icons/fa';
import { t } from 'ttag';
+
+import { toggleOnlineCanvas } from '../actions';
import { numberToString } from '../core/utils';
@@ -15,19 +17,42 @@ const OnlineBox = () => {
online,
totalPixels,
name,
+ onlineCanvas,
+ canvasId,
] = useSelector((state) => [
state.ranks.online,
state.ranks.totalPixels,
state.user.name,
+ state.gui.onlineCanvas,
+ state.canvas.canvasId,
], shallowEqual);
+ const dispatch = useDispatch();
+
+ const onlineUsers = (onlineCanvas) ? online[canvasId] : online.total;
return (
- {(online || name)
+ {(onlineUsers || name)
? (
-
- {(online)
- &&
{online} }
+
dispatch(toggleOnlineCanvas())}
+ >
+ {(onlineUsers)
+ && (
+
+ {onlineUsers}
+
+ {(onlineCanvas) && }
+
+
+ )}
{(name != null)
&& (
diff --git a/src/components/windows/CanvasSelect.jsx b/src/components/windows/CanvasSelect.jsx
index 1b11f78..99450d8 100644
--- a/src/components/windows/CanvasSelect.jsx
+++ b/src/components/windows/CanvasSelect.jsx
@@ -12,9 +12,10 @@ import { changeWindowType, selectCanvas } from '../../actions';
const CanvasSelect = ({ windowId }) => {
- const [canvases, showHiddenCanvases] = useSelector((state) => [
+ const [canvases, showHiddenCanvases, online] = useSelector((state) => [
state.canvas.canvases,
state.canvas.showHiddenCanvases,
+ state.ranks.online,
], shallowEqual);
const dispatch = useDispatch();
const selCanvas = useCallback((canvasId) => dispatch(selectCanvas(canvasId)),
@@ -44,6 +45,8 @@ const CanvasSelect = ({ windowId }) => {
(!canvases[canvasId].hid || showHiddenCanvases)
&& (
{
logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`);
@@ -95,9 +92,7 @@ class SocketServer {
ws.on('pong', heartbeat);
ws.on('close', () => {
- // is close called on terminate?
- // possible memory leak?
- ipCounter.delete(ip);
+ ipCounter.delete(getIPFromRequest(req));
this.deleteAllChunks(ws);
});
@@ -114,6 +109,7 @@ class SocketServer {
this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this);
this.reloadUser = this.reloadUser.bind(this);
this.ping = this.ping.bind(this);
+ this.onlineCounterBroadcast = this.onlineCounterBroadcast.bind(this);
socketEvents.on('broadcast', this.broadcast);
socketEvents.on('pixelUpdate', this.broadcastPixelBuffer);
@@ -170,7 +166,7 @@ class SocketServer {
});
});
- setInterval(SocketServer.onlineCounterBroadcast, 10 * 1000);
+ setInterval(this.onlineCounterBroadcast, 10 * 1000);
setInterval(this.ping, 45 * 1000);
}
@@ -294,8 +290,36 @@ class SocketServer {
});
}
- static onlineCounterBroadcast() {
- const online = ipCounter.amount() || 0;
+ onlineCounterBroadcast() {
+ const online = {};
+ online.total = ipCounter.amount() || 0;
+ const ipsPerCanvas = {};
+ const it = this.wss.clients.keys();
+ let client = it.next();
+ while (!client.done) {
+ const ws = client.value;
+ if (ws.readyState === WebSocket.OPEN
+ && ws.user
+ ) {
+ const canvasId = ws.canvasId || 0;
+ const { ip } = ws.user;
+ // only count unique IPs per canvas
+ if (!ipsPerCanvas[canvasId]) {
+ ipsPerCanvas[canvasId] = [ip];
+ } else if (ipsPerCanvas[canvasId].includes(ip)) {
+ client = it.next();
+ continue;
+ } else {
+ ipsPerCanvas[canvasId].push(ip);
+ }
+ //--
+ if (!online[canvasId]) {
+ online[canvasId] = 0;
+ }
+ online[canvasId] += 1;
+ }
+ client = it.next();
+ }
socketEvents.broadcastOnlineCounter(online);
}
@@ -406,6 +430,7 @@ class SocketServer {
}
case RegisterCanvas.OP_CODE: {
const canvasId = RegisterCanvas.hydrate(buffer);
+ if (!canvases[canvasId]) return;
if (ws.canvasId !== null && ws.canvasId !== canvasId) {
this.deleteAllChunks(ws);
}
diff --git a/src/socket/packets/OnlineCounter.js b/src/socket/packets/OnlineCounter.js
index 320ff52..c301a09 100644
--- a/src/socket/packets/OnlineCounter.js
+++ b/src/socket/packets/OnlineCounter.js
@@ -1,19 +1,48 @@
+/*
+ *
+ * Numbers of online players per canvas
+ *
+ */
+
const OP_CODE = 0xA7;
export default {
OP_CODE,
+ // CLIENT (receiver)
+ /*
+ * {
+ * total: totalOnline,
+ * canvasId: online,
+ * ....
+ * }
+ */
hydrate(data) {
- // CLIENT (receiver)
- const online = data.getInt16(1);
- return { online };
+ const online = {};
+ online.total = data.getUint16(1);
+ let off = data.byteLength;
+ while (off > 3) {
+ const onlineUsers = data.getUint16(off -= 2);
+ const canvas = data.getUint8(off -= 1);
+ online[canvas] = onlineUsers;
+ }
+ return online;
},
- dehydrate({ online }) {
+
+ dehydrate(online) {
// SERVER (sender)
if (!process.env.BROWSER) {
- const buffer = Buffer.allocUnsafe(1 + 2);
- buffer.writeUInt8(OP_CODE, 0);
+ const canvasIds = Object.keys(online);
- buffer.writeInt16BE(online, 1);
+ const buffer = Buffer.allocUnsafe(3 + canvasIds.length * (1 + 2));
+ buffer.writeUInt8(OP_CODE, 0);
+ buffer.writeUInt16BE(online.total, 1);
+ let cnt = 1;
+ for (let p = 0; p < canvasIds.length; p += 1) {
+ const canvasId = canvasIds[p];
+ const onlineUsers = online[canvasId];
+ buffer.writeUInt8(Number(canvasId), cnt += 2);
+ buffer.writeUInt16BE(onlineUsers, cnt += 1);
+ }
return buffer;
}
diff --git a/src/styles/default.css b/src/styles/default.css
index 22ae3e3..dc59d1d 100644
--- a/src/styles/default.css
+++ b/src/styles/default.css
@@ -330,6 +330,7 @@ tr:nth-child(even) {
.onlinebox {
left: 16px;
bottom: 57px;
+ cursor: pointer;
white-space: nowrap;
}
@@ -800,7 +801,7 @@ tr:nth-child(even) {
padding: 0;
}
-.actionbuttons:hover, .coorbox:hover, .menu > div:hover {
+.actionbuttons:hover, .coorbox:hover, .menu > div:hover, .onlinebox:hover {
background-color: #d2d2d2cc;
}
diff --git a/webpack.config.web.babel.js b/webpack.config.web.babel.js
index 5d0b80b..ce165f4 100644
--- a/webpack.config.web.babel.js
+++ b/webpack.config.web.babel.js
@@ -107,8 +107,8 @@ export default ({
externals: [
/\/proxies\.json$/,
/\/canvases\.json$/,
- /^\.\/styleassets\.json$/,
- /^\.\/assets\.json$/,
+ /\/styleassets\.json$/,
+ /\/assets\.json$/,
nodeExternals(),
],