move context menu out of store
This commit is contained in:
parent
352649e3bd
commit
856b1288be
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -89,7 +89,7 @@ const MdLink = ({ href, title, refEmbed }) => {
|
|||
</>
|
||||
)}
|
||||
{showEmbed && embedAvailable && (
|
||||
(refEmbed)
|
||||
(refEmbed && refEmbed.current)
|
||||
? ReactDOM.createPortal(
|
||||
<Embed url={href} />,
|
||||
refEmbed.current,
|
||||
|
|
|
@ -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 />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
import UserContextMenu from './UserContextMenu';
|
||||
import ChannelContextMenu from './ChannelContextMenu';
|
||||
|
||||
export default {
|
||||
USER: UserContextMenu,
|
||||
CHANNEL: ChannelContextMenu,
|
||||
};
|
42
src/components/contextmenus/index.jsx
Normal file
42
src/components/contextmenus/index.jsx
Normal 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);
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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,7 +174,6 @@ const Chat = ({
|
|||
name="info"
|
||||
country="xx"
|
||||
msg={t`Start chatting here`}
|
||||
windowId={windowId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -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`}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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] || {};
|
||||
|
|
Loading…
Reference in New Issue
Block a user