diff --git a/package-lock.json b/package-lock.json
index c36bde9..e4e4945 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,7 +17,6 @@
"express-session": "^1.17.2",
"image-q": "^4.0.0",
"js-file-download": "^0.4.12",
- "localforage": "^1.10.0",
"morgan": "^1.10.0",
"multer": "^1.4.4",
"mysql2": "^2.3.3",
@@ -42,6 +41,7 @@
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.4.1",
+ "reselect": "^4.1.6",
"sequelize": "^6.21.3",
"sharp": "^0.30.7",
"startaudiocontext": "^1.2.1",
@@ -6301,11 +6301,6 @@
"@types/node": "16.9.1"
}
},
- "node_modules/immediate": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
- "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
- },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -7097,14 +7092,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/lie": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
- "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
- "dependencies": {
- "immediate": "~3.0.5"
- }
- },
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -7134,14 +7121,6 @@
"node": ">=8.9.0"
}
},
- "node_modules/localforage": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
- "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
- "dependencies": {
- "lie": "3.1.1"
- }
- },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -9177,6 +9156,11 @@
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
+ "node_modules/reselect": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
+ "integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
+ },
"node_modules/resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@@ -15913,11 +15897,6 @@
"@types/node": "16.9.1"
}
},
- "immediate": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
- "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
- },
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -16506,14 +16485,6 @@
"type-check": "~0.4.0"
}
},
- "lie": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
- "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
- "requires": {
- "immediate": "~3.0.5"
- }
- },
"lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -16537,14 +16508,6 @@
"json5": "^2.1.2"
}
},
- "localforage": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
- "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
- "requires": {
- "lie": "3.1.1"
- }
- },
"locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -18047,6 +18010,11 @@
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
+ "reselect": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
+ "integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
+ },
"resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
diff --git a/package.json b/package.json
index 540fc79..a410a9f 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,6 @@
"express-session": "^1.17.2",
"image-q": "^4.0.0",
"js-file-download": "^0.4.12",
- "localforage": "^1.10.0",
"morgan": "^1.10.0",
"multer": "^1.4.4",
"mysql2": "^2.3.3",
@@ -56,6 +55,7 @@
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.4.1",
+ "reselect": "^4.1.6",
"sequelize": "^6.21.3",
"sharp": "^0.30.7",
"startaudiocontext": "^1.2.1",
diff --git a/src/client.js b/src/client.js
index a899946..8f851ba 100644
--- a/src/client.js
+++ b/src/client.js
@@ -4,7 +4,6 @@
import createKeyPressHandler from './controls/keypress';
import {
- fetchMe,
initTimer,
urlChange,
receiveOnline,
@@ -15,6 +14,9 @@ import {
setMobile,
windowResize,
} from './store/actions';
+import {
+ fetchMe,
+} from './store/actions/thunks';
import {
receivePixelUpdate,
receivePixelReturn,
@@ -36,12 +38,12 @@ function init() {
pixels.forEach((pxl) => {
const [offset, color] = pxl;
// remove protection
- receivePixelUpdate(store, i, j, offset, color & 0x7F);
+ receivePixelUpdate(getRenderer(), i, j, offset, color & 0x7F);
});
});
- SocketClient.on('pixelReturn',
- (args) => receivePixelReturn(store, ...args),
- );
+ SocketClient.on('pixelReturn', (args) => {
+ receivePixelReturn(store, getRenderer(), args);
+ });
SocketClient.on('cooldownPacket', (coolDown) => {
store.dispatch(receiveCoolDown(coolDown));
});
diff --git a/src/components/BanInfo.jsx b/src/components/BanInfo.jsx
index d42a6ab..472be5f 100644
--- a/src/components/BanInfo.jsx
+++ b/src/components/BanInfo.jsx
@@ -7,7 +7,7 @@ import { useDispatch } from 'react-redux';
import { t } from 'ttag';
import useInterval from './hooks/interval';
-import { showHelpModal } from '../store/actions';
+import { showHelpModal } from '../store/actions/windows';
import {
largeDurationToString,
} from '../core/utils';
diff --git a/src/components/CoordinatesBox.jsx b/src/components/CoordinatesBox.jsx
index f208a63..61d4b96 100644
--- a/src/components/CoordinatesBox.jsx
+++ b/src/components/CoordinatesBox.jsx
@@ -7,7 +7,7 @@ import { useSelector, useDispatch } from 'react-redux';
import { t } from 'ttag';
import copy from '../utils/clipboard';
-import { notify } from '../store/actions';
+import { notify } from '../store/actions/thunks';
function renderCoordinates(cell) {
diff --git a/src/components/GetIID.jsx b/src/components/GetIID.jsx
index b3f3b46..2b05fb5 100644
--- a/src/components/GetIID.jsx
+++ b/src/components/GetIID.jsx
@@ -6,7 +6,7 @@ import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { t } from 'ttag';
-import { notify } from '../store/actions';
+import { notify } from '../store/actions/thunks';
import copyTextToClipboard from '../utils/clipboard';
import {
requestIID,
diff --git a/src/components/LogInArea.jsx b/src/components/LogInArea.jsx
index 46fc63b..818bcf4 100644
--- a/src/components/LogInArea.jsx
+++ b/src/components/LogInArea.jsx
@@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux';
import { t } from 'ttag';
import LogInForm from './LogInForm';
-import { changeWindowType } from '../store/actions';
+import { changeWindowType } from '../store/actions/windows';
const logoStyle = {
marginRight: 5,
diff --git a/src/components/SocialSettings.jsx b/src/components/SocialSettings.jsx
index 860a82e..8acfaca 100644
--- a/src/components/SocialSettings.jsx
+++ b/src/components/SocialSettings.jsx
@@ -9,7 +9,7 @@ import { t } from 'ttag';
import {
setBlockingDm,
setUserBlock,
-} from '../store/actions';
+} from '../store/actions/thunks';
import MdToggleButton from './MdToggleButton';
const SocialSettings = ({ done }) => {
diff --git a/src/components/Window.jsx b/src/components/Window.jsx
index a9caadb..127f7b9 100644
--- a/src/components/Window.jsx
+++ b/src/components/Window.jsx
@@ -3,7 +3,7 @@
*/
import React, {
- useState, useCallback, useRef, useEffect,
+ useState, useCallback, useRef, useEffect, useMemo,
} from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { t } from 'ttag';
@@ -16,21 +16,26 @@ import {
toggleMaximizeWindow,
cloneWindow,
focusWindow,
-} from '../store/actions';
+} from '../store/actions/windows';
+import {
+ makeSelectWindowById,
+ makeSelectWindowPosById,
+ selectShowWindows,
+} from '../store/selectors/windows';
import useDrag from './hooks/drag';
import COMPONENTS from './windows';
-// eslint-disable-next-line max-len
-const selectWindowById = (state, windowId) => state.windows.windows.find((win) => win.windowId === windowId);
-
const Window = ({ id }) => {
const [render, setRender] = useState(false);
const titleBarRef = useRef(null);
const resizeRef = useRef(null);
- const win = useSelector((state) => selectWindowById(state, id));
- const showWindows = useSelector((state) => state.windows.showWindows);
+ const selectWindowById = useMemo(() => makeSelectWindowById(id), []);
+ const selectWIndowPosById = useMemo(() => makeSelectWindowPosById(id), []);
+ const win = useSelector(selectWindowById);
+ const position = useSelector(selectWIndowPosById);
+ const showWindows = useSelector(selectShowWindows);
const dispatch = useDispatch();
@@ -101,12 +106,14 @@ const Window = ({ id }) => {
}
const {
- width, height,
- xPos, yPos,
windowType,
- z,
title,
} = win;
+ const {
+ xPos, yPos,
+ width, height,
+ z,
+ } = position;
const [Content, name] = COMPONENTS[windowType];
@@ -155,10 +162,6 @@ const Window = ({ id }) => {
);
}
- if (!showWindows) {
- return null;
- }
-
return (
state.windows.windows.map((win) => win.windowId);
-// eslint-disable-next-line max-len
-const selectMeta = (state) => [state.windows.showWindows, state.windows.someFullscreen];
+} from '../store/actions/windows';
+import {
+ selectIfFullscreen,
+ selectActiveWindowIds,
+} from '../store/selectors/windows';
const WindowManager = () => {
- const windowIds = useSelector(selectWindowIds, shallowEqual);
- const [showWindows, someFullscreen] = useSelector(selectMeta, shallowEqual);
+ const windowIds = useSelector(selectActiveWindowIds, shallowEqual);
+ const [
+ fullscreenExistOrShowWindows,
+ someOpenFullscreen,
+ ] = useSelector(selectIfFullscreen, shallowEqual);
const dispatch = useDispatch();
- if ((!showWindows && !someFullscreen) || !windowIds.length) {
+ if (!fullscreenExistOrShowWindows || !windowIds.length) {
return null;
}
return (
dispatch(closeFullscreenWindows())}
/>
{windowIds.map((id) => )}
diff --git a/src/components/buttons/CanvasSwitchButton.jsx b/src/components/buttons/CanvasSwitchButton.jsx
index ed58df6..5e4c21b 100644
--- a/src/components/buttons/CanvasSwitchButton.jsx
+++ b/src/components/buttons/CanvasSwitchButton.jsx
@@ -7,7 +7,7 @@ import { useDispatch } from 'react-redux';
import { FaFlipboard } from 'react-icons/fa';
import { t } from 'ttag';
-import { showCanvasSelectionModal } from '../../store/actions';
+import { showCanvasSelectionModal } from '../../store/actions/windows';
const CanvasSwitchButton = () => {
diff --git a/src/components/buttons/ChatButton.jsx b/src/components/buttons/ChatButton.jsx
index 5238a18..23f30f3 100644
--- a/src/components/buttons/ChatButton.jsx
+++ b/src/components/buttons/ChatButton.jsx
@@ -12,7 +12,7 @@ import { t } from 'ttag';
import {
hideAllWindowTypes,
openChatWindow,
-} from '../../store/actions';
+} from '../../store/actions/windows';
/*
* return [ chatOpen, chatHiden ]
diff --git a/src/components/buttons/HelpButton.jsx b/src/components/buttons/HelpButton.jsx
index 5cb2570..d63f268 100644
--- a/src/components/buttons/HelpButton.jsx
+++ b/src/components/buttons/HelpButton.jsx
@@ -7,7 +7,7 @@ import { useDispatch } from 'react-redux';
import { FaQuestion } from 'react-icons/fa';
import { t } from 'ttag';
-import { showHelpModal } from '../../store/actions';
+import { showHelpModal } from '../../store/actions/windows';
const HelpButton = () => {
diff --git a/src/components/buttons/LogInButton.jsx b/src/components/buttons/LogInButton.jsx
index 74edf65..35a0185 100644
--- a/src/components/buttons/LogInButton.jsx
+++ b/src/components/buttons/LogInButton.jsx
@@ -7,7 +7,7 @@ import { useDispatch } from 'react-redux';
import { MdPerson } from 'react-icons/md';
import { t } from 'ttag';
-import { showUserAreaModal } from '../../store/actions';
+import { showUserAreaModal } from '../../store/actions/windows';
const LogInButton = () => {
diff --git a/src/components/buttons/SettingsButton.jsx b/src/components/buttons/SettingsButton.jsx
index 425cdca..ae29a6c 100644
--- a/src/components/buttons/SettingsButton.jsx
+++ b/src/components/buttons/SettingsButton.jsx
@@ -7,7 +7,7 @@ import { useDispatch } from 'react-redux';
import { FaCog } from 'react-icons/fa';
import { t } from 'ttag';
-import { showSettingsModal } from '../../store/actions';
+import { showSettingsModal } from '../../store/actions/windows';
const SettingsButton = () => {
diff --git a/src/components/contextmenus/ChannelContextMenu.jsx b/src/components/contextmenus/ChannelContextMenu.jsx
index 2c39503..0eff8fa 100644
--- a/src/components/contextmenus/ChannelContextMenu.jsx
+++ b/src/components/contextmenus/ChannelContextMenu.jsx
@@ -11,10 +11,12 @@ import {
} from '../hooks/clickOutside';
import {
hideContextMenu,
- setLeaveChannel,
muteChatChannel,
unmuteChatChannel,
} from '../../store/actions';
+import {
+ setLeaveChannel,
+} from '../../store/actions/thunks';
const ChannelContextMenu = () => {
const wrapperRef = useRef(null);
diff --git a/src/components/contextmenus/UserContextMenu.jsx b/src/components/contextmenus/UserContextMenu.jsx
index a93cb14..0a57f3a 100644
--- a/src/components/contextmenus/UserContextMenu.jsx
+++ b/src/components/contextmenus/UserContextMenu.jsx
@@ -11,11 +11,15 @@ import {
} from '../hooks/clickOutside';
import {
hideContextMenu,
- addToChatInputMessage,
+} from '../../store/actions';
+import {
startDm,
setUserBlock,
+} from '../../store/actions/thunks';
+import {
+ addToChatInputMessage,
setWindowArgs,
-} from '../../store/actions';
+} from '../../store/actions/windows';
import { escapeMd } from '../../core/utils';
const UserContextMenu = () => {
diff --git a/src/components/windows/CanvasSelect.jsx b/src/components/windows/CanvasSelect.jsx
index 563bde1..61fc162 100644
--- a/src/components/windows/CanvasSelect.jsx
+++ b/src/components/windows/CanvasSelect.jsx
@@ -7,7 +7,8 @@ import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { t } from 'ttag';
import CanvasItem from '../CanvasItem';
-import { changeWindowType, selectCanvas } from '../../store/actions';
+import { selectCanvas } from '../../store/actions';
+import { changeWindowType } from '../../store/actions/windows';
const CanvasSelect = ({ windowId }) => {
diff --git a/src/components/windows/Chat.jsx b/src/components/windows/Chat.jsx
index f0dfb29..8cec06a 100644
--- a/src/components/windows/Chat.jsx
+++ b/src/components/windows/Chat.jsx
@@ -13,13 +13,17 @@ import ChatMessage from '../ChatMessage';
import ChannelDropDown from '../contextmenus/ChannelDropDown';
import {
- showUserAreaModal,
- fetchChatMessages,
showContextMenu,
- setWindowTitle,
- setWindowArgs,
markChannelAsRead,
} from '../../store/actions';
+import {
+ showUserAreaModal,
+ setWindowTitle,
+ setWindowArgs,
+} from '../../store/actions/windows';
+import {
+ fetchChatMessages,
+} from '../../store/actions/thunks';
import SocketClient from '../../socket/SocketClient';
diff --git a/src/components/windows/ForgotPassword.jsx b/src/components/windows/ForgotPassword.jsx
index 336cc8d..23e74cd 100644
--- a/src/components/windows/ForgotPassword.jsx
+++ b/src/components/windows/ForgotPassword.jsx
@@ -5,7 +5,7 @@ import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { t } from 'ttag';
-import { changeWindowType } from '../../store/actions';
+import { changeWindowType } from '../../store/actions/windows';
import { validateEMail } from '../../utils/validation';
import { requestNewPassword } from '../../store/actions/fetch';
diff --git a/src/components/windows/Register.jsx b/src/components/windows/Register.jsx
index fe84eb5..544b005 100644
--- a/src/components/windows/Register.jsx
+++ b/src/components/windows/Register.jsx
@@ -11,7 +11,8 @@ import {
} from '../../utils/validation';
import { requestRegistration } from '../../store/actions/fetch';
-import { changeWindowType, loginUser } from '../../store/actions';
+import { loginUser } from '../../store/actions';
+import { changeWindowType } from '../../store/actions/windows';
function validate(name, email, password, confirmPassword) {
diff --git a/src/components/windows/UserArea.jsx b/src/components/windows/UserArea.jsx
index f3125c6..c9fcbcf 100644
--- a/src/components/windows/UserArea.jsx
+++ b/src/components/windows/UserArea.jsx
@@ -8,8 +8,10 @@ import { t } from 'ttag';
import {
fetchStats,
+} from '../../store/actions/thunks';
+import {
setWindowArgs,
-} from '../../store/actions';
+} from '../../store/actions/windows';
import useInterval from '../hooks/interval';
import LogInArea from '../LogInArea';
import Tabs from '../Tabs';
diff --git a/src/controls/PixelPainterControls.js b/src/controls/PixelPainterControls.js
index e9d0e53..b199874 100644
--- a/src/controls/PixelPainterControls.js
+++ b/src/controls/PixelPainterControls.js
@@ -243,6 +243,7 @@ class PixelPlainterControls {
const [i, j] = getChunkOfPixel(canvasSize, x, y);
const offset = getOffsetOfPixel(canvasSize, x, y);
tryPlacePixel(
+ renderer,
store,
i, j, offset,
selectedColor,
diff --git a/src/controls/keypress.js b/src/controls/keypress.js
index 00ee36c..86f967d 100644
--- a/src/controls/keypress.js
+++ b/src/controls/keypress.js
@@ -9,9 +9,11 @@ import {
toggleHiddenCanvases,
togglePixelNotify,
toggleMute,
- notify,
selectCanvas,
} from '../store/actions';
+import {
+ notify,
+} from '../store/actions/thunks';
const usedKeys = ['g', 'h', 'x', 'm', 'r', 'p'];
diff --git a/src/socket/SocketClient.js b/src/socket/SocketClient.js
index e0dc455..7b22ad8 100644
--- a/src/socket/SocketClient.js
+++ b/src/socket/SocketClient.js
@@ -248,8 +248,7 @@ class SocketClient extends EventEmitter {
`Socket is closed. Reconnect will be attempted in ${timeout} ms.`,
e.reason,
);
-
- setTimeout(() => this.connect(), 5000);
+ setTimeout(() => this.connect(), timeout);
}
reconnect() {
diff --git a/src/socket/packets/PixelReturn.js b/src/socket/packets/PixelReturn.js
index 28465d2..8ba9285 100644
--- a/src/socket/packets/PixelReturn.js
+++ b/src/socket/packets/PixelReturn.js
@@ -9,13 +9,13 @@ export default {
const coolDownSeconds = data.getInt16(6);
const pxlCnt = data.getUint8(8);
const rankedPxlCnt = data.getUint8(9);
- return [
+ return {
retCode,
wait,
coolDownSeconds,
pxlCnt,
rankedPxlCnt,
- ];
+ };
},
dehydrate(
retCode,
diff --git a/src/store/actions/README.md b/src/store/actions/README.md
deleted file mode 100644
index a128d25..0000000
--- a/src/store/actions/README.md
+++ /dev/null
@@ -1,148 +0,0 @@
-# Actions
-
-List of redux actions for reference:
-
-```js
-export type Action =
- { type: 'LOGGED_OUT' }
- | { type: 'ALERT',
- title: string,
- message: string,
- alertType: string,
- btn: string,
- }
- | { type: 'CLOSE_ALERT' }
- | { 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' }
- | { type: 'TOGGLE_CHAT_NOTIFY' }
- | { type: 'TOGGLE_POTATO_MODE' }
- | { type: 'TOGGLE_LIGHT_GRID' }
- | { type: 'TOGGLE_OPEN_MENU' }
- | { type: 'TOGGLE_HISTORICAL_VIEW' }
- | { type: 'SELECT_STYLE', style: string }
- | { type: 'SET_NOTIFICATION', notification: string }
- | { type: 'UNSET_NOTIFICATION' }
- | { type: 'SET_REQUESTING_PIXEL', requestingPixel: boolean }
- | { type: 'SET_HOVER', hover: Array }
- | { type: 'UNSET_HOVER' }
- | { type: 'SET_WAIT', wait: ?number }
- | { type: 'RECEIVE_COOLDOWN', wait: number }
- | { type: 'SET_MOBILE', mobile: boolean }
- | { type: 'WINDOW_RESIZE'}
- | { type: 'COOLDOWN_END' }
- | { type: 'COOLDOWN_SET', coolDown: number }
- | { type: 'COOLDOWN_DELTA', delta: number }
- | { type: 'SELECT_COLOR', color: ColorIndex }
- | { type: 'SELECT_CANVAS', canvasId: number }
- | { type: 'PLACED_PIXELS', amount: number }
- | { type: 'PIXEL_WAIT' }
- | { type: 'SET_VIEW_COORDINATES', view: Array }
- | { type: 'SET_SCALE', scale: number, zoompoint: Array }
- | { type: 'REQUEST_BIG_CHUNK', center: Array }
- | { type: 'PRE_LOADED_BIG_CHUNK', center: Array }
- | { type: 'RECEIVE_BIG_CHUNK', center: Array, chunk: Uint8Array }
- | { type: 'RECEIVE_BIG_CHUNK_FAILURE', center: Array, error: Error }
- | { type: 'UPDATE_PIXEL',
- i: number,
- j: number,
- offset: number,
- color: ColorIndex,
- notify: boolean,
- }
- | { type: 'RECEIVE_ONLINE', online: number }
- | { type: 'RECEIVE_CHAT_MESSAGE',
- name: string,
- text: string,
- country: string,
- channel: number,
- user: number,
- isPing: boolean,
- isRead: boolean,
- }
- | { type: 'RECEIVE_CHAT_HISTORY', cid: number, history: Array }
- | { type: 'OPEN_CHAT_CHANNEL', cid: number }
- | { type: 'CLOSE_CHAT_CHANNEL', cid: number }
- | { type: 'ADD_CHAT_CHANNEL', channel: Object }
- | { type: 'REMOVE_CHAT_CHANNEL', cid: number }
- | { type: 'MUTE_CHAT_CHANNEL', cid: number }
- | { type: 'UNMUTE_CHAT_CHANNEL', cid: number }
- | { type: 'MARK_CHANNEL_AS_READ', cid: number }
- | { type: 'SET_CHAT_FETCHING', fetching: boolean }
- | { type: 'OPEN_WINDOW',
- windowType: string,
- title: string,
- fullscreen: boolean,
- cloneable: boolean,
- args: Object,
- }
- | { type: 'CLOSE_WINDOW', windowId: number }
- | { type: 'REMOVE_WINDOW', windowId: number }
- | { type: 'HIDE_ALL_WINDOW_TYPE', windowType: string, hide: boolean }
- | { type: 'CLOSE_ALL_WINDOW_TYPE', windowType: string}
- | { type: 'FOCUS_WINDOW', windowId: number }
- | { type: 'CLONE_WINDOW', windowId: number }
- | { type: 'TOGGLE_MAXIMIZE_WINDOW', windowId: number }
- | { type: 'CLOSE_FULLSCREEN_WINDOWS' }
- | { type: 'MOVE_WINDOW', windowId: number, xDiff: number, yDiff: number }
- | { type: 'RESIZE_WINDOW', windowId: number, xDiff: number, yDiff: number }
- | { type: 'CHANGE_WINDOW_TYPE',
- windowId: number,
- windowType: number,
- args: Object,
- }
- | { type: 'SET_WINDOW_TITLE', windowId: number, title: string }
- | { type: 'BLOCK_USER', userId: number, userName: string }
- | { type: 'UNBLOCK_USER', userId: number, userName: string }
- | { type: 'SET_BLOCKING_DM', blockDm: boolean }
- | { type: 'RECEIVE_ME',
- name: string,
- waitSeconds: number,
- messages: Array,
- mailreg: boolean,
- totalPixels: number,
- dailyTotalPixels: number,
- ranking: number,
- dailyRanking: number,
- blockDm: boolean,
- canvases: Object,
- channels: Object,
- blocked: Array,
- userlvl: number,
- }
- | { type: 'LOGIN',
- name: string,
- waitSeconds: number,
- messages: Array,
- mailreg: boolean,
- totalPixels: number,
- dailyTotalPixels: number,
- ranking: number,
- dailyRanking: number,
- blockDm: boolean,
- canvases: Object,
- channels: Object,
- blocked: Array,
- userlvl: number,
- }
- | { type: 'LOGOUT' }
- | { type: 'RECEIVE_STATS', totalRanking: Object, totalDailyRanking: Object }
- | { type: 'SET_NAME', name: string }
- | { type: 'SET_MAILREG', mailreg: boolean }
- | { type: 'REM_FROM_MESSAGES', message: string }
- | { type: 'SHOW_CONTEXT_MENU',
- menuType: string,
- xPos: number,
- yPos: number,
- args: Object,
- }
- | { type: 'HIDE_CONTEXT_MENU' }
- | { type: 'RELOAD_URL' }
- | { type: 'SET_HISTORICAL_TIME', date: string, time: string }
- | { type: 'ON_VIEW_FINISH_CHANGE' };
-
-```
diff --git a/src/store/actions/index.js b/src/store/actions/index.js
index 60e1ec6..7ca376e 100644
--- a/src/store/actions/index.js
+++ b/src/store/actions/index.js
@@ -1,14 +1,5 @@
import { t } from 'ttag';
-import {
- requestStartDm,
- requestBlock,
- requestBlockDm,
- requestLeaveChan,
- requestRankings,
- requestMe,
-} from './fetch';
-
export function pAlert(
title,
message,
@@ -115,27 +106,10 @@ export function toggleOpenMenu() {
};
}
-/*
- * requestingPixel is inveted, it has the meaning of
- * "can i request a pixel"
- */
-export function setRequestingPixel(requestingPixel) {
+export function setAllowSettingPixel(allowSettingPixel) {
return {
- type: 'SET_REQUESTING_PIXEL',
- requestingPixel,
- };
-}
-
-export function setNotification(notification) {
- return {
- type: 'SET_NOTIFICATION',
- notification,
- };
-}
-
-export function unsetNotification() {
- return {
- type: 'UNSET_NOTIFICATION',
+ type: 'ALLOW_SETTING_PIXEL',
+ allowSettingPixel,
};
}
@@ -152,13 +126,6 @@ export function unsetHover() {
};
}
-export function setWait(wait) {
- return {
- type: 'SET_WAIT',
- wait,
- };
-}
-
export function setMobile(mobile) {
return {
type: 'SET_MOBILE',
@@ -186,61 +153,6 @@ export function selectCanvas(canvasId) {
};
}
-export function placedPixels(amount) {
- return {
- type: 'PLACED_PIXELS',
- amount,
- };
-}
-
-export function pixelWait() {
- return {
- type: 'PIXEL_WAIT',
- };
-}
-
-export function receiveOnline(online) {
- return {
- type: 'RECEIVE_ONLINE',
- online,
- };
-}
-
-export function receiveChatMessage(
- name,
- text,
- country,
- channel,
- user,
- isPing,
- isRead,
-) {
- return {
- type: 'RECEIVE_CHAT_MESSAGE',
- name,
- text,
- country,
- channel,
- user,
- isPing,
- isRead,
- };
-}
-
-let lastNotify = null;
-export function notify(notification) {
- return async (dispatch) => {
- dispatch(setNotification(notification));
- if (lastNotify) {
- clearTimeout(lastNotify);
- lastNotify = null;
- }
- lastNotify = setTimeout(() => {
- dispatch(unsetNotification());
- }, 1500);
- };
-}
-
export function setViewCoordinates(view) {
return {
type: 'SET_VIEW_COORDINATES',
@@ -358,38 +270,6 @@ export function receiveCoolDown(
};
}
-/*
- * draw pixel on canvas
- * @param i, j, offset Chunk and offset in chunk
- * @param color integer Color Index
- * @param notifyPxl Bool if pixel notification appears (false when my own pixel)
- */
-export function updatePixel(
- i,
- j,
- offset,
- color,
- notifyPxl = true,
-) {
- return {
- type: 'UPDATE_PIXEL',
- i,
- j,
- offset,
- color,
- notify: notifyPxl,
- };
-}
-
-export function loginUser(
- me,
-) {
- return {
- type: 'LOGIN',
- ...me,
- };
-}
-
export function receiveMe(
me,
) {
@@ -399,13 +279,6 @@ export function receiveMe(
};
}
-export function logoutUser(
-) {
- return {
- type: 'LOGOUT',
- };
-}
-
export function receiveStats(
rankings,
) {
@@ -417,6 +290,58 @@ export function receiveStats(
};
}
+export function receiveOnline(online) {
+ return {
+ type: 'RECEIVE_ONLINE',
+ online,
+ };
+}
+
+export function receiveChatMessage(
+ name,
+ text,
+ country,
+ channel,
+ user,
+ isPing,
+ isRead,
+) {
+ return {
+ type: 'RECEIVE_CHAT_MESSAGE',
+ name,
+ text,
+ country,
+ channel,
+ user,
+ isPing,
+ isRead,
+ };
+}
+
+/*
+ * check socket/packets/PixelReturn.js for args
+ */
+export function storeReceivePixelReturn(args) {
+ args.type = 'RECEIVE_PIXEL_RETURN';
+ return args;
+}
+
+export function logoutUser(
+) {
+ return {
+ type: 'LOGOUT',
+ };
+}
+
+export function loginUser(
+ me,
+) {
+ return {
+ type: 'LOGIN',
+ ...me,
+ };
+}
+
export function setName(
name,
) {
@@ -444,35 +369,6 @@ export function remFromMessages(
};
}
-export function fetchStats() {
- return async (dispatch) => {
- const rankings = await requestRankings();
- if (!rankings.errors) {
- dispatch(receiveStats(rankings));
- }
- };
-}
-
-export function fetchMe() {
- return async (dispatch) => {
- const me = await requestMe();
- if (!me.errors) {
- dispatch(receiveMe(me));
- }
- };
-}
-
-function receiveChatHistory(
- cid,
- history,
-) {
- return {
- type: 'RECEIVE_CHAT_HISTORY',
- cid,
- history,
- };
-}
-
export function markChannelAsRead(
cid,
) {
@@ -482,42 +378,6 @@ export function markChannelAsRead(
};
}
-function setChatFetching(fetching) {
- return {
- type: 'SET_CHAT_FETCHING',
- fetching,
- };
-}
-
-function setApiFetching(fetching) {
- return {
- type: 'SET_API_FETCHING',
- fetching,
- };
-}
-
-export function fetchChatMessages(
- cid,
-) {
- return async (dispatch) => {
- dispatch(setChatFetching(true));
- const response = await fetch(`api/chathistory?cid=${cid}&limit=50`, {
- credentials: 'include',
- });
-
- /*
- * timeout in order to not spam api requests and get rate limited
- */
- if (response.ok) {
- setTimeout(() => { dispatch(setChatFetching(false)); }, 500);
- const { history } = await response.json();
- dispatch(receiveChatHistory(cid, history));
- } else {
- setTimeout(() => { dispatch(setChatFetching(false)); }, 5000);
- }
- };
-}
-
function setCoolDown(coolDown) {
return {
type: 'COOLDOWN_SET',
@@ -560,132 +420,6 @@ export function initTimer() {
};
}
-/*
- * fullscreen means to open as modal
- * Look into store/reducers/windows.js to know what it means
- */
-export function openWindow(
- windowType,
- title = '',
- args = null,
- fullscreen = false,
- cloneable = true,
- xPos = null,
- yPos = null,
- width = null,
- height = null,
-) {
- return {
- type: 'OPEN_WINDOW',
- windowType,
- title,
- args,
- fullscreen,
- cloneable,
- xPos,
- yPos,
- width,
- height,
- };
-}
-
-export function setWindowArgs(
- windowId,
- args,
-) {
- return {
- type: 'SET_WINDOW_ARGS',
- windowId,
- args,
- };
-}
-
-function showFullscreenWindow(modalType, title) {
- return openWindow(
- modalType,
- title,
- null,
- true,
- );
-}
-
-export function closeFullscreenWindows() {
- return {
- type: 'CLOSE_FULLSCREEN_WINDOWS',
- };
-}
-
-export function showSettingsModal() {
- return showFullscreenWindow(
- 'SETTINGS',
- '',
- );
-}
-
-export function showUserAreaModal() {
- return showFullscreenWindow(
- 'USERAREA',
- '',
- );
-}
-
-export function changeWindowType(
- windowId,
- windowType,
- title = '',
- args = null,
-) {
- return {
- type: 'CHANGE_WINDOW_TYPE',
- windowId,
- windowType,
- title,
- args,
- };
-}
-
-export function setWindowTitle(windowId, title) {
- return {
- type: 'SET_WINDOW_TITLE',
- windowId,
- title,
- };
-}
-
-export function showRegisterModal() {
- return showFullscreenWindow(
- 'REGISTER',
- t`Register New Account`,
- );
-}
-
-export function showForgotPasswordModal() {
- return showFullscreenWindow(
- 'FORGOT_PASSWORD',
- t`Restore my Password`,
- );
-}
-
-export function showHelpModal() {
- return showFullscreenWindow(
- 'HELP',
- t`Welcome to PixelPlanet.fun`,
- );
-}
-export function showArchiveModal() {
- return showFullscreenWindow(
- 'ARCHIVE',
- t`Look at past Canvases`,
- );
-}
-
-export function showCanvasSelectionModal() {
- return showFullscreenWindow(
- 'CANVAS_SELECTION',
- '',
- );
-}
-
export function showContextMenu(
menuType,
xPos,
@@ -766,213 +500,6 @@ export function unmuteChatChannel(cid) {
};
}
-export function addToChatInputMessage(windowId, msg, focus = true) {
- return (dispatch, getState) => {
- const args = getState().windows.args[windowId];
- let inputMessage = args && args.inputMessage;
- if (!inputMessage) {
- inputMessage = '';
- } else if (inputMessage.slice(-1) !== ' ') {
- inputMessage += ' ';
- }
- inputMessage += msg;
-
- dispatch(setWindowArgs(windowId, {
- inputMessage,
- }));
-
- if (focus) {
- const inputElem = document.getElementById(`chtipt-${windowId}`);
- if (inputElem) {
- inputElem.focus();
- }
- }
- };
-}
-
-export function closeWindow(windowId) {
- return {
- type: 'CLOSE_WINDOW',
- windowId,
- };
-}
-
-export function removeWindow(windowId) {
- return {
- type: 'REMOVE_WINDOW',
- windowId,
- };
-}
-
-export function focusWindow(windowId) {
- return {
- type: 'FOCUS_WINDOW',
- windowId,
- };
-}
-
-export function cloneWindow(windowId) {
- return {
- type: 'CLONE_WINDOW',
- windowId,
- };
-}
-
-export function toggleMaximizeWindow(windowId) {
- return {
- type: 'TOGGLE_MAXIMIZE_WINDOW',
- windowId,
- };
-}
-
-export function moveWindow(windowId, xDiff, yDiff) {
- return {
- type: 'MOVE_WINDOW',
- windowId,
- xDiff,
- yDiff,
- };
-}
-
-export function resizeWindow(windowId, xDiff, yDiff) {
- return {
- type: 'RESIZE_WINDOW',
- windowId,
- xDiff,
- yDiff,
- };
-}
-
-export function closeAllWindowTypes(windowType) {
- return {
- type: 'CLOSE_ALL_WINDOW_TYPE',
- windowType,
- };
-}
-
-export function hideAllWindowTypes(
- windowType,
- hide,
-) {
- return {
- type: 'HIDE_ALL_WINDOW_TYPE',
- windowType,
- hide,
- };
-}
-
-export function openChatWindow() {
- const width = 350;
- const height = 350;
- return openWindow(
- 'CHAT',
- '',
- null,
- false,
- true,
- window.innerWidth - width - 62,
- window.innerHeight - height - 64,
- width,
- height,
- );
-}
-
-/*
- * query with either userId or userName
- */
-export function startDm(windowId, query) {
- return async (dispatch) => {
- dispatch(setApiFetching(true));
- const res = await requestStartDm(query);
- if (typeof res === 'string') {
- dispatch(pAlert(
- 'Direct Message Error',
- res,
- 'error',
- 'OK',
- ));
- } else {
- const cid = Number(Object.keys(res)[0]);
- dispatch(addChatChannel(res));
- dispatch(setWindowArgs(windowId, {
- chatChannel: cid,
- }));
- }
- dispatch(setApiFetching(false));
- };
-}
-
-export function gotCoolDownDelta(delta) {
- return {
- type: 'COOLDOWN_DELTA',
- delta,
- };
-}
-
-export function setUserBlock(
- userId,
- userName,
- block,
-) {
- return async (dispatch) => {
- dispatch(setApiFetching(true));
- const res = await requestBlock(userId, block);
- if (res) {
- dispatch(pAlert(
- 'User Block Error',
- res,
- 'error',
- 'OK',
- ));
- } else if (block) {
- dispatch(blockUser(userId, userName));
- } else {
- dispatch(unblockUser(userId, userName));
- }
- dispatch(setApiFetching(false));
- };
-}
-
-export function setBlockingDm(
- block,
-) {
- return async (dispatch) => {
- dispatch(setApiFetching(true));
- const res = await requestBlockDm(block);
- if (res) {
- dispatch(pAlert(
- 'Blocking DMs Error',
- res,
- 'error',
- 'OK',
- ));
- } else {
- dispatch(blockingDm(block));
- }
- dispatch(setApiFetching(false));
- };
-}
-
-export function setLeaveChannel(
- cid,
-) {
- return async (dispatch) => {
- dispatch(setApiFetching(true));
- const res = await requestLeaveChan(cid);
- if (res) {
- dispatch(pAlert(
- 'Leaving Channel Error',
- res,
- 'error',
- 'OK',
- ));
- } else {
- dispatch(removeChatChannel(cid));
- }
- dispatch(setApiFetching(false));
- };
-}
-
export function hideContextMenu() {
return {
type: 'HIDE_CONTEXT_MENU',
@@ -1004,3 +531,4 @@ export function urlChange() {
dispatch(reloadUrl());
};
}
+
diff --git a/src/store/actions/thunks.js b/src/store/actions/thunks.js
new file mode 100644
index 0000000..3816452
--- /dev/null
+++ b/src/store/actions/thunks.js
@@ -0,0 +1,206 @@
+/*
+ * thunk actions
+ */
+import {
+ requestStartDm,
+ requestBlock,
+ requestBlockDm,
+ requestLeaveChan,
+ requestRankings,
+ requestMe,
+} from './fetch';
+
+import {
+ setWindowArgs,
+} from './windows';
+import {
+ addChatChannel,
+ pAlert,
+ receiveStats,
+ receiveMe,
+ blockUser,
+ unblockUser,
+ blockingDm,
+ removeChatChannel,
+} from './index';
+
+function setApiFetching(fetching) {
+ return {
+ type: 'SET_API_FETCHING',
+ fetching,
+ };
+}
+
+function setChatFetching(fetching) {
+ return {
+ type: 'SET_CHAT_FETCHING',
+ fetching,
+ };
+}
+
+function receiveChatHistory(
+ cid,
+ history,
+) {
+ return {
+ type: 'RECEIVE_CHAT_HISTORY',
+ cid,
+ history,
+ };
+}
+
+/*
+ * query with either userId or userName
+ */
+export function startDm(windowId, query) {
+ return async (dispatch) => {
+ dispatch(setApiFetching(true));
+ const res = await requestStartDm(query);
+ if (typeof res === 'string') {
+ dispatch(pAlert(
+ 'Direct Message Error',
+ res,
+ 'error',
+ 'OK',
+ ));
+ } else {
+ const cid = Number(Object.keys(res)[0]);
+ dispatch(addChatChannel(res));
+ dispatch(setWindowArgs(windowId, {
+ chatChannel: cid,
+ }));
+ }
+ dispatch(setApiFetching(false));
+ };
+}
+
+export function fetchStats() {
+ return async (dispatch) => {
+ const rankings = await requestRankings();
+ if (!rankings.errors) {
+ dispatch(receiveStats(rankings));
+ }
+ };
+}
+
+export function fetchMe() {
+ return async (dispatch) => {
+ const me = await requestMe();
+ if (!me.errors) {
+ dispatch(receiveMe(me));
+ }
+ };
+}
+
+export function fetchChatMessages(
+ cid,
+) {
+ return async (dispatch) => {
+ dispatch(setChatFetching(true));
+ const response = await fetch(`api/chathistory?cid=${cid}&limit=50`, {
+ credentials: 'include',
+ });
+
+ /*
+ * timeout in order to not spam api requests and get rate limited
+ */
+ if (response.ok) {
+ setTimeout(() => { dispatch(setChatFetching(false)); }, 500);
+ const { history } = await response.json();
+ dispatch(receiveChatHistory(cid, history));
+ } else {
+ setTimeout(() => { dispatch(setChatFetching(false)); }, 5000);
+ }
+ };
+}
+
+export function setUserBlock(
+ userId,
+ userName,
+ block,
+) {
+ return async (dispatch) => {
+ dispatch(setApiFetching(true));
+ const res = await requestBlock(userId, block);
+ if (res) {
+ dispatch(pAlert(
+ 'User Block Error',
+ res,
+ 'error',
+ 'OK',
+ ));
+ } else if (block) {
+ dispatch(blockUser(userId, userName));
+ } else {
+ dispatch(unblockUser(userId, userName));
+ }
+ dispatch(setApiFetching(false));
+ };
+}
+
+export function setBlockingDm(
+ block,
+) {
+ return async (dispatch) => {
+ dispatch(setApiFetching(true));
+ const res = await requestBlockDm(block);
+ if (res) {
+ dispatch(pAlert(
+ 'Blocking DMs Error',
+ res,
+ 'error',
+ 'OK',
+ ));
+ } else {
+ dispatch(blockingDm(block));
+ }
+ dispatch(setApiFetching(false));
+ };
+}
+
+export function setLeaveChannel(
+ cid,
+) {
+ return async (dispatch) => {
+ dispatch(setApiFetching(true));
+ const res = await requestLeaveChan(cid);
+ if (res) {
+ dispatch(pAlert(
+ 'Leaving Channel Error',
+ res,
+ 'error',
+ 'OK',
+ ));
+ } else {
+ dispatch(removeChatChannel(cid));
+ }
+ dispatch(setApiFetching(false));
+ };
+}
+
+function setNotification(notification) {
+ return {
+ type: 'SET_NOTIFICATION',
+ notification,
+ };
+}
+
+function unsetNotification() {
+ return {
+ type: 'UNSET_NOTIFICATION',
+ };
+}
+
+let lastNotify = null;
+export function notify(notification) {
+ return (dispatch) => {
+ dispatch(setNotification(notification));
+ if (lastNotify) {
+ clearTimeout(lastNotify);
+ lastNotify = null;
+ }
+ lastNotify = setTimeout(() => {
+ dispatch(unsetNotification());
+ }, 1500);
+ };
+}
diff --git a/src/store/actions/windows.js b/src/store/actions/windows.js
new file mode 100644
index 0000000..dbb902a
--- /dev/null
+++ b/src/store/actions/windows.js
@@ -0,0 +1,238 @@
+/*
+ * Actions that are exclusively used by windows
+ */
+
+import { t } from 'ttag';
+
+export function openWindow(
+ windowType,
+ title = '',
+ args = null,
+ fullscreen = false,
+ cloneable = true,
+ xPos = null,
+ yPos = null,
+ width = null,
+ height = null,
+) {
+ return {
+ type: 'OPEN_WINDOW',
+ windowType,
+ title,
+ args,
+ fullscreen,
+ cloneable,
+ xPos,
+ yPos,
+ width,
+ height,
+ };
+}
+
+export function setWindowArgs(
+ windowId,
+ args,
+) {
+ return {
+ type: 'SET_WINDOW_ARGS',
+ windowId,
+ args,
+ };
+}
+
+function showFullscreenWindow(modalType, title) {
+ return openWindow(
+ modalType,
+ title,
+ null,
+ true,
+ );
+}
+
+export function closeFullscreenWindows() {
+ return {
+ type: 'CLOSE_FULLSCREEN_WINDOWS',
+ };
+}
+
+export function showSettingsModal() {
+ return showFullscreenWindow(
+ 'SETTINGS',
+ '',
+ );
+}
+
+export function showUserAreaModal() {
+ return showFullscreenWindow(
+ 'USERAREA',
+ '',
+ );
+}
+
+export function changeWindowType(
+ windowId,
+ windowType,
+ title = '',
+ args = null,
+) {
+ return {
+ type: 'CHANGE_WINDOW_TYPE',
+ windowId,
+ windowType,
+ title,
+ args,
+ };
+}
+
+export function setWindowTitle(windowId, title) {
+ return {
+ type: 'SET_WINDOW_TITLE',
+ windowId,
+ title,
+ };
+}
+
+export function showRegisterModal() {
+ return showFullscreenWindow(
+ 'REGISTER',
+ t`Register New Account`,
+ );
+}
+
+export function showForgotPasswordModal() {
+ return showFullscreenWindow(
+ 'FORGOT_PASSWORD',
+ t`Restore my Password`,
+ );
+}
+
+export function showHelpModal() {
+ return showFullscreenWindow(
+ 'HELP',
+ t`Welcome to PixelPlanet.fun`,
+ );
+}
+export function showArchiveModal() {
+ return showFullscreenWindow(
+ 'ARCHIVE',
+ t`Look at past Canvases`,
+ );
+}
+
+export function showCanvasSelectionModal() {
+ return showFullscreenWindow(
+ 'CANVAS_SELECTION',
+ '',
+ );
+}
+
+export function addToChatInputMessage(windowId, msg, focus = true) {
+ return (dispatch, getState) => {
+ const args = getState().windows.args[windowId];
+ let inputMessage = args && args.inputMessage;
+ if (!inputMessage) {
+ inputMessage = '';
+ } else if (inputMessage.slice(-1) !== ' ') {
+ inputMessage += ' ';
+ }
+ inputMessage += msg;
+
+ dispatch(setWindowArgs(windowId, {
+ inputMessage,
+ }));
+
+ if (focus) {
+ const inputElem = document.getElementById(`chtipt-${windowId}`);
+ if (inputElem) {
+ inputElem.focus();
+ }
+ }
+ };
+}
+
+export function closeWindow(windowId) {
+ return {
+ type: 'CLOSE_WINDOW',
+ windowId,
+ };
+}
+
+export function removeWindow(windowId) {
+ return {
+ type: 'REMOVE_WINDOW',
+ windowId,
+ };
+}
+
+export function focusWindow(windowId) {
+ return {
+ type: 'FOCUS_WINDOW',
+ windowId,
+ };
+}
+
+export function cloneWindow(windowId) {
+ return {
+ type: 'CLONE_WINDOW',
+ windowId,
+ };
+}
+
+export function toggleMaximizeWindow(windowId) {
+ return {
+ type: 'TOGGLE_MAXIMIZE_WINDOW',
+ windowId,
+ };
+}
+
+export function moveWindow(windowId, xDiff, yDiff) {
+ return {
+ type: 'MOVE_WINDOW',
+ windowId,
+ xDiff,
+ yDiff,
+ };
+}
+
+export function resizeWindow(windowId, xDiff, yDiff) {
+ return {
+ type: 'RESIZE_WINDOW',
+ windowId,
+ xDiff,
+ yDiff,
+ };
+}
+
+export function closeAllWindowTypes(windowType) {
+ return {
+ type: 'CLOSE_ALL_WINDOW_TYPE',
+ windowType,
+ };
+}
+
+export function hideAllWindowTypes(
+ windowType,
+ hide,
+) {
+ return {
+ type: 'HIDE_ALL_WINDOW_TYPE',
+ windowType,
+ hide,
+ };
+}
+
+export function openChatWindow() {
+ const width = 350;
+ const height = 350;
+ return openWindow(
+ 'CHAT',
+ '',
+ null,
+ false,
+ true,
+ window.innerWidth - width - 62,
+ window.innerHeight - height - 64,
+ width,
+ height,
+ );
+}
diff --git a/src/store/configureStore.js b/src/store/configureStore.js
index f76740e..e82599e 100644
--- a/src/store/configureStore.js
+++ b/src/store/configureStore.js
@@ -1,7 +1,15 @@
-import { applyMiddleware, createStore, compose } from 'redux';
+/*
+ * redux store
+ */
+
+/* eslint-disable no-console */
+
+import {
+ applyMiddleware, createStore, compose, combineReducers,
+} from 'redux';
import thunk from 'redux-thunk';
-import { persistStore, persistCombineReducers } from 'redux-persist';
-import localForage from 'localforage';
+import { persistStore, persistReducer } from 'redux-persist';
+import storage from 'redux-persist/lib/storage';
/*
* reducers
@@ -29,12 +37,23 @@ import array from './middleware/array';
import promise from './middleware/promise';
import notifications from './middleware/notifications';
import title from './middleware/title';
-import placePixelControl from './middleware/placePixelControl';
+// import placePixelControl from './middleware/placePixelControl';
import extensions from './middleware/extensions';
-const reducers = persistCombineReducers({
+const CURRENT_VERSION = 3;
+
+const reducers = persistReducer({
key: 'primary',
- storage: localForage,
+ storage,
+ version: CURRENT_VERSION,
+ migrate: (state, version) => {
+ if (version !== CURRENT_VERSION) {
+ console.log('Newer version run, resetting store.');
+ return Promise.resolve({});
+ }
+ console.log(`Store version: ${version}`);
+ return Promise.resolve(state);
+ },
blacklist: [
'user',
'canvas',
@@ -43,7 +62,7 @@ const reducers = persistCombineReducers({
'contextMenu',
'fetching',
],
-}, {
+}, combineReducers({
audio,
canvas,
gui,
@@ -55,7 +74,7 @@ const reducers = persistCombineReducers({
contextMenu,
chatRead,
fetching,
-});
+}));
const store = createStore(
reducers,
@@ -70,7 +89,7 @@ const store = createStore(
title,
socketClientHook,
rendererHook,
- placePixelControl,
+ // placePixelControl,
extensions,
),
),
diff --git a/src/store/middleware/audio.js b/src/store/middleware/audio.js
index 1d82bee..19004ae 100644
--- a/src/store/middleware/audio.js
+++ b/src/store/middleware/audio.js
@@ -68,30 +68,6 @@ export default (store) => (next) => (action) => {
break;
}
- case 'PIXEL_WAIT': {
- /*
- * TODO refactor other sounds also like this one
- * i.e. gain sould ramp to 0, do oscillator first
- */
- const oscillatorNode = context.createOscillator();
- const gainNode = context.createGain();
- const { currentTime } = context;
-
- oscillatorNode.type = 'sine';
- oscillatorNode.start(currentTime);
- oscillatorNode.frequency.setValueAtTime(1479.98, currentTime);
- oscillatorNode.frequency.exponentialRampToValueAtTime(
- 493.88,
- currentTime + 0.01,
- );
- oscillatorNode.stop(currentTime + 0.1);
- gainNode.gain.setValueAtTime(0.5, currentTime);
- gainNode.gain.setTargetAtTime(0, currentTime, 0.1);
- oscillatorNode.connect(gainNode);
- gainNode.connect(context.destination);
- break;
- }
-
case 'ALERT': {
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
@@ -143,40 +119,62 @@ export default (store) => (next) => (action) => {
break;
}
- case 'PLACED_PIXELS': {
- const { palette, selectedColor: color } = state.canvas;
- const colorsAmount = palette.colors.length;
+ case 'RECEIVE_PIXEL_RETURN': {
+ switch (action.retCode) {
+ case 0: {
+ // successfully placed pixel
+ const { palette, selectedColor: color } = state.canvas;
+ const colorsAmount = palette.colors.length;
- const clrFreq = 100 + Math.log(color / colorsAmount + 1) * 300;
- const oscillatorNode = context.createOscillator();
- const gainNode = context.createGain();
- const { currentTime } = context;
+ const clrFreq = 100 + Math.log(color / colorsAmount + 1) * 300;
+ const oscillatorNode = context.createOscillator();
+ const gainNode = context.createGain();
+ const { currentTime } = context;
- oscillatorNode.type = 'sine';
- oscillatorNode.frequency.setValueAtTime(clrFreq, currentTime);
- oscillatorNode.frequency.exponentialRampToValueAtTime(
- 1400,
- currentTime + 0.2,
- );
+ oscillatorNode.type = 'sine';
+ oscillatorNode.start(currentTime);
+ oscillatorNode.frequency.setValueAtTime(clrFreq, currentTime);
+ oscillatorNode.frequency.exponentialRampToValueAtTime(
+ 1400,
+ currentTime + 0.2,
+ );
+ oscillatorNode.stop(currentTime + 0.1);
+ gainNode.gain.setValueAtTime(0.5, currentTime);
+ gainNode.gain.setTargetAtTime(0, currentTime, 0.1);
+ oscillatorNode.connect(gainNode);
+ gainNode.connect(context.destination);
+ break;
+ }
+ case 9: {
+ // pixelstack used up
+ const oscillatorNode = context.createOscillator();
+ const gainNode = context.createGain();
+ const { currentTime } = context;
- gainNode.gain.setValueAtTime(0.5, currentTime);
- gainNode.gain.exponentialRampToValueAtTime(
- 0.2,
- currentTime + 0.1,
- );
-
- oscillatorNode.connect(gainNode);
- gainNode.connect(context.destination);
-
- oscillatorNode.start();
- oscillatorNode.stop(currentTime + 0.1);
+ oscillatorNode.type = 'sine';
+ oscillatorNode.start(currentTime);
+ oscillatorNode.frequency.setValueAtTime(1479.98, currentTime);
+ oscillatorNode.frequency.exponentialRampToValueAtTime(
+ 493.88,
+ currentTime + 0.01,
+ );
+ oscillatorNode.stop(currentTime + 0.1);
+ gainNode.gain.setValueAtTime(0.5, currentTime);
+ gainNode.gain.setTargetAtTime(0, currentTime, 0.1);
+ oscillatorNode.connect(gainNode);
+ gainNode.connect(context.destination);
+ break;
+ }
+ default:
+ // nothing
+ }
break;
}
case 'COOLDOWN_END': {
// do not play sound if last cooldown end was <5s ago
const { lastCoolDownEnd } = state.user;
- if (lastCoolDownEnd && lastCoolDownEnd.getTime() + 5000 > Date.now()) {
+ if (lastCoolDownEnd && lastCoolDownEnd + 5000 > Date.now()) {
break;
}
diff --git a/src/store/middleware/notifications.js b/src/store/middleware/notifications.js
index e76899e..dc6220a 100644
--- a/src/store/middleware/notifications.js
+++ b/src/store/middleware/notifications.js
@@ -2,14 +2,13 @@
* Notifications
*
*/
-
+import { t } from 'ttag';
export default (store) => (next) => (action) => {
try {
if (!document.hasFocus()) {
switch (action.type) {
- case 'RECEIVE_ME':
- case 'PLACED_PIXELS': {
+ case 'RECEIVE_ME': {
if (window.Notification
&& Notification.permission !== 'granted'
&& Notification.permission !== 'denied'
@@ -25,17 +24,17 @@ export default (store) => (next) => (action) => {
// do not notify if last cooldown end was <15s ago
const { lastCoolDownEnd } = state.user;
if (lastCoolDownEnd
- && lastCoolDownEnd.getTime() + 15000 > Date.now()) {
+ && lastCoolDownEnd + 15000 > Date.now()) {
break;
}
if (window.Notification && Notification.permission === 'granted') {
// eslint-disable-next-line no-new
- new Notification('Your next pixels are ready', {
+ new Notification(t`Your next pixels are ready`, {
icon: '/tile.png',
silent: false,
vibrate: [200, 100],
- body: 'You can now place more on pixelplanet.fun :)',
+ body: t`You can now place more on pixelplanet.fun :)`,
});
}
break;
@@ -52,11 +51,11 @@ export default (store) => (next) => (action) => {
if (window.Notification && Notification.permission === 'granted') {
// eslint-disable-next-line no-new
- new Notification(`${name} mentioned you`, {
+ new Notification(`${name} ${t`mentioned you`}`, {
icon: '/tile.png',
silent: false,
vibrate: [200, 100],
- body: 'You have new messages in chat',
+ body: t`You have new messages in chat`,
});
}
break;
diff --git a/src/store/middleware/placePixelControl.js b/src/store/middleware/placePixelControl.js
index a4ed170..2a52cd6 100644
--- a/src/store/middleware/placePixelControl.js
+++ b/src/store/middleware/placePixelControl.js
@@ -3,17 +3,36 @@
*
*/
-import { requestFromQueue } from '../../ui/placePixel';
+import { getRenderer } from '../../ui/renderer';
+import { receivePixelReturn } from '../../ui/placePixel';
export default (store) => (next) => (action) => {
+ const ret = next(action);
+
switch (action.type) {
- case 'CLOSE_ALERT': {
- requestFromQueue(store);
+ case 'RECEIVE_PIXEL_RETURN': {
+ const renderer = getRenderer();
+ const {
+ retCode,
+ wait,
+ coolDownSeconds,
+ pxlCnt,
+ rankedPxlCnt,
+ } = action;
+ receivePixelReturn(
+ store,
+ renderer,
+ retCode,
+ wait,
+ coolDownSeconds,
+ pxlCnt,
+ rankedPxlCnt,
+ );
break;
}
default:
// nothing
}
- return next(action);
+ return ret;
};
diff --git a/src/store/middleware/rendererHook.js b/src/store/middleware/rendererHook.js
index 6195a82..0f915aa 100644
--- a/src/store/middleware/rendererHook.js
+++ b/src/store/middleware/rendererHook.js
@@ -95,7 +95,7 @@ export default (store) => (next) => (action) => {
}
case 'TOGGLE_GRID':
- case 'SET_REQUESTING_PIXEL': {
+ case 'ALLOW_SETTING_PIXEL': {
const renderer = getRenderer();
renderer.forceNextSubrender = true;
break;
@@ -108,30 +108,21 @@ export default (store) => (next) => (action) => {
break;
}
- case 'UPDATE_PIXEL': {
- const {
- i,
- j,
- offset,
- color,
- notify,
- } = action;
- const renderer = getRenderer();
- renderer.renderPixel(i, j, offset, color, notify);
- break;
- }
-
case 'SET_VIEW_COORDINATES': {
const renderer = getRenderer();
renderer.updateView(state);
break;
}
- case 'COOLDOWN_DELTA': {
- const { delta } = action;
+ case 'RECEIVE_PIXEL_RETURN': {
const renderer = getRenderer();
- if (renderer && renderer.controls && renderer.controls.gotCoolDownDelta) {
- renderer.controls.gotCoolDownDelta(delta * -1);
+ renderer.forceNextSubrender = true;
+ const { coolDownSeconds } = action;
+ if (coolDownSeconds < 0
+ && renderer && renderer.controls
+ && renderer.controls.gotCoolDownDelta
+ ) {
+ renderer.controls.gotCoolDownDelta(coolDownSeconds * -1);
}
break;
}
diff --git a/src/store/reducers/ranks.js b/src/store/reducers/ranks.js
index acfb79a..b9d5df2 100644
--- a/src/store/reducers/ranks.js
+++ b/src/store/reducers/ranks.js
@@ -24,11 +24,16 @@ export default function ranks(
action,
) {
switch (action.type) {
- case 'PLACED_PIXELS': {
+ case 'RECEIVE_PIXEL_RETURN': {
+ const {
+ rankedPxlCnt,
+ } = action;
+ if (!rankedPxlCnt) {
+ return state;
+ }
let { totalPixels, dailyTotalPixels } = state;
- const { amount } = action;
- totalPixels += amount;
- dailyTotalPixels += amount;
+ totalPixels += rankedPxlCnt;
+ dailyTotalPixels += rankedPxlCnt;
return {
...state,
totalPixels,
diff --git a/src/store/reducers/user.js b/src/store/reducers/user.js
index b1d77f9..0cb21a7 100644
--- a/src/store/reducers/user.js
+++ b/src/store/reducers/user.js
@@ -4,7 +4,7 @@ const initialState = {
wait: null,
coolDown: null, // ms
lastCoolDownEnd: null,
- requestingPixel: true,
+ allowSettingPixel: true,
// messages are sent by api/me, like not_verified status
messages: [],
mailreg: false,
@@ -35,34 +35,34 @@ export default function user(
return {
...state,
coolDown: null,
- lastCoolDownEnd: new Date(),
+ lastCoolDownEnd: Date().now(),
wait: null,
};
}
- case 'SET_REQUESTING_PIXEL': {
- const { requestingPixel } = action;
+ case 'ALLOW_SETTING_PIXEL': {
+ const { allowSettingPixel } = action;
return {
...state,
- requestingPixel,
+ allowSettingPixel,
};
}
- case 'SET_WAIT': {
- const { wait: duration } = action;
- const wait = duration
- ? new Date(Date.now() + duration)
- : null;
+ case 'RECEIVE_PIXEL_RETURN': {
+ const {
+ wait: duration,
+ } = action;
return {
...state,
- wait,
+ wait: (duration) ? Date.now() + duration : state.wait,
+ allowSettingPixel: true,
};
}
case 'RECEIVE_COOLDOWN': {
const { wait: duration } = action;
const wait = duration
- ? new Date(Date.now() + duration)
+ ? Date.now() + duration
: null;
return {
...state,
diff --git a/src/store/reducers/windows.js b/src/store/reducers/windows.js
index 3095ccd..84ed18e 100644
--- a/src/store/reducers/windows.js
+++ b/src/store/reducers/windows.js
@@ -73,13 +73,20 @@ function clampPos(prefXPos, prefYPos, width, height) {
*/
function sortWindows(newState, force = false) {
if (newState.zMax >= MAX_AMOUNT_WINDOWS * 0.5 || force) {
- const orderedZ = newState.windows.map((win) => win.z)
+ const positions = { ...newState.positions };
+ const ids = Object.keys(positions);
+ const orderedZ = ids
+ .map((id) => positions[id].z)
.sort((a, b) => !b || (a && a >= b));
- newState.windows = newState.windows.map((win) => ({
- ...win,
- z: orderedZ.indexOf(win.z),
- }));
+ for (let i = 0; i < ids.length; i += 1) {
+ const id = ids[i];
+ positions[id] = {
+ ...positions[id],
+ z: orderedZ.indexOf(positions[id].z),
+ };
+ }
newState.zMax = orderedZ.length - 1;
+ newState.positions = positions;
}
return newState;
}
@@ -87,8 +94,6 @@ function sortWindows(newState, force = false) {
const initialState = {
// if windows get shown, false on small screens
showWindows: window.innerWidth > SCREEN_WIDTH_THRESHOLD,
- // if at least one window is in fullscreen
- someFullscreen: false,
// highest zIndex of window
zMax: 0,
// [
@@ -97,20 +102,25 @@ const initialState = {
// open: boolean,
// hidden: boolean,
// fullscreen: boolean,
- // z: number,
// windowType: string,
// title: string,
// title that is additionally shown to the window-type-title
- // width: number,
- // height: number,
- // xPos: percentage,
- // yPos: percentage,
// cloneable: boolean,
// },
// ]
windows: [],
// {
// windowId: {
+ // width: number,
+ // height: number,
+ // xPos: percentage,
+ // yPos: percentage,
+ // z: number,
+ // }
+ // }
+ positions: {},
+ // {
+ // windowId: {
// ...
// }
// }
@@ -152,39 +162,38 @@ export default function windows(
}
const windowId = generateWindowId(state);
const newZMax = state.zMax + 1;
- const newWindows = [
- ...state.windows,
- {
- windowId,
- windowType,
- open: true,
- hidden: false,
- fullscreen,
- z: newZMax,
- title,
- width,
- height,
- xPos,
- yPos,
- cloneable,
- },
- ];
-
- const someFullscreen = newWindows.some(
- (win) => win.fullscreen && !win.hidden,
- );
return sortWindows({
...state,
- someFullscreen,
zMax: newZMax,
- windows: newWindows,
+ windows: [
+ ...state.windows,
+ {
+ windowId,
+ windowType,
+ open: true,
+ hidden: false,
+ fullscreen,
+ title,
+ cloneable,
+ },
+ ],
args: {
...state.args,
[windowId]: {
...args,
},
},
+ positions: {
+ ...state.positions,
+ [windowId]: {
+ width,
+ height,
+ xPos,
+ yPos,
+ z: newZMax,
+ },
+ },
});
}
@@ -193,12 +202,15 @@ export default function windows(
windowId,
} = action;
const args = { ...state.args };
+ const positions = { ...state.positions };
delete args[windowId];
+ delete positions[windowId];
return {
...state,
windows: state.windows.filter((win) => win.windowId !== windowId),
args,
+ positions,
};
}
@@ -215,13 +227,8 @@ export default function windows(
};
});
- const someFullscreen = newWindows.some(
- (win) => win.fullscreen && !win.hidden,
- );
-
return {
...state,
- someFullscreen,
windows: newWindows,
};
}
@@ -238,13 +245,8 @@ export default function windows(
};
});
- const someFullscreen = newWindows.some(
- (win) => win.fullscreen && !win.hidden,
- );
-
return {
...state,
- someFullscreen,
windows: newWindows,
};
}
@@ -272,6 +274,7 @@ export default function windows(
windowId,
} = action;
const win = state.windows.find((w) => w.windowId === windowId);
+ const position = state.positions[windowId];
const newWindowId = generateWindowId(state);
const newZMax = state.zMax + 1;
const {
@@ -286,9 +289,6 @@ export default function windows(
{
...win,
windowId: newWindowId,
- xPos: Math.min(win.xPos + 15, width - SCREEN_MARGIN_EW),
- yPos: Math.min(win.yPos + 15, height - SCREEN_MARGIN_S),
- z: newZMax,
},
],
args: {
@@ -297,6 +297,15 @@ export default function windows(
...state.args[windowId],
},
},
+ positions: {
+ ...state.positions,
+ [newWindowId]: {
+ ...position,
+ xPos: Math.min(position.xPos + 15, width - SCREEN_MARGIN_EW),
+ yPos: Math.min(position.yPos + 15, height - SCREEN_MARGIN_S),
+ z: newZMax,
+ },
+ },
});
}
@@ -321,13 +330,8 @@ export default function windows(
};
});
- const someFullscreen = newWindows.some(
- (win) => win.fullscreen && !win.hidden,
- );
-
return {
...state,
- someFullscreen,
args,
windows: newWindows,
};
@@ -338,29 +342,22 @@ export default function windows(
windowId,
} = action;
const {
- windows: oldWindows, zMax,
+ zMax,
} = state;
-
- const newWindows = [];
-
- for (let i = 0; i < oldWindows.length; i += 1) {
- const win = oldWindows[i];
- if (win.windowId !== windowId) {
- newWindows.push(win);
- } else {
- if (win.z === zMax) {
- return state;
- }
- newWindows.push({
- ...win,
- z: zMax + 1,
- });
- }
+ const { z } = state.positions[windowId];
+ if (z === zMax) {
+ return state;
}
return sortWindows({
...state,
zMax: zMax + 1,
- windows: newWindows,
+ positions: {
+ ...state.positions,
+ [windowId]: {
+ ...state.positions[windowId],
+ z: zMax + 1,
+ },
+ },
});
}
@@ -379,13 +376,8 @@ export default function windows(
};
});
- const someFullscreen = newWindows.some(
- (win) => win.fullscreen && !win.hidden,
- );
-
return {
...state,
- someFullscreen,
windows: newWindows,
};
}
@@ -403,7 +395,6 @@ export default function windows(
return {
...state,
- someFullscreen: false,
windows: newWindows,
};
}
@@ -414,23 +405,23 @@ export default function windows(
xDiff,
yDiff,
} = action;
- const newWindows = state.windows.map((win) => {
- if (win.windowId !== windowId) return win;
- const [xPos, yPos] = clampPos(
- win.xPos + xDiff,
- win.yPos + yDiff,
- win.width,
- win.height,
- );
- return {
- ...win,
- xPos,
- yPos,
- };
- });
+ let {
+ xPos, yPos,
+ } = state.positions[windowId];
+ const {
+ width, height,
+ } = state.positions[windowId];
+ [xPos, yPos] = clampPos(xPos + xDiff, yPos + yDiff, width, height);
return {
...state,
- windows: newWindows,
+ positions: {
+ ...state.positions,
+ [windowId]: {
+ ...state.positions[windowId],
+ xPos,
+ yPos,
+ },
+ },
};
}
@@ -440,22 +431,18 @@ export default function windows(
xDiff,
yDiff,
} = action;
- const newWindows = state.windows.map((win) => {
- if (win.windowId !== windowId) return win;
- const [width, height] = clampSize(
- win.width + xDiff,
- win.height + yDiff,
- false,
- );
- return {
- ...win,
- width: Math.max(width, SCREEN_MARGIN_EW - win.xPos),
- height,
- };
- });
+ let { width, height } = state.positions[windowId];
+ [width, height] = clampSize(width + xDiff, height + yDiff, false);
return {
...state,
- windows: newWindows,
+ positions: {
+ ...state.positions,
+ [windowId]: {
+ ...state.positions[windowId],
+ width,
+ height,
+ },
+ },
};
}
@@ -466,16 +453,17 @@ export default function windows(
innerHeight: height,
} = window;
- let { windows: newWindows, args, someFullscreen } = state;
+ let { windows: newWindows, args, positions } = state;
const showWindows = width > SCREEN_WIDTH_THRESHOLD;
if (action.type === 'RECEIVE_ME') {
- if (state.modal || !showWindows) {
- // reset if out of date
+ if (!showWindows) {
+ // reset on phones on every refresh
return initialState;
}
- args = { ...state.args };
+ args = { ...args };
+ positions = { ...positions };
newWindows = newWindows.filter((win) => {
if (win.open && (win.fullscreen || showWindows)) {
@@ -486,12 +474,9 @@ export default function windows(
`Cleaning up window from previous session: ${win.windowId}`,
);
delete args[win.windowId];
+ delete positions[win.windowId];
return false;
});
-
- someFullscreen = newWindows.some(
- (win) => win.fullscreen && !win.hidden,
- );
}
if (!showWindows) {
@@ -499,45 +484,47 @@ export default function windows(
...state,
windows: newWindows,
showWindows,
- someFullscreen,
args,
+ positions,
};
}
const xMax = width - SCREEN_MARGIN_EW;
const yMax = height - SCREEN_MARGIN_S;
let modified = false;
- const fixWindows = [];
+ const newPositions = {};
for (let i = 0; i < newWindows.length; i += 1) {
- const win = newWindows[i];
+ const id = newWindows[i].windowId;
const {
xPos,
yPos,
width: winWidth,
height: winHeight,
- } = win;
+ } = positions[id];
if (xPos > xMax || yPos > yMax
|| width > winWidth || height > winHeight) {
modified = true;
- fixWindows.push({
- ...win,
+ newPositions[id] = {
xPos: Math.min(xMax, xPos),
yPos: Math.min(yMax, yPos),
width: Math.min(winWidth, width - SCREEN_MARGIN_S),
height: Math.min(winHeight, height - SCREEN_MARGIN_S),
- });
+ };
} else {
- fixWindows.push(win);
+ newPositions[id] = positions[id];
}
}
+ if (modified) {
+ positions = newPositions;
+ }
return {
...state,
- windows: (modified) ? fixWindows : newWindows,
+ windows: newWindows,
showWindows: true,
- someFullscreen,
args,
+ positions,
};
}
diff --git a/src/store/selectors/windows.js b/src/store/selectors/windows.js
new file mode 100644
index 0000000..6fff03b
--- /dev/null
+++ b/src/store/selectors/windows.js
@@ -0,0 +1,46 @@
+/*
+ * selectors for window manager
+ *
+ * Memoize heavy selectors or else they recalculate on any store update
+ * see https://redux.js.org/usage/deriving-data-selectors
+ */
+import { createSelector } from 'reselect';
+
+const selectWindows = (state) => state.windows.windows;
+export const selectShowWindows = (state) => state.windows.showWindows;
+
+export const selectIfFullscreen = createSelector(
+ selectWindows,
+ selectShowWindows,
+ (windows, showWindows) => [
+ windows.some((win) => win.fullscreen && !win.hidden) || showWindows,
+ windows.some((win) => win.fullscreen && win.open && !win.hidden),
+ ],
+);
+
+export const selectActiveWindowIds = createSelector(
+ selectWindows,
+ selectShowWindows,
+ (windows, showWindows) => {
+ if (!showWindows) {
+ windows = windows.filter((win) => win.fullscreen);
+ }
+ return windows.map((win) => win.windowId);
+ },
+);
+
+/*
+ * function factory returning a selector for given windowId
+ * use useMemo to cache result
+ */
+export const makeSelectWindowById = (windowId) => createSelector(
+ selectWindows,
+ (windows) => windows.find((win) => win.windowId === windowId),
+);
+
+/*
+ * function factory for non-memorized selector
+ * use useMemo to cache result
+ */
+// eslint-disable-next-line max-len
+export const makeSelectWindowPosById = (windowId) => (state) => state.windows.positions[windowId];
diff --git a/src/ui/Renderer2D.js b/src/ui/Renderer2D.js
index 3a4a1b9..70ec66f 100644
--- a/src/ui/Renderer2D.js
+++ b/src/ui/Renderer2D.js
@@ -424,7 +424,7 @@ class Renderer {
isLightGrid,
} = state.gui;
const {
- requestingPixel,
+ allowSettingPixel,
} = state.user;
const {
view,
@@ -444,13 +444,13 @@ class Renderer {
// if we have to render placeholder
const doRenderPlaceholder = (
viewscale >= 3
- && requestingPixel
+ && allowSettingPixel
&& (hover || this.hover)
&& !isPotato
);
const doRenderPotatoPlaceholder = (
viewscale >= 3
- && requestingPixel
+ && allowSettingPixel
&& (hover !== this.hover
|| this.forceNextRender
|| this.forceNextSubrender
diff --git a/src/ui/Renderer3D.js b/src/ui/Renderer3D.js
index 7b07895..c1451c7 100644
--- a/src/ui/Renderer3D.js
+++ b/src/ui/Renderer3D.js
@@ -398,7 +398,7 @@ class Renderer {
store,
} = this;
const {
- requestingPixel,
+ allowSettingPixel,
} = store.getState().user;
mouse.set(
@@ -414,7 +414,7 @@ class Renderer {
.add(intersect.face.normal.multiplyScalar(0.5))
.floor()
.addScalar(0.5);
- if (!requestingPixel
+ if (!allowSettingPixel
|| target.clone().sub(camera.position).length() > 120) {
rollOverMesh.position.y = -10;
} else {
@@ -442,7 +442,7 @@ class Renderer {
store,
} = this;
const {
- requestingPixel,
+ allowSettingPixel,
} = store.getState().user;
mouse.set(0, 0);
@@ -454,9 +454,9 @@ class Renderer {
.add(intersect.face.normal.multiplyScalar(0.5))
.floor()
.addScalar(0.5);
- // TODO make rollOverMesh in a different color while requestingPixel false
+ // TODO make rollOverMesh in a different color while allowSettingPixel false
// instead of hiding it.... we can now queue Voxels
- if (!requestingPixel
+ if (!allowSettingPixel
|| target.clone().sub(camera.position).length() > 50) {
rollOverMesh.position.y = -10;
} else {
@@ -482,6 +482,7 @@ class Renderer {
const [i, j] = getChunkOfPixel(canvasSize, x, y, z);
const offset = getOffsetOfPixel(canvasSize, x, y, z);
tryPlacePixel(
+ this,
store,
i, j,
offset,
@@ -578,10 +579,10 @@ class Renderer {
const state = this.store.getState();
const {
- requestingPixel,
+ allowSettingPixel,
isOnMobile,
} = state.user;
- if (!requestingPixel || isOnMobile) {
+ if (!allowSettingPixel || isOnMobile) {
return;
}
diff --git a/src/ui/placePixel.js b/src/ui/placePixel.js
index 6b193a9..ca61fa9 100644
--- a/src/ui/placePixel.js
+++ b/src/ui/placePixel.js
@@ -3,18 +3,19 @@
* Always just one pixelrequest, queue additional requests to send later
* Pixels get predicted on the client and reset if server refused
*
+ * TODO move stuff out of here and to actions / middleware
+ * clientPredictions could be in rendererHook
+ *
* */
import { t } from 'ttag';
import {
- notify,
- setRequestingPixel,
+ setAllowSettingPixel,
pAlert,
- gotCoolDownDelta,
- setWait,
- placedPixels,
- pixelWait,
- updatePixel,
+ storeReceivePixelReturn,
} from '../store/actions';
+import {
+ notify,
+} from '../store/actions/thunks';
import SocketClient from '../socket/SocketClient';
let pixelTimeout = null;
@@ -35,8 +36,10 @@ let clientPredictions = [];
*/
let lastRequestValues = {};
-
-export function requestFromQueue(store) {
+/*
+ * request pixel placement from queue
+ */
+function requestFromQueue(store) {
if (!pixelQueue.length) {
pixelTimeout = null;
return;
@@ -46,7 +49,7 @@ export function requestFromQueue(store) {
pixelTimeout = setTimeout(() => {
pixelQueue = [];
pixelTimeout = null;
- store.dispatch(setRequestingPixel(true));
+ store.dispatch(setAllowSettingPixel(true));
store.dispatch(pAlert(
t`Error :(`,
t`Didn't get an answer from pixelplanet. Maybe try to refresh?`,
@@ -57,11 +60,14 @@ export function requestFromQueue(store) {
lastRequestValues = pixelQueue.shift();
const { i, j, pixels } = lastRequestValues;
SocketClient.requestPlacePixels(i, j, pixels);
- store.dispatch(setRequestingPixel(false));
+ store.dispatch(setAllowSettingPixel(false));
}
+/*
+ * got pixel update from websocket
+ */
export function receivePixelUpdate(
- store,
+ renderer,
i,
j,
offset,
@@ -79,7 +85,7 @@ export function receivePixelUpdate(
return;
}
}
- store.dispatch(updatePixel(i, j, offset, color));
+ renderer.renderPixel(i, j, offset, color, true);
}
/*
@@ -87,7 +93,7 @@ export function receivePixelUpdate(
* @param i, j, offset data of the first pixel that got rejected
*/
function revertPredictionsAt(
- store,
+ renderer,
sI,
sJ,
sOffset,
@@ -111,14 +117,18 @@ function revertPredictionsAt(
while (p < clientPredictions.length) {
const [i, j, offset, color] = clientPredictions[p];
- store.dispatch(updatePixel(i, j, offset, color, false));
+ renderer.renderPixel(i, j, offset, color, false);
p += 1;
}
clientPredictions = [];
}
+/*
+ * try to place a pixel
+ */
export function tryPlacePixel(
+ renderer,
store,
i,
j,
@@ -126,7 +136,7 @@ export function tryPlacePixel(
color,
curColor,
) {
- store.dispatch(updatePixel(i, j, offset, color, false));
+ renderer.renderPixel(i, j, offset, color, false);
clientPredictions.push([i, j, offset, curColor, color]);
if (pixelQueue.length) {
@@ -150,28 +160,26 @@ export function tryPlacePixel(
}
}
+/*
+ * got return from pixel request
+ */
export function receivePixelReturn(
store,
- retCode,
- wait,
- coolDownSeconds,
- pxlCnt,
- rankedPxlCnt,
+ renderer,
+ args,
) {
clearTimeout(pixelTimeout);
- /*
- * the terms coolDown is used in a different meaning here
- * coolDown is the delta seconds of the placed pixel
- */
- if (wait) {
- store.dispatch(setWait(wait));
- }
+ store.dispatch(storeReceivePixelReturn(args));
+
+ const {
+ retCode,
+ coolDownSeconds,
+ pxlCnt,
+ } = args;
+
if (coolDownSeconds) {
store.dispatch(notify(coolDownSeconds));
- if (coolDownSeconds < 0) {
- store.dispatch(gotCoolDownDelta(coolDownSeconds));
- }
}
if (retCode) {
@@ -181,7 +189,7 @@ export function receivePixelReturn(
*/
const { i, j, pixels } = lastRequestValues;
const [offset] = pixels[pxlCnt];
- revertPredictionsAt(store, i, j, offset);
+ revertPredictionsAt(renderer, i, j, offset);
pixelQueue = [];
}
@@ -190,7 +198,6 @@ export function receivePixelReturn(
let type = 'error';
switch (retCode) {
case 0:
- store.dispatch(placedPixels(rankedPxlCnt));
break;
case 1:
errorTitle = t`Invalid Canvas`;
@@ -226,7 +233,6 @@ export function receivePixelReturn(
break;
case 9:
// pixestack used up
- store.dispatch(pixelWait());
break;
case 10:
errorTitle = 'Captcha';
@@ -267,11 +273,6 @@ export function receivePixelReturn(
));
}
- store.dispatch(setRequestingPixel(true));
-
- if (!msg) {
- /* start next request if queue isn't empty */
- requestFromQueue(store);
- }
+ requestFromQueue(store);
}