use reselect

reorganize windows reducer
This commit is contained in:
HF 2022-08-14 22:56:38 +02:00
parent 2f1a60fce7
commit 4429f2a573
42 changed files with 931 additions and 1060 deletions

54
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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));
});

View File

@ -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';

View File

@ -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) {

View File

@ -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,

View File

@ -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,

View File

@ -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 }) => {

View File

@ -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 (
<div
className={`window ${extraClasses}`}

View File

@ -9,29 +9,21 @@ import Window from './Window';
import Overlay from './Overlay';
import {
closeFullscreenWindows,
} from '../store/actions';
// eslint-disable-next-line max-len
const selectWindowIds = (state) => state.windows.windows.map((win) => win.windowId);
// eslint-disable-next-line max-len
const selectMeta = (state) => {
console.log('check');
return [
state.windows.showWindows,
state.windows.someFullscreen,
state.windows.windows.some((win) => win.fullscreen && win.open && !win.hidden),
]};
} from '../store/actions/windows';
import {
selectIfFullscreen,
selectActiveWindowIds,
} from '../store/selectors/windows';
const WindowManager = () => {
const windowIds = useSelector(selectWindowIds, shallowEqual);
const windowIds = useSelector(selectActiveWindowIds, shallowEqual);
const [
showWindows,
someFullscreen,
fullscreenExistOrShowWindows,
someOpenFullscreen,
] = useSelector(selectMeta, shallowEqual);
] = useSelector(selectIfFullscreen, shallowEqual);
const dispatch = useDispatch();
if ((!showWindows && !someFullscreen) || !windowIds.length) {
if (!fullscreenExistOrShowWindows || !windowIds.length) {
return null;
}

View File

@ -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 = () => {

View File

@ -12,7 +12,7 @@ import { t } from 'ttag';
import {
hideAllWindowTypes,
openChatWindow,
} from '../../store/actions';
} from '../../store/actions/windows';
/*
* return [ chatOpen, chatHiden ]

View File

@ -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 = () => {

View File

@ -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 = () => {

View File

@ -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 = () => {

View File

@ -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);

View File

@ -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 = () => {

View File

@ -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 }) => {

View File

@ -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';

View File

@ -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';

View File

@ -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) {

View File

@ -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';

View File

@ -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,

View File

@ -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'];

View File

@ -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() {

View File

@ -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,

View File

@ -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' };
```

View File

@ -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());
};
}

206
src/store/actions/thunks.js Normal file
View File

@ -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);
};
}

View File

@ -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,
);
}

View File

@ -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,
),
),

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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,

View File

@ -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,

View File

@ -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) {
// 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,
};
}

View File

@ -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];

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}