move context menu out of store

This commit is contained in:
HF 2022-08-17 14:57:57 +02:00
parent 352649e3bd
commit 856b1288be
29 changed files with 256 additions and 306 deletions

View File

@ -19,8 +19,8 @@ import WindowManager from './WindowManager';
const iconContextValue = { style: { verticalAlign: 'middle' } };
const App = ({ store }) => (
<Provider store={store}>
const App = () => (
<>
<Style />
<IconContext.Provider value={iconContextValue}>
<CanvasSwitchButton />
@ -32,12 +32,16 @@ const App = ({ store }) => (
<UI />
<WindowManager />
</IconContext.Provider>
</Provider>
</>
);
function renderApp(domParent, store) {
const root = createRoot(domParent);
root.render(<App store={store} />);
root.render(
<Provider store={store}>
<App />
</Provider>,
);
}
export default renderApp;

View File

@ -12,18 +12,22 @@ import UIWin from './UIWin';
const iconContextValue = { style: { verticalAlign: 'middle' } };
const AppWin = ({ store }) => (
<Provider store={store}>
const AppWin = () => (
<>
<Style />
<IconContext.Provider value={iconContextValue}>
<UIWin />
</IconContext.Provider>
</Provider>
</>
);
function renderAppWin(domParent, store) {
const root = createRoot(domParent);
root.render(<AppWin store={store} />);
root.render(
<Provider store={store}>
<AppWin />
</Provider>,
);
}
export default renderAppWin;

View File

@ -1,7 +1,6 @@
import React, { useRef } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import { showContextMenu } from '../store/actions';
import { MarkdownParagraph } from './Markdown';
import {
colorFromText,
@ -11,19 +10,20 @@ import {
import { parseParagraph } from '../core/MarkdownParser';
const selectStyle = (state) => state.gui.style.indexOf('dark') !== -1;
function ChatMessage({
name,
uid,
country,
windowId,
msg,
ts,
openCm,
}) {
const dispatch = useDispatch();
const isDarkMode = useSelector(
(state) => state.gui.style.indexOf('dark') !== -1,
selectStyle,
);
const refEmbed = useRef(null);
const refEmbed = useRef();
const isInfo = (name === 'info');
const isEvent = (name === 'event');
@ -66,15 +66,7 @@ function ChatMessage({
title={name}
tabIndex={-1}
onClick={(event) => {
const {
clientX,
clientY,
} = event;
dispatch(showContextMenu('USER', clientX, clientY, {
windowId,
uid,
name,
}));
openCm(event.clientX, event.clientY, name, uid);
}}
>
{name}

View File

@ -89,7 +89,7 @@ const MdLink = ({ href, title, refEmbed }) => {
</>
)}
{showEmbed && embedAvailable && (
(refEmbed)
(refEmbed && refEmbed.current)
? ReactDOM.createPortal(
<Embed url={href} />,
refEmbed.current,

View File

@ -13,25 +13,18 @@ import Palette from './Palette';
import Alert from './Alert';
import HistorySelect from './HistorySelect';
import Mobile3DControls from './Mobile3DControls';
import ContextMenues from './contextmenus';
const UI = () => {
const [
isHistoricalView,
is3D,
isOnMobile,
menuOpen,
menuType,
] = useSelector((state) => [
state.canvas.isHistoricalView,
state.canvas.is3D,
state.user.isOnMobile,
state.contextMenu.menuOpen,
state.contextMenu.menuType,
], shallowEqual);
const ContextMenu = menuOpen && menuType && ContextMenues[menuType];
return (
<>
<Alert />
@ -47,7 +40,6 @@ const UI = () => {
</>
)}
<NotifyBox />
{ContextMenu && <ContextMenu />}
</>
);
};

View File

@ -16,10 +16,13 @@ import {
toggleMaximizeWindow,
cloneWindow,
focusWindow,
setWindowTitle,
setWindowArgs,
} from '../store/actions/windows';
import {
makeSelectWindowById,
makeSelectWindowPosById,
makeSelectWindowArgs,
selectShowWindows,
} from '../store/selectors/windows';
import useDrag from './hooks/drag';
@ -28,17 +31,26 @@ import COMPONENTS from './windows';
const Window = ({ id }) => {
const [render, setRender] = useState(false);
const titleBarRef = useRef(null);
const resizeRef = useRef(null);
const titleBarRef = useRef();
const resizeRef = useRef();
const selectWindowById = useMemo(() => makeSelectWindowById(id), []);
const selectWIndowPosById = useMemo(() => makeSelectWindowPosById(id), []);
const selectWindowArgs = useMemo(() => makeSelectWindowArgs(id), []);
const win = useSelector(selectWindowById);
const position = useSelector(selectWIndowPosById);
const showWindows = useSelector(selectShowWindows);
const args = useSelector(selectWindowArgs);
const dispatch = useDispatch();
const setArgs = useCallback(
(newArgs) => dispatch(setWindowArgs(id, newArgs),
), [dispatch]);
const setTitle = useCallback(
(title) => dispatch(setWindowTitle(id, title),
), [dispatch]);
const {
open,
hidden,
@ -156,7 +168,7 @@ const Window = ({ id }) => {
className="modal-content"
key="content"
>
<Content windowId={id} />
<Content args={args} setArgs={setArgs} setTitle={setTitle} />
</div>
</div>
);
@ -224,7 +236,7 @@ const Window = ({ id }) => {
className="win-content"
key="content"
>
<Content windowId={id} />
<Content args={args} setArgs={setArgs} setTitle={setTitle} />
</div>
</div>
);

View File

@ -36,7 +36,7 @@ const ChatButton = () => {
shallowEqual,
);
const chatNotify = useSelector((state) => state.audio.chatNotify);
const chatNotify = useSelector((state) => state.gui.chatNotify);
const channels = useSelector((state) => state.chat.channels);
const [unread, mute] = useSelector((state) => [
state.chatRead.unread,

View File

@ -2,15 +2,11 @@
*
*/
import React, { useRef, useCallback } from 'react';
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { t } from 'ttag';
import {
useClickOutside,
} from '../hooks/clickOutside';
import {
hideContextMenu,
muteChatChannel,
unmuteChatChannel,
} from '../../store/actions';
@ -18,31 +14,25 @@ import {
setLeaveChannel,
} from '../../store/actions/thunks';
const ChannelContextMenu = () => {
const wrapperRef = useRef(null);
/*
* args: {
* cid,
* }
*/
const ChannelContextMenu = ({ args, close }) => {
const channels = useSelector((state) => state.chat.channels);
const muteArr = useSelector((state) => state.chatRead.mute);
const { xPos, yPos, args } = useSelector((state) => state.contextMenu);
const { cid } = args;
const dispatch = useDispatch();
const close = useCallback(() => dispatch(hideContextMenu()), [dispatch]);
useClickOutside([wrapperRef], close);
const isMuted = muteArr.includes(cid);
return (
<div
ref={wrapperRef}
className="contextmenu"
style={{
right: window.innerWidth - xPos,
top: yPos,
}}
>
<>
<div
role="button"
key="mute"
onClick={() => {
if (isMuted) {
dispatch(unmuteChatChannel(cid));
@ -58,6 +48,7 @@ const ChannelContextMenu = () => {
{(channels[cid][1] !== 0)
&& (
<div
key="leave"
role="button"
onClick={() => {
dispatch(setLeaveChannel(cid));
@ -68,7 +59,7 @@ const ChannelContextMenu = () => {
{t`Close`}
</div>
)}
</div>
</>
);
};

View File

@ -32,7 +32,7 @@ const ChannelDropDown = ({
const unread = useSelector((state) => state.chatRead.unread);
const mute = useSelector((state) => state.chatRead.mute);
const channels = useSelector((state) => state.chat.channels);
const chatNotify = useSelector((state) => state.audio.chatNotify);
const chatNotify = useSelector((state) => state.gui.chatNotify);
useEffect(() => {
setOffset(buttonRef.current.clientHeight);

View File

@ -2,57 +2,46 @@
*
*/
import React, { useRef } from 'react';
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { t } from 'ttag';
import {
useClickOutside,
} from '../hooks/clickOutside';
import {
hideContextMenu,
} from '../../store/actions';
import {
startDm,
setUserBlock,
} from '../../store/actions/thunks';
import {
addToChatInputMessage,
setWindowArgs,
} from '../../store/actions/windows';
import { escapeMd } from '../../core/utils';
const UserContextMenu = () => {
const wrapperRef = useRef(null);
const { xPos, yPos, args } = useSelector((state) => state.contextMenu);
const { windowId, name, uid } = args;
/*
* args: {
* name,
* uid,
* setChannel,
* addToInput,
* }
*/
const UserContextMenu = ({ args, close }) => {
const channels = useSelector((state) => state.chat.channels);
const fetching = useSelector((state) => state.fetching.fetchingApi);
const dispatch = useDispatch();
const close = () => dispatch(hideContextMenu());
useClickOutside([wrapperRef], close);
const {
name,
uid,
setChannel,
addToInput,
} = args;
return (
<div
ref={wrapperRef}
className="contextmenu"
style={{
left: xPos,
top: yPos,
}}
>
<>
<div
role="button"
key="ping"
tabIndex={0}
onClick={() => {
const ping = `@[${escapeMd(name)}](${uid})`;
dispatch(
addToChatInputMessage(windowId, ping),
);
addToInput(ping);
close();
}}
style={{ borderTop: 'none' }}
@ -61,6 +50,7 @@ const UserContextMenu = () => {
</div>
<div
role="button"
key="dm"
tabIndex={0}
onClick={() => {
/*
@ -71,15 +61,13 @@ const UserContextMenu = () => {
for (let i = 0; i < cids.length; i += 1) {
const cid = cids[i];
if (channels[cid].length === 4 && channels[cid][3] === uid) {
dispatch(setWindowArgs(windowId, {
chatChannel: cid,
}));
setChannel(cid);
close();
return;
}
}
if (!fetching) {
dispatch(startDm(windowId, { userId: uid }));
dispatch(startDm({ userId: uid }, setChannel));
}
close();
}}
@ -87,17 +75,18 @@ const UserContextMenu = () => {
{t`DM`}
</div>
<div
role="button"
key="block"
tabIndex={-1}
onClick={() => {
dispatch(setUserBlock(uid, name, true));
close();
}}
role="button"
tabIndex={-1}
>
{t`Block`}
</div>
</div>
</>
);
};
export default UserContextMenu;
export default React.memo(UserContextMenu);

View File

@ -1,7 +0,0 @@
import UserContextMenu from './UserContextMenu';
import ChannelContextMenu from './ChannelContextMenu';
export default {
USER: UserContextMenu,
CHANNEL: ChannelContextMenu,
};

View File

@ -0,0 +1,42 @@
import React, { useRef } from 'react';
import ReactDOM from 'react-dom';
import UserContextMenu from './UserContextMenu';
import ChannelContextMenu from './ChannelContextMenu';
import {
useClickOutside,
} from '../hooks/clickOutside';
export const types = {
USER: UserContextMenu,
CHANNEL: ChannelContextMenu,
};
const ContextMenu = ({
type, x, y, args, close,
}) => {
const wrapperRef = useRef(null);
useClickOutside([wrapperRef], close);
if (!type) {
return null;
}
const Content = types[type];
return ReactDOM.createPortal((
<div
ref={wrapperRef}
className={`contextmenu ${type}`}
style={{
left: x,
top: y,
}}
>
<Content close={close} args={args} />
</div>
), document.getElementById('app'));
};
export default React.memo(ContextMenu);

View File

@ -1,9 +1,9 @@
/*
* @flex
*
* detect click outside
*/
/* eslint-disable consistent-return */
import { useEffect, useLayoutEffect, useCallback } from 'react';
/*
@ -47,9 +47,16 @@ export function useConditionalClickOutside(insideRefs, active, callback) {
*/
export function useClickOutside(insideRefs, callback) {
useEffect(() => {
const insideElems = insideRefs.some((ref) => ref.current)
? insideRefs.map((ref) => ref.current)
: null;
if (insideElems === null) {
return;
}
const handleClickOutside = (event) => {
if (insideRefs.every((ref) => !ref.current
|| !ref.current.contains(event.target))) {
if (insideElems.every((elem) => !elem || !elem.contains(event.target))) {
callback();
}
};
@ -62,11 +69,12 @@ export function useClickOutside(insideRefs, callback) {
capture: true,
});
window.addEventListener('resize', handleWindowResize);
return () => {
document.removeEventListener('mousedown', handleClickOutside, {
capture: true,
});
window.removeEventListener('resize', handleWindowResize);
};
}, [callback]);
}, [insideRefs, callback]);
}

View File

@ -1,9 +1,9 @@
/*
* @flex
*
* mouse draging
*/
/* eslint-disable consistent-return */
import { useEffect, useCallback } from 'react';
/*
@ -49,23 +49,22 @@ function useDrag(elRef, startHandler, diffHandler) {
}, [startHandler, diffHandler]);
useEffect(() => {
let prevCurrent = null;
const refElem = elRef.current;
if (elRef.current) {
elRef.current.addEventListener('mousedown', startDrag, {
passive: false,
});
elRef.current.addEventListener('touchstart', startDrag, {
passive: false,
});
prevCurrent = elRef.current;
if (!refElem) {
return;
}
refElem.addEventListener('mousedown', startDrag, {
passive: false,
});
refElem.addEventListener('touchstart', startDrag, {
passive: false,
});
return () => {
if (prevCurrent) {
prevCurrent.removeEventListener('mousedown', startDrag);
prevCurrent.removeEventListener('touchstart', startDrag);
}
refElem.removeEventListener('mousedown', startDrag);
refElem.removeEventListener('touchstart', startDrag);
};
}, [elRef, startDrag]);
}

View File

@ -9,17 +9,15 @@ import useStayScrolled from 'react-stay-scrolled';
import { useSelector, useDispatch } from 'react-redux';
import { t } from 'ttag';
import ContextMenu from '../contextmenus';
import ChatMessage from '../ChatMessage';
import ChannelDropDown from '../contextmenus/ChannelDropDown';
import {
showContextMenu,
markChannelAsRead,
} from '../../store/actions';
import {
showUserAreaModal,
setWindowTitle,
setWindowArgs,
} from '../../store/actions/windows';
import {
fetchChatMessages,
@ -28,38 +26,66 @@ import SocketClient from '../../socket/SocketClient';
const Chat = ({
windowId,
args,
setArgs,
setTitle,
}) => {
const listRef = useRef();
const targetRef = useRef();
const inputRef = useRef();
const [blockedIds, setBlockedIds] = useState([]);
const [btnSize, setBtnSize] = useState(20);
const [cmArgs, setCmArgs] = useState({});
const dispatch = useDispatch();
const setChannel = useCallback((cid) => {
dispatch(markChannelAsRead(cid));
dispatch(setWindowArgs(windowId, {
chatChannel: Number(cid),
}));
}, [dispatch]);
const setChatInputMessage = useCallback((msg) => {
dispatch(setWindowArgs(windowId, {
inputMessage: msg,
}));
}, [dispatch]);
const ownName = useSelector((state) => state.user.name);
// eslint-disable-next-line max-len
const fetching = useSelector((state) => state.fetching.fetchingChat);
const { channels, messages, blocked } = useSelector((state) => state.chat);
const {
chatChannel = 1,
inputMessage = '',
} = useSelector((state) => state.windows.args[windowId] || {});
} = args;
const setChannel = useCallback((cid) => {
dispatch(markChannelAsRead(cid));
setArgs({
chatChannel: Number(cid),
});
}, [dispatch]);
const addToInput = useCallback((msg) => {
const inputElem = inputRef.current;
if (!inputElem) {
return;
}
let newInputMessage = inputElem.value;
if (newInputMessage.slice(-1) !== ' ') {
newInputMessage += ' ';
}
newInputMessage += `${msg} `;
inputElem.value = newInputMessage;
inputRef.current.focus();
}, []);
const closeCm = useCallback(() => {
setCmArgs({});
}, []);
const openUserCm = useCallback((x, y, name, uid) => {
setCmArgs({
type: 'USER',
x,
y,
args: {
name,
uid,
setChannel,
addToInput,
},
});
}, [setChannel, addToInput]);
const { stayScrolled } = useStayScrolled(listRef, {
initialScroll: Infinity,
@ -76,7 +102,7 @@ const Chat = ({
useEffect(() => {
if (channels[chatChannel]) {
const channelName = channels[chatChannel][0];
dispatch(setWindowTitle(windowId, `Chan: ${channelName}`));
setTitle(`Chan: ${channelName}`);
}
}, [chatChannel]);
@ -101,11 +127,11 @@ const Chat = ({
function handleSubmit(evt) {
evt.preventDefault();
const inptMsg = inputMessage.trim();
const inptMsg = inputRef.current.value.trim();
if (!inptMsg) return;
// send message via websocket
SocketClient.sendChatMessage(inptMsg, chatChannel);
setChatInputMessage('');
inputRef.current.value = '';
}
/*
@ -127,6 +153,13 @@ const Chat = ({
ref={targetRef}
className="chat-container"
>
<ContextMenu
type={cmArgs.type}
x={cmArgs.x}
y={cmArgs.y}
args={cmArgs.args}
close={closeCm}
/>
<ul
className="chatarea"
ref={listRef}
@ -141,10 +174,9 @@ const Chat = ({
name="info"
country="xx"
msg={t`Start chatting here`}
windowId={windowId}
/>
)
}
}
{
channelMessages.map((message) => ((blockedIds.includes(message[3]))
? null : (
@ -155,7 +187,7 @@ const Chat = ({
uid={message[3]}
ts={message[4]}
key={message[5]}
windowId={windowId}
openCm={openUserCm}
/>
)))
}
@ -168,15 +200,13 @@ const Chat = ({
}}
>
{(ownName) ? (
<React.Fragment key={`chtipt-${windowId}`}>
<React.Fragment key="chtipt">
<input
style={{
flexGrow: 1,
minWidth: 40,
}}
id={`chtipt-${windowId}`}
value={inputMessage}
onChange={(e) => setChatInputMessage(e.target.value)}
ref={inputRef}
autoComplete="off"
maxLength="200"
type="text"
@ -193,6 +223,7 @@ const Chat = ({
) : (
<div
className="modallink"
key="nlipt"
onClick={() => dispatch(showUserAreaModal())}
style={{
textAlign: 'center',
@ -206,6 +237,7 @@ const Chat = ({
</div>
)}
<ChannelDropDown
key="cdd"
setChatChannel={setChannel}
chatChannel={chatChannel}
/>
@ -219,15 +251,15 @@ const Chat = ({
<span
onClick={(event) => {
const {
clientX,
clientY,
clientX: x,
clientY: y,
} = event;
dispatch(showContextMenu(
'CHANNEL',
clientX,
clientY,
{ cid: chatChannel },
));
setCmArgs({
type: 'CHANNEL',
x,
y,
args: { cid: chatChannel },
});
}}
role="button"
title={t`Channel settings`}

View File

@ -72,8 +72,8 @@ function Settings() {
state.gui.isPotato,
state.gui.isLightGrid,
state.gui.style,
state.audio.mute,
state.audio.chatNotify,
state.gui.mute,
state.gui.chatNotify,
state.canvas.isHistoricalView,
], shallowEqual);
const dispatch = useDispatch();

View File

@ -328,7 +328,7 @@ export async function executeWatchAction(
&& Date.now() - ts > 5 * 60 * 1000
&& !iid
) {
return { info: 'Cann not watch so many pixels' };
return { info: 'Can not watch so many pixels' };
}
if (action === 'summary') {

View File

@ -419,21 +419,6 @@ export function initTimer() {
};
}
export function showContextMenu(
menuType,
xPos,
yPos,
args,
) {
return {
type: 'SHOW_CONTEXT_MENU',
menuType,
xPos,
yPos,
args,
};
}
export function openChatChannel(cid) {
return {
type: 'OPEN_CHAT_CHANNEL',
@ -499,12 +484,6 @@ export function unmuteChatChannel(cid) {
};
}
export function hideContextMenu() {
return {
type: 'HIDE_CONTEXT_MENU',
};
}
export function reloadUrl() {
return {
type: 'RELOAD_URL',

View File

@ -10,9 +10,6 @@ import {
requestMe,
} from './fetch';
import {
setWindowArgs,
} from './windows';
import {
addChatChannel,
pAlert,
@ -52,7 +49,7 @@ function receiveChatHistory(
/*
* query with either userId or userName
*/
export function startDm(windowId, query) {
export function startDm(query, cb = null) {
return async (dispatch) => {
dispatch(setApiFetching(true));
const res = await requestStartDm(query);
@ -66,9 +63,9 @@ export function startDm(windowId, query) {
} else {
const cid = Number(Object.keys(res)[0]);
dispatch(addChatChannel(res));
dispatch(setWindowArgs(windowId, {
chatChannel: cid,
}));
if (cb) {
cb(cid);
}
}
dispatch(setApiFetching(false));
};

View File

@ -126,30 +126,6 @@ export function showCanvasSelectionModal() {
);
}
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_WIN',

View File

@ -14,7 +14,6 @@ import storage from 'redux-persist/lib/storage';
/*
* reducers
*/
import audio from './reducers/audio';
import canvas from './reducers/canvas';
import gui from './reducers/gui';
import windows from './reducers/windows';
@ -22,14 +21,13 @@ import user from './reducers/user';
import ranks from './reducers/ranks';
import alert from './reducers/alert';
import chat from './reducers/chat';
import contextMenu from './reducers/contextMenu';
import chatRead from './reducers/chatRead';
import fetching from './reducers/fetching';
/*
* middleware
*/
import audiom from './middleware/audio';
import audio from './middleware/audio';
import socketClientHook from './middleware/socketClientHook';
import rendererHook from './middleware/rendererHook';
import array from './middleware/array';
@ -38,7 +36,7 @@ import notifications from './middleware/notifications';
import title from './middleware/title';
import extensions from './middleware/extensions';
const CURRENT_VERSION = 3;
const CURRENT_VERSION = 5;
const reducers = persistReducer({
key: 'primary',
@ -58,11 +56,9 @@ const reducers = persistReducer({
'canvas',
'alert',
'chat',
'contextMenu',
'fetching',
],
}, combineReducers({
audio,
canvas,
gui,
windows,
@ -70,7 +66,6 @@ const reducers = persistReducer({
ranks,
alert,
chat,
contextMenu,
chatRead,
fetching,
}));
@ -83,7 +78,7 @@ const store = createStore(
thunk,
promise,
array,
audiom,
audio,
notifications,
title,
socketClientHook,

View File

@ -14,7 +14,6 @@ import storage from 'redux-persist/lib/storage';
/*
* reducers
*/
import audio from './reducers/audio';
import canvas from './reducers/canvas';
import gui from './reducers/gui';
import win from './reducers/win';
@ -44,7 +43,6 @@ const reducers = persistReducer({
'win',
],
}, combineReducers({
audio,
canvas,
gui,
win,
@ -62,6 +60,9 @@ const store = createStore(
);
persistStore(store);
export const persistor = persistStore(store);
export default store;
// persistent stores: gui, windows, ranks, chatRead;

View File

@ -9,7 +9,7 @@ const context = AudioContext && new AudioContext();
export default (store) => (next) => (action) => {
const state = store.getState();
const { mute, chatNotify } = state.audio;
const { mute, chatNotify } = state.gui;
if (!mute && context) {
switch (action.type) {

View File

@ -42,7 +42,7 @@ export default (store) => (next) => (action) => {
case 'REC_CHAT_MESSAGE': {
const state = store.getState();
const { chatNotify } = state.audio;
const { chatNotify } = state.gui;
if (!chatNotify) break;
const { isPing } = action;

View File

@ -1,27 +0,0 @@
const initialState = {
mute: false,
chatNotify: true,
};
export default function audio(
state = initialState,
action,
) {
switch (action.type) {
case 'TOGGLE_MUTE':
return {
...state,
mute: !state.mute,
};
case 'TOGGLE_CHAT_NOTIFY':
return {
...state,
chatNotify: !state.chatNotify,
};
default:
return state;
}
}

View File

@ -1,47 +0,0 @@
/**
* https://stackoverflow.com/questions/35623656/how-can-i-display-a-modal-dialog-in-redux-that-performs-asynchronous-actions/35641680#35641680
*
*/
const initialState = {
menuOpen: false,
menuType: null,
xPos: 0,
yPos: 0,
args: {},
};
export default function contextMenu(
state = initialState,
action,
) {
switch (action.type) {
case 'SHOW_CONTEXT_MENU': {
const {
menuType, xPos, yPos, args,
} = action;
return {
...state,
menuType,
xPos,
yPos,
menuOpen: true,
args: {
...args,
},
};
}
case 'WIN_RESIZE':
case 'HIDE_CONTEXT_MENU': {
return {
...state,
menuOpen: false,
};
}
default:
return state;
}
}

View File

@ -7,6 +7,8 @@ const initialState = {
isLightGrid: false,
compactPalette: false,
paletteOpen: true,
mute: false,
chatNotify: true,
// top-left button menu
menuOpen: false,
// show online users per canvas instead of total
@ -123,6 +125,18 @@ export default function gui(
};
}
case 'TOGGLE_MUTE':
return {
...state,
mute: !state.mute,
};
case 'TOGGLE_CHAT_NOTIFY':
return {
...state,
chatNotify: !state.chatNotify,
};
default:
return state;
}

View File

@ -446,7 +446,7 @@ export default function windows(
};
}
case 'REC_ME':
case 'persist/REHYDRATE':
case 'WIN_RESIZE': {
const {
innerWidth: width,
@ -456,7 +456,8 @@ export default function windows(
let { windows: newWindows, args, positions } = state;
const showWindows = width > SCREEN_WIDTH_THRESHOLD;
if (action.type === 'REC_ME') {
if (action.type === 'persist/REHYDRATE') {
console.log('persist', state, action.payload);
if (!showWindows) {
// reset on phones on every refresh
return initialState;

View File

@ -44,3 +44,6 @@ export const makeSelectWindowById = (windowId) => createSelector(
*/
// eslint-disable-next-line max-len
export const makeSelectWindowPosById = (windowId) => (state) => state.windows.positions[windowId];
// eslint-disable-next-line max-len
export const makeSelectWindowArgs = (windowId) => (state) => state.windows.args[windowId] || {};