add basic window reducer and add chat to it
This commit is contained in:
parent
3b0dcd9545
commit
baca212686
|
@ -610,28 +610,21 @@ export function showContextMenu(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setChatInputMessage(message: string): Action {
|
|
||||||
return {
|
|
||||||
type: 'SET_CHAT_INPUT_MSG',
|
|
||||||
message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addToChatInputMessage(message: string): Action {
|
|
||||||
return {
|
|
||||||
type: 'ADD_CHAT_INPUT_MSG',
|
|
||||||
message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function showChatModal(forceModal: boolean = false): Action {
|
export function showChatModal(forceModal: boolean = false): Action {
|
||||||
if (window.innerWidth > 604 && !forceModal) { return toggleChatBox(); }
|
if (window.innerWidth > 604 && !forceModal) { return toggleChatBox(); }
|
||||||
return showModal('CHAT');
|
return showModal('CHAT');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setChatChannel(cid: number): Action {
|
export function openChatChannel(cid: number): Action {
|
||||||
return {
|
return {
|
||||||
type: 'SET_CHAT_CHANNEL',
|
type: 'OPEN_CHAT_CHANNEL',
|
||||||
|
cid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeChatChannel(cid: number): Action {
|
||||||
|
return {
|
||||||
|
type: 'CLOSE_CHAT_CHANNEL',
|
||||||
cid,
|
cid,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -687,10 +680,59 @@ export function unmuteChatChannel(cid: number): Action {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setChatChannel(windowId: number, cid: number): Action {
|
||||||
|
return {
|
||||||
|
type: 'SET_CHAT_CHANNEL',
|
||||||
|
windowId,
|
||||||
|
cid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setChatInputMessage(windowId: number, msg: string): Action {
|
||||||
|
return {
|
||||||
|
type: 'SET_CHAT_INPUT_MSG',
|
||||||
|
windowId,
|
||||||
|
msg,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addToChatInputMessage(windowId: number, msg: string): Action {
|
||||||
|
return {
|
||||||
|
type: 'ADD_CHAT_INPUT_MSG',
|
||||||
|
windowId,
|
||||||
|
msg,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function moveWindow(windowId, xDiff, yDiff): Action {
|
||||||
|
return {
|
||||||
|
type: 'MOVE_WINDOW',
|
||||||
|
windowId,
|
||||||
|
xDiff,
|
||||||
|
yDiff,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openChatWindow(): Action {
|
||||||
|
return {
|
||||||
|
type: 'OPEN_WINDOW',
|
||||||
|
windowType: 'CHAT',
|
||||||
|
title: 'chat',
|
||||||
|
width: 700,
|
||||||
|
height: 300,
|
||||||
|
xPos: 100,
|
||||||
|
yPos: 100,
|
||||||
|
args: {
|
||||||
|
chatChannel: 1,
|
||||||
|
inputMessage: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* query: Object with either userId: number or userName: string
|
* query: Object with either userId: number or userName: string
|
||||||
*/
|
*/
|
||||||
export function startDm(query): PromiseAction {
|
export function startDm(windowId, query): PromiseAction {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
dispatch(setApiFetching(true));
|
dispatch(setApiFetching(true));
|
||||||
const res = await requestStartDm(query);
|
const res = await requestStartDm(query);
|
||||||
|
@ -704,7 +746,7 @@ export function startDm(query): PromiseAction {
|
||||||
} else {
|
} else {
|
||||||
const cid = Object.keys(res)[0];
|
const cid = Object.keys(res)[0];
|
||||||
dispatch(addChatChannel(res));
|
dispatch(addChatChannel(res));
|
||||||
dispatch(setChatChannel(cid));
|
dispatch(setChatChannel(windowId, cid));
|
||||||
}
|
}
|
||||||
dispatch(setApiFetching(false));
|
dispatch(setApiFetching(false));
|
||||||
};
|
};
|
||||||
|
|
|
@ -65,14 +65,17 @@ export type Action =
|
||||||
isPing: boolean,
|
isPing: boolean,
|
||||||
}
|
}
|
||||||
| { type: 'RECEIVE_CHAT_HISTORY', cid: number, history: Array }
|
| { type: 'RECEIVE_CHAT_HISTORY', cid: number, history: Array }
|
||||||
| { type: 'SET_CHAT_CHANNEL', cid: number }
|
| { type: 'OPEN_CHAT_CHANNEL', cid: number }
|
||||||
|
| { type: 'CLOSE_CHAT_CHANNEL', cid: number }
|
||||||
| { type: 'ADD_CHAT_CHANNEL', channel: Object }
|
| { type: 'ADD_CHAT_CHANNEL', channel: Object }
|
||||||
| { type: 'REMOVE_CHAT_CHANNEL', cid: number }
|
| { type: 'REMOVE_CHAT_CHANNEL', cid: number }
|
||||||
| { type: 'MUTE_CHAT_CHANNEL', cid: number }
|
| { type: 'MUTE_CHAT_CHANNEL', cid: number }
|
||||||
| { type: 'UNMUTE_CHAT_CHANNEL', cid: number }
|
| { type: 'UNMUTE_CHAT_CHANNEL', cid: number }
|
||||||
|
| { type: 'SET_CHAT_CHANNEL', windowId: number, cid: number }
|
||||||
|
| { type: 'SET_CHAT_INPUT_MSG', windowId: number, msg: string }
|
||||||
|
| { type: 'ADD_CHAT_INPUT_MSG', windowId: number, msg: string }
|
||||||
| { type: 'SET_CHAT_FETCHING', fetching: boolean }
|
| { type: 'SET_CHAT_FETCHING', fetching: boolean }
|
||||||
| { type: 'SET_CHAT_INPUT_MSG', message: string }
|
| { type: 'MOVE_WINDOW', windowId: number, xDiff: number, yDiff: number }
|
||||||
| { type: 'ADD_CHAT_INPUT_MSG', message: string }
|
|
||||||
| { type: 'BLOCK_USER', userId: number, userName: string }
|
| { type: 'BLOCK_USER', userId: number, userName: string }
|
||||||
| { type: 'UNBLOCK_USER', userId: number, userName: string }
|
| { type: 'UNBLOCK_USER', userId: number, userName: string }
|
||||||
| { type: 'SET_BLOCKING_DM', blockDm: boolean }
|
| { type: 'SET_BLOCKING_DM', blockDm: boolean }
|
||||||
|
|
|
@ -67,7 +67,7 @@ function init() {
|
||||||
name,
|
name,
|
||||||
text,
|
text,
|
||||||
country,
|
country,
|
||||||
channelId,
|
Number(channelId),
|
||||||
userId,
|
userId,
|
||||||
isPing,
|
isPing,
|
||||||
));
|
));
|
||||||
|
@ -90,9 +90,8 @@ function init() {
|
||||||
//
|
//
|
||||||
function checkMobile() {
|
function checkMobile() {
|
||||||
store.dispatch(setMobile(true));
|
store.dispatch(setMobile(true));
|
||||||
document.removeEventListener('touchstart', checkMobile, false);
|
|
||||||
}
|
}
|
||||||
document.addEventListener('touchstart', checkMobile, false);
|
document.addEventListener('touchstart', checkMobile, { once: true });
|
||||||
|
|
||||||
store.dispatch(initTimer());
|
store.dispatch(initTimer());
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import Menu from './Menu';
|
||||||
import UI from './UI';
|
import UI from './UI';
|
||||||
import ExpandMenuButton from './ExpandMenuButton';
|
import ExpandMenuButton from './ExpandMenuButton';
|
||||||
import ModalRoot from './ModalRoot';
|
import ModalRoot from './ModalRoot';
|
||||||
|
import WindowsRoot from './WindowsRoot';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<div>
|
<div>
|
||||||
|
@ -35,6 +36,7 @@ const App = () => (
|
||||||
<ExpandMenuButton />
|
<ExpandMenuButton />
|
||||||
<UI />
|
<UI />
|
||||||
<ModalRoot />
|
<ModalRoot />
|
||||||
|
<WindowsRoot />
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
import type { State } from '../reducers';
|
import type { State } from '../reducers';
|
||||||
|
|
||||||
const UserContextMenu = ({
|
const ChannelContextMenu = ({
|
||||||
xPos,
|
xPos,
|
||||||
yPos,
|
yPos,
|
||||||
cid,
|
cid,
|
||||||
|
@ -128,4 +128,4 @@ function mapDispatchToProps(dispatch) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(UserContextMenu);
|
export default connect(mapStateToProps, mapDispatchToProps)(ChannelContextMenu);
|
||||||
|
|
|
@ -7,22 +7,15 @@
|
||||||
import React, {
|
import React, {
|
||||||
useRef, useState, useEffect, useCallback, useLayoutEffect,
|
useRef, useState, useEffect, useCallback, useLayoutEffect,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { MdChat } from 'react-icons/md';
|
import { MdChat } from 'react-icons/md';
|
||||||
import { FaUserFriends } from 'react-icons/fa';
|
import { FaUserFriends } from 'react-icons/fa';
|
||||||
|
|
||||||
import type { State } from '../reducers';
|
import type { State } from '../reducers';
|
||||||
import {
|
|
||||||
setChatChannel,
|
|
||||||
} from '../actions';
|
|
||||||
|
|
||||||
const ChannelDropDown = ({
|
const ChannelDropDown = ({
|
||||||
channels,
|
setChatChannel,
|
||||||
chatChannel,
|
chatChannel,
|
||||||
unread,
|
|
||||||
chatNotify,
|
|
||||||
mute,
|
|
||||||
setChannel,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
const [sortChans, setSortChans] = useState([]);
|
const [sortChans, setSortChans] = useState([]);
|
||||||
|
@ -36,6 +29,12 @@ const ChannelDropDown = ({
|
||||||
const wrapperRef = useRef(null);
|
const wrapperRef = useRef(null);
|
||||||
const buttonRef = useRef(null);
|
const buttonRef = useRef(null);
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOffset(buttonRef.current.clientHeight);
|
setOffset(buttonRef.current.clientHeight);
|
||||||
}, [buttonRef]);
|
}, [buttonRef]);
|
||||||
|
@ -200,7 +199,7 @@ const ChannelDropDown = ({
|
||||||
const [cid, unreadCh, name] = ch;
|
const [cid, unreadCh, name] = ch;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={() => setChannel(cid)}
|
onClick={() => setChatChannel(cid)}
|
||||||
className={
|
className={
|
||||||
`chn${
|
`chn${
|
||||||
(cid === chatChannel) ? ' selected' : ''
|
(cid === chatChannel) ? ' selected' : ''
|
||||||
|
@ -226,29 +225,4 @@ const ChannelDropDown = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state: State) {
|
export default React.memo(ChannelDropDown);
|
||||||
const { channels } = state.chat;
|
|
||||||
const {
|
|
||||||
chatChannel,
|
|
||||||
unread,
|
|
||||||
mute,
|
|
||||||
} = state.chatRead;
|
|
||||||
const { chatNotify } = state.audio;
|
|
||||||
return {
|
|
||||||
channels,
|
|
||||||
chatChannel,
|
|
||||||
unread,
|
|
||||||
mute,
|
|
||||||
chatNotify,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
setChannel(channelId) {
|
|
||||||
dispatch(setChatChannel(channelId));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelDropDown);
|
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {
|
import React, {
|
||||||
useRef, useLayoutEffect, useState, useEffect,
|
useRef, useLayoutEffect, useState, useEffect, useCallback,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import useStayScrolled from 'react-stay-scrolled';
|
import useStayScrolled from 'react-stay-scrolled';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { t } from 'ttag';
|
import { t } from 'ttag';
|
||||||
|
|
||||||
import type { State } from '../reducers';
|
import type { State } from '../reducers';
|
||||||
|
@ -18,8 +18,8 @@ import {
|
||||||
showUserAreaModal,
|
showUserAreaModal,
|
||||||
showChatModal,
|
showChatModal,
|
||||||
setChatChannel,
|
setChatChannel,
|
||||||
fetchChatMessages,
|
|
||||||
setChatInputMessage,
|
setChatInputMessage,
|
||||||
|
fetchChatMessages,
|
||||||
showContextMenu,
|
showContextMenu,
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
import ProtocolClient from '../socket/ProtocolClient';
|
import ProtocolClient from '../socket/ProtocolClient';
|
||||||
|
@ -30,27 +30,29 @@ function escapeRegExp(string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Chat = ({
|
const Chat = ({
|
||||||
|
windowId,
|
||||||
showExpand,
|
showExpand,
|
||||||
channels,
|
|
||||||
messages,
|
|
||||||
chatChannel,
|
|
||||||
ownName,
|
|
||||||
open,
|
|
||||||
inputMessage,
|
|
||||||
setInputMessage,
|
|
||||||
setChannel,
|
|
||||||
fetchMessages,
|
|
||||||
fetching,
|
|
||||||
blocked,
|
|
||||||
triggerModal,
|
|
||||||
openChannelContextMenu,
|
|
||||||
}) => {
|
}) => {
|
||||||
const listRef = useRef();
|
const listRef = useRef();
|
||||||
const targetRef = useRef();
|
const targetRef = useRef();
|
||||||
|
|
||||||
const [nameRegExp, setNameRegExp] = useState(null);
|
const [nameRegExp, setNameRegExp] = useState(null);
|
||||||
const [blockedIds, setBlockedIds] = useState([]);
|
const [blockedIds, setBlockedIds] = useState([]);
|
||||||
const [btnSize, setBtnSize] = useState(20);
|
const [btnSize, setBtnSize] = useState(20);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const setChannel = useCallback((cid) => dispatch(
|
||||||
|
setChatChannel(windowId, cid),
|
||||||
|
), [dispatch]);
|
||||||
|
|
||||||
|
const ownName = useSelector((state) => state.user.name);
|
||||||
|
const isDarkMode = useSelector((state) => state.gui.style.indexOf('dark') !== -1);
|
||||||
|
const fetching = useSelector((state) => state.fetching.fetchingChat);
|
||||||
|
const { channels, messages, blocked } = useSelector((state) => state.chat);
|
||||||
|
|
||||||
|
const { chatChannel, inputMessage } = useSelector((state) => state.windows.args[windowId]);
|
||||||
|
|
||||||
const { stayScrolled } = useStayScrolled(listRef, {
|
const { stayScrolled } = useStayScrolled(listRef, {
|
||||||
initialScroll: Infinity,
|
initialScroll: Infinity,
|
||||||
inaccuracy: 10,
|
inaccuracy: 10,
|
||||||
|
@ -58,7 +60,7 @@ const Chat = ({
|
||||||
|
|
||||||
const channelMessages = messages[chatChannel] || [];
|
const channelMessages = messages[chatChannel] || [];
|
||||||
if (channels[chatChannel] && !messages[chatChannel] && !fetching) {
|
if (channels[chatChannel] && !messages[chatChannel] && !fetching) {
|
||||||
fetchMessages(chatChannel);
|
dispatch(fetchChatMessages(chatChannel));
|
||||||
}
|
}
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
@ -94,6 +96,7 @@ const Chat = ({
|
||||||
// send message via websocket
|
// send message via websocket
|
||||||
ProtocolClient.sendChatMessage(msg, chatChannel);
|
ProtocolClient.sendChatMessage(msg, chatChannel);
|
||||||
setInputMessage('');
|
setInputMessage('');
|
||||||
|
dispatch(setChatInputMessage(windowId, ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -102,13 +105,13 @@ const Chat = ({
|
||||||
* set channel to first available one
|
* set channel to first available one
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!channels[chatChannel]) {
|
if (!chatChannel || !channels[chatChannel]) {
|
||||||
const cids = Object.keys(channels);
|
const cids = Object.keys(channels);
|
||||||
if (cids.length) {
|
if (cids.length) {
|
||||||
setChannel(cids[0]);
|
setChannel(cids[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [chatChannel, channels]);
|
}, [channels]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -133,11 +136,12 @@ const Chat = ({
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
} = event;
|
} = event;
|
||||||
openChannelContextMenu(
|
dispatch(showContextMenu(
|
||||||
|
'CHANNEL',
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
chatChannel,
|
{ cid: chatChannel },
|
||||||
);
|
));
|
||||||
}}
|
}}
|
||||||
role="button"
|
role="button"
|
||||||
title={t`Channel settings`}
|
title={t`Channel settings`}
|
||||||
|
@ -147,7 +151,7 @@ const Chat = ({
|
||||||
{(showExpand)
|
{(showExpand)
|
||||||
&& (
|
&& (
|
||||||
<span
|
<span
|
||||||
onClick={triggerModal}
|
onClick={() => dispatch(showChatModal())}
|
||||||
role="button"
|
role="button"
|
||||||
title={t`maximize`}
|
title={t`maximize`}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
|
@ -168,6 +172,8 @@ const Chat = ({
|
||||||
msgArray={splitChatMessage(t`Start chatting here`, nameRegExp)}
|
msgArray={splitChatMessage(t`Start chatting here`, nameRegExp)}
|
||||||
country="xx"
|
country="xx"
|
||||||
uid={0}
|
uid={0}
|
||||||
|
dark={isDarkMode}
|
||||||
|
windowId={windowId}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -179,6 +185,8 @@ const Chat = ({
|
||||||
msgArray={splitChatMessage(message[1], nameRegExp)}
|
msgArray={splitChatMessage(message[1], nameRegExp)}
|
||||||
country={message[2]}
|
country={message[2]}
|
||||||
uid={message[3]}
|
uid={message[3]}
|
||||||
|
dark={isDarkMode}
|
||||||
|
windowId={windowId}
|
||||||
/>
|
/>
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
@ -192,9 +200,10 @@ const Chat = ({
|
||||||
<input
|
<input
|
||||||
style={{ flexGrow: 1, minWidth: 40 }}
|
style={{ flexGrow: 1, minWidth: 40 }}
|
||||||
value={inputMessage}
|
value={inputMessage}
|
||||||
onChange={(e) => setInputMessage(e.target.value)}
|
onChange={(e) => dispatch(
|
||||||
|
setChatInputMessage(windowId, e.target.value),
|
||||||
|
)}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
id="chatmsginput"
|
|
||||||
maxLength="200"
|
maxLength="200"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={t`Chat here`}
|
placeholder={t`Chat here`}
|
||||||
|
@ -206,13 +215,16 @@ const Chat = ({
|
||||||
>
|
>
|
||||||
‣
|
‣
|
||||||
</button>
|
</button>
|
||||||
<ChannelDropDown />
|
<ChannelDropDown
|
||||||
|
setChatChannel={setChannel}
|
||||||
|
chatChannel={chatChannel}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className="modallink"
|
className="modallink"
|
||||||
onClick={open}
|
onClick={() => dispatch(showUserAreaModal())}
|
||||||
style={{ textAlign: 'center', fontSize: 13 }}
|
style={{ textAlign: 'center', fontSize: 13 }}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
@ -224,52 +236,4 @@ const Chat = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state: State) {
|
export default Chat;
|
||||||
const { name } = state.user;
|
|
||||||
const { chatChannel } = state.chatRead;
|
|
||||||
const {
|
|
||||||
channels,
|
|
||||||
messages,
|
|
||||||
inputMessage,
|
|
||||||
blocked,
|
|
||||||
} = state.chat;
|
|
||||||
const {
|
|
||||||
fetchingChat: fetching,
|
|
||||||
} = state.fetching;
|
|
||||||
return {
|
|
||||||
channels,
|
|
||||||
messages,
|
|
||||||
fetching,
|
|
||||||
blocked,
|
|
||||||
inputMessage,
|
|
||||||
chatChannel,
|
|
||||||
ownName: name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
open() {
|
|
||||||
dispatch(showUserAreaModal());
|
|
||||||
},
|
|
||||||
triggerModal() {
|
|
||||||
dispatch(showChatModal(true));
|
|
||||||
},
|
|
||||||
setChannel(channelId) {
|
|
||||||
dispatch(setChatChannel(channelId));
|
|
||||||
},
|
|
||||||
fetchMessages(channelId) {
|
|
||||||
dispatch(fetchChatMessages(channelId));
|
|
||||||
},
|
|
||||||
setInputMessage(message) {
|
|
||||||
dispatch(setChatInputMessage(message));
|
|
||||||
},
|
|
||||||
openChannelContextMenu(xPos, yPos, cid) {
|
|
||||||
dispatch(showContextMenu('CHANNEL', xPos, yPos, {
|
|
||||||
cid,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Chat);
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { connect } from 'react-redux';
|
||||||
import { MdForum } from 'react-icons/md';
|
import { MdForum } from 'react-icons/md';
|
||||||
import { t } from 'ttag';
|
import { t } from 'ttag';
|
||||||
|
|
||||||
import { showChatModal } from '../actions';
|
import { showChatModal, openChatWindow } from '../actions';
|
||||||
|
|
||||||
|
|
||||||
const ChatButton = ({
|
const ChatButton = ({
|
||||||
|
@ -80,7 +80,8 @@ const ChatButton = ({
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
open() {
|
open() {
|
||||||
dispatch(showChatModal(false));
|
// dispatch(showChatModal(false));
|
||||||
|
dispatch(openChatWindow());
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import { showContextMenu } from '../actions';
|
import { showContextMenu } from '../actions';
|
||||||
import { colorFromText, setBrightness } from '../core/utils';
|
import { colorFromText, setBrightness } from '../core/utils';
|
||||||
|
@ -13,14 +13,16 @@ function ChatMessage({
|
||||||
name,
|
name,
|
||||||
uid,
|
uid,
|
||||||
country,
|
country,
|
||||||
|
dark,
|
||||||
|
windowId,
|
||||||
msgArray,
|
msgArray,
|
||||||
openUserContextMenu,
|
|
||||||
darkMode,
|
|
||||||
}) {
|
}) {
|
||||||
if (!name || !msgArray) {
|
if (!name || !msgArray) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const isInfo = (name === 'info');
|
const isInfo = (name === 'info');
|
||||||
const isEvent = (name === 'event');
|
const isEvent = (name === 'event');
|
||||||
let className = 'msg';
|
let className = 'msg';
|
||||||
|
@ -51,7 +53,7 @@ function ChatMessage({
|
||||||
<span
|
<span
|
||||||
className="chatname"
|
className="chatname"
|
||||||
style={{
|
style={{
|
||||||
color: setBrightness(colorFromText(name), darkMode),
|
color: setBrightness(colorFromText(name), dark),
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
role="button"
|
role="button"
|
||||||
|
@ -61,12 +63,11 @@ function ChatMessage({
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
} = event;
|
} = event;
|
||||||
openUserContextMenu(
|
dispatch(showContextMenu('USER', clientX, clientY, {
|
||||||
clientX,
|
windowId,
|
||||||
clientY,
|
|
||||||
uid,
|
uid,
|
||||||
name,
|
name,
|
||||||
);
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
|
@ -95,7 +96,7 @@ function ChatMessage({
|
||||||
<span
|
<span
|
||||||
className="ping"
|
className="ping"
|
||||||
style={{
|
style={{
|
||||||
color: setBrightness(colorFromText(txt.substr(1)), darkMode),
|
color: setBrightness(colorFromText(txt.substr(1)), dark),
|
||||||
}}
|
}}
|
||||||
>{txt}</span>
|
>{txt}</span>
|
||||||
);
|
);
|
||||||
|
@ -104,7 +105,7 @@ function ChatMessage({
|
||||||
<span
|
<span
|
||||||
className="mention"
|
className="mention"
|
||||||
style={{
|
style={{
|
||||||
color: setBrightness(colorFromText(txt.substr(1)), darkMode),
|
color: setBrightness(colorFromText(txt.substr(1)), dark),
|
||||||
}}
|
}}
|
||||||
>{txt}</span>
|
>{txt}</span>
|
||||||
);
|
);
|
||||||
|
@ -116,21 +117,4 @@ function ChatMessage({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state: State) {
|
export default React.memo(ChatMessage);
|
||||||
const { style } = state.gui;
|
|
||||||
const darkMode = style.indexOf('dark') !== -1;
|
|
||||||
return { darkMode };
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
openUserContextMenu(xPos, yPos, uid, name) {
|
|
||||||
dispatch(showContextMenu('USER', xPos, yPos, {
|
|
||||||
uid,
|
|
||||||
name,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatMessage);
|
|
||||||
|
|
|
@ -32,26 +32,24 @@ const UI = ({
|
||||||
menuOpen,
|
menuOpen,
|
||||||
menuType,
|
menuType,
|
||||||
}) => {
|
}) => {
|
||||||
|
const contextMenu = (menuOpen && menuType) ? CONTEXT_MENUS[menuType] : null;
|
||||||
|
|
||||||
if (isHistoricalView) {
|
if (isHistoricalView) {
|
||||||
return (
|
return [
|
||||||
<div>
|
<HistorySelect />,
|
||||||
<HistorySelect />
|
contextMenu,
|
||||||
{(menuOpen && menuType) ? CONTEXT_MENUS[menuType] : null}
|
];
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return (
|
return [
|
||||||
<div>
|
<Alert />,
|
||||||
<Alert />
|
<PalselButton />,
|
||||||
<PalselButton />
|
<Palette />,
|
||||||
<Palette />
|
(!is3D) && <GlobeButton />,
|
||||||
{(!is3D) && <GlobeButton />}
|
(is3D && isOnMobile) && <Mobile3DControls />,
|
||||||
{(is3D && isOnMobile) && <Mobile3DControls />}
|
<CoolDownBox />,
|
||||||
<CoolDownBox />
|
<NotifyBox />,
|
||||||
<NotifyBox />
|
contextMenu,
|
||||||
{(menuOpen && menuType) && CONTEXT_MENUS[menuType]}
|
];
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state: State) {
|
function mapStateToProps(state: State) {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import React, {
|
import React, {
|
||||||
useRef, useEffect,
|
useRef, useEffect,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { t } from 'ttag';
|
import { t } from 'ttag';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -16,23 +16,20 @@ import {
|
||||||
setUserBlock,
|
setUserBlock,
|
||||||
setChatChannel,
|
setChatChannel,
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
import type { State } from '../reducers';
|
|
||||||
|
|
||||||
const UserContextMenu = ({
|
const UserContextMenu = ({
|
||||||
xPos,
|
|
||||||
yPos,
|
|
||||||
uid,
|
|
||||||
name,
|
|
||||||
addToInput,
|
|
||||||
dm,
|
|
||||||
block,
|
|
||||||
channels,
|
|
||||||
fetching,
|
|
||||||
setChannel,
|
setChannel,
|
||||||
close,
|
|
||||||
}) => {
|
}) => {
|
||||||
const wrapperRef = useRef(null);
|
const wrapperRef = useRef(null);
|
||||||
|
|
||||||
|
const { xPos, yPos, args } = useSelector((state) => state.contextMenu);
|
||||||
|
const { windowId, name, uid } = args;
|
||||||
|
|
||||||
|
const channels = useSelector((state) => state.chat.channels);
|
||||||
|
const fetching = useSelector((state) => state.fetching.fetchingApi);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const close = () => dispatch(hideContextMenu());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event) => {
|
const handleClickOutside = (event) => {
|
||||||
|
@ -64,7 +61,7 @@ const UserContextMenu = ({
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
addToInput(`@${name} `);
|
dispatch(addToChatInputMessage(windowId, `@${name} `));
|
||||||
close();
|
close();
|
||||||
}}
|
}}
|
||||||
style={{ borderTop: 'none' }}
|
style={{ borderTop: 'none' }}
|
||||||
|
@ -83,13 +80,13 @@ const UserContextMenu = ({
|
||||||
for (let i = 0; i < cids.length; i += 1) {
|
for (let i = 0; i < cids.length; i += 1) {
|
||||||
const cid = cids[i];
|
const cid = cids[i];
|
||||||
if (channels[cid].length === 4 && channels[cid][3] === uid) {
|
if (channels[cid].length === 4 && channels[cid][3] === uid) {
|
||||||
setChannel(cid);
|
dispatch(setChatChannel(windowId, cid));
|
||||||
close();
|
close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!fetching) {
|
if (!fetching) {
|
||||||
dm(uid);
|
dispatch(startDm(windowId, { userId: uid }));
|
||||||
}
|
}
|
||||||
close();
|
close();
|
||||||
}}
|
}}
|
||||||
|
@ -98,7 +95,7 @@ const UserContextMenu = ({
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
block(uid, name);
|
dispatch(setUserBlock(uid, name, true));
|
||||||
close();
|
close();
|
||||||
}}
|
}}
|
||||||
role="button"
|
role="button"
|
||||||
|
@ -110,55 +107,4 @@ const UserContextMenu = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state: State) {
|
export default UserContextMenu;
|
||||||
const {
|
|
||||||
xPos,
|
|
||||||
yPos,
|
|
||||||
args,
|
|
||||||
} = state.contextMenu;
|
|
||||||
const {
|
|
||||||
channels,
|
|
||||||
} = state.chat;
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
uid,
|
|
||||||
} = args;
|
|
||||||
const {
|
|
||||||
fetchingApi: fetching,
|
|
||||||
} = state.fetching;
|
|
||||||
return {
|
|
||||||
xPos,
|
|
||||||
yPos,
|
|
||||||
channels,
|
|
||||||
name,
|
|
||||||
uid,
|
|
||||||
fetching,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
addToInput(text) {
|
|
||||||
dispatch(addToChatInputMessage(text));
|
|
||||||
const input = document.getElementById('chatmsginput');
|
|
||||||
if (input) {
|
|
||||||
input.focus();
|
|
||||||
input.select();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dm(userId) {
|
|
||||||
dispatch(startDm({ userId }));
|
|
||||||
},
|
|
||||||
block(userId, userName) {
|
|
||||||
dispatch(setUserBlock(userId, userName, true));
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
dispatch(hideContextMenu());
|
|
||||||
},
|
|
||||||
setChannel(channelId) {
|
|
||||||
dispatch(setChatChannel(channelId));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(UserContextMenu);
|
|
||||||
|
|
80
src/components/Window.jsx
Normal file
80
src/components/Window.jsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* draw window
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
import Chat from './Chat';
|
||||||
|
import {
|
||||||
|
moveWindow,
|
||||||
|
} from '../actions';
|
||||||
|
|
||||||
|
const selectWindowById = (state, windowId) => state.windows.windows.find((win) => win.windowId === windowId);
|
||||||
|
|
||||||
|
const WINDOW_COMPONENTS = {
|
||||||
|
NONE: <div />,
|
||||||
|
CHAT: Chat,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Window = ({ id }) => {
|
||||||
|
const win = useSelector((state) => selectWindowById(state, id));
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const startMove = useCallback((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
let {
|
||||||
|
clientX: startX,
|
||||||
|
clientY: startY,
|
||||||
|
} = event;
|
||||||
|
const move = (evt) => {
|
||||||
|
const {
|
||||||
|
clientX: curX,
|
||||||
|
clientY: curY,
|
||||||
|
} = evt;
|
||||||
|
dispatch(moveWindow(id, curX - startX, curY - startY));
|
||||||
|
startX = curX;
|
||||||
|
startY = curY;
|
||||||
|
};
|
||||||
|
document.addEventListener('mousemove', move);
|
||||||
|
const stopMove = () => {
|
||||||
|
document.removeEventListener('mousemove', move);
|
||||||
|
};
|
||||||
|
document.addEventListener('mouseup', stopMove, { once: true });
|
||||||
|
document.addEventListener('mouseleave', stopMove, { once: true });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const {
|
||||||
|
width, height,
|
||||||
|
xPos, yPos,
|
||||||
|
windowType,
|
||||||
|
title,
|
||||||
|
} = win;
|
||||||
|
|
||||||
|
const Content = WINDOW_COMPONENTS[windowType];
|
||||||
|
|
||||||
|
console.log(`render window ${id}`);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="window"
|
||||||
|
style={{
|
||||||
|
left: xPos,
|
||||||
|
top: yPos,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="topbar"
|
||||||
|
onMouseDown={startMove}
|
||||||
|
>Move Here</div>
|
||||||
|
<Content windowId={id} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(Window);
|
21
src/components/WindowsRoot.jsx
Normal file
21
src/components/WindowsRoot.jsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* draw windows
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useSelector, shallowEqual } from 'react-redux';
|
||||||
|
|
||||||
|
import Window from './Window';
|
||||||
|
|
||||||
|
const selectWindowIds = (state) => state.windows.windows.map((win) => win.windowId);
|
||||||
|
|
||||||
|
const WindowsRoot = () => {
|
||||||
|
const windowIds = useSelector(selectWindowIds, shallowEqual);
|
||||||
|
|
||||||
|
return windowIds.map((id) => (
|
||||||
|
<Window id={id} />
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WindowsRoot;
|
|
@ -5,7 +5,6 @@ import { MAX_CHAT_MESSAGES } from '../core/constants';
|
||||||
import type { Action } from '../actions/types';
|
import type { Action } from '../actions/types';
|
||||||
|
|
||||||
export type ChatState = {
|
export type ChatState = {
|
||||||
inputMessage: string,
|
|
||||||
/*
|
/*
|
||||||
* {
|
* {
|
||||||
* cid: [
|
* cid: [
|
||||||
|
@ -30,7 +29,6 @@ export type ChatState = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: ChatState = {
|
const initialState: ChatState = {
|
||||||
inputMessage: '',
|
|
||||||
channels: {},
|
channels: {},
|
||||||
blocked: [],
|
blocked: [],
|
||||||
messages: {},
|
messages: {},
|
||||||
|
@ -63,7 +61,6 @@ export default function chat(
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
inputMessage: '',
|
|
||||||
channels,
|
channels,
|
||||||
blocked: [],
|
blocked: [],
|
||||||
messages,
|
messages,
|
||||||
|
@ -138,30 +135,6 @@ export default function chat(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_CHAT_INPUT_MSG': {
|
|
||||||
const { message } = action;
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
inputMessage: message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'ADD_CHAT_INPUT_MSG': {
|
|
||||||
const { message } = action;
|
|
||||||
let { inputMessage } = state;
|
|
||||||
const lastChar = inputMessage.substr(-1);
|
|
||||||
const pad = (lastChar && lastChar !== ' ');
|
|
||||||
if (pad) {
|
|
||||||
inputMessage += ' ';
|
|
||||||
}
|
|
||||||
inputMessage += message;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
inputMessage,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'RECEIVE_CHAT_MESSAGE': {
|
case 'RECEIVE_CHAT_MESSAGE': {
|
||||||
const {
|
const {
|
||||||
name, text, country, channel, user,
|
name, text, country, channel, user,
|
||||||
|
|
|
@ -18,15 +18,17 @@ export type ChatReadState = {
|
||||||
// booleans if channel is unread
|
// booleans if channel is unread
|
||||||
// {cid: unread, ...}
|
// {cid: unread, ...}
|
||||||
unread: Object,
|
unread: Object,
|
||||||
// selected chat channel
|
// currently open chat channels can contain duplications
|
||||||
chatChannel: number,
|
// just used to keep track of what channels we are seeing in
|
||||||
|
// windows to decide if readTS gets changed,
|
||||||
|
chatChannels: Array,
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: ChatReadState = {
|
const initialState: ChatReadState = {
|
||||||
mute: [],
|
mute: [],
|
||||||
readTs: {},
|
readTs: {},
|
||||||
unread: {},
|
unread: {},
|
||||||
chatChannel: 1,
|
chatChannels: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,11 +59,14 @@ export default function chatRead(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_CHAT_CHANNEL': {
|
case 'OPEN_CHAT_CHANNEL': {
|
||||||
const { cid } = action;
|
const { cid } = action;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
chatChannel: cid,
|
chatChannels: [
|
||||||
|
...state.chatChannels,
|
||||||
|
cid,
|
||||||
|
],
|
||||||
readTs: {
|
readTs: {
|
||||||
...state.readTs,
|
...state.readTs,
|
||||||
[cid]: Date.now() + TIME_DIFF_THREASHOLD,
|
[cid]: Date.now() + TIME_DIFF_THREASHOLD,
|
||||||
|
@ -73,6 +78,19 @@ export default function chatRead(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'CLOSE_CHAT_CHANNEL': {
|
||||||
|
const { cid } = action;
|
||||||
|
const chatChannels = [...state.chatChannels];
|
||||||
|
const pos = chatChannels.indexOf(cid);
|
||||||
|
if (pos !== -1) {
|
||||||
|
chatChannels.splice(pos, 1);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
chatChannels,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 'ADD_CHAT_CHANNEL': {
|
case 'ADD_CHAT_CHANNEL': {
|
||||||
const [cid] = Object.keys(action.channel);
|
const [cid] = Object.keys(action.channel);
|
||||||
return {
|
return {
|
||||||
|
@ -106,16 +124,14 @@ export default function chatRead(
|
||||||
|
|
||||||
case 'RECEIVE_CHAT_MESSAGE': {
|
case 'RECEIVE_CHAT_MESSAGE': {
|
||||||
const { channel: cid } = action;
|
const { channel: cid } = action;
|
||||||
const { chatChannel } = state;
|
const { chatChannels } = state;
|
||||||
// eslint-disable-next-line eqeqeq
|
const readTs = chatChannels.includes(cid)
|
||||||
const readTs = (chatChannel == cid)
|
|
||||||
? {
|
? {
|
||||||
...state.readTs,
|
...state.readTs,
|
||||||
// 15s treshold for desync
|
// 15s treshold for desync
|
||||||
[cid]: Date.now() + TIME_DIFF_THREASHOLD,
|
[cid]: Date.now() + TIME_DIFF_THREASHOLD,
|
||||||
} : state.readTs;
|
} : state.readTs;
|
||||||
// eslint-disable-next-line eqeqeq
|
const unread = chatChannels.includes(cid)
|
||||||
const unread = (chatChannel != cid)
|
|
||||||
? {
|
? {
|
||||||
...state.unread,
|
...state.unread,
|
||||||
[cid]: true,
|
[cid]: true,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import audio from './audio';
|
||||||
import canvas from './canvas';
|
import canvas from './canvas';
|
||||||
import gui from './gui';
|
import gui from './gui';
|
||||||
import modal from './modal';
|
import modal from './modal';
|
||||||
|
import windows from './windows';
|
||||||
import user from './user';
|
import user from './user';
|
||||||
import ranks from './ranks';
|
import ranks from './ranks';
|
||||||
import alert from './alert';
|
import alert from './alert';
|
||||||
|
@ -59,6 +60,7 @@ export default persistCombineReducers(config, {
|
||||||
canvas,
|
canvas,
|
||||||
gui,
|
gui,
|
||||||
modal,
|
modal,
|
||||||
|
windows,
|
||||||
user,
|
user,
|
||||||
ranks,
|
ranks,
|
||||||
alert,
|
alert,
|
||||||
|
|
174
src/reducers/windows.js
Normal file
174
src/reducers/windows.js
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
* state for open windows and modal and its content
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Action } from '../actions/types';
|
||||||
|
|
||||||
|
export type WindowsState = {
|
||||||
|
// [
|
||||||
|
// {
|
||||||
|
// windowId: number,
|
||||||
|
// windowType: string,
|
||||||
|
// title: string,
|
||||||
|
// width: number,
|
||||||
|
// height: number,
|
||||||
|
// xPos: percentage,
|
||||||
|
// yPos: percentage,
|
||||||
|
// },
|
||||||
|
// ]
|
||||||
|
windows: Array,
|
||||||
|
// {
|
||||||
|
// windowId: {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
args: Object,
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: WindowsState = {
|
||||||
|
windows: [],
|
||||||
|
args: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function windows(
|
||||||
|
state: WindowsState = initialState,
|
||||||
|
action: Action,
|
||||||
|
): WindowsState {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'OPEN_WINDOW': {
|
||||||
|
const {
|
||||||
|
windowType,
|
||||||
|
title,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
xPos,
|
||||||
|
yPos,
|
||||||
|
args,
|
||||||
|
} = action;
|
||||||
|
let windowId = Math.floor(Math.random() * 99999) + 1;
|
||||||
|
while (state.args[windowId]) {
|
||||||
|
windowId += 1;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
windows: [
|
||||||
|
...state.windows,
|
||||||
|
{
|
||||||
|
windowId,
|
||||||
|
windowType,
|
||||||
|
title,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
xPos,
|
||||||
|
yPos,
|
||||||
|
args,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
args: {
|
||||||
|
...state.args,
|
||||||
|
[windowId]: args,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'CLOSE_WINDOW': {
|
||||||
|
const {
|
||||||
|
windowId,
|
||||||
|
} = action;
|
||||||
|
const args = { ...state.args };
|
||||||
|
delete args[windowId];
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
windows: state.windows.filter((win) => win.windowId !== windowId),
|
||||||
|
args,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'MOVE_WINDOW': {
|
||||||
|
const {
|
||||||
|
windowId,
|
||||||
|
xDiff,
|
||||||
|
yDiff,
|
||||||
|
} = action;
|
||||||
|
const newWindows = state.windows.map((win) => {
|
||||||
|
if (win.windowId !== windowId) return win;
|
||||||
|
return {
|
||||||
|
...win,
|
||||||
|
xPos: win.xPos + xDiff,
|
||||||
|
yPos: win.yPos + yDiff,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
windows: newWindows,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'CLOSE_ALL_WINDOWS': {
|
||||||
|
return initialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* args specific actions
|
||||||
|
*/
|
||||||
|
case 'ADD_CHAT_INPUT_MSG': {
|
||||||
|
const {
|
||||||
|
windowId,
|
||||||
|
msg,
|
||||||
|
} = action;
|
||||||
|
let { inputMessage } = state.args[windowId];
|
||||||
|
const lastChar = inputMessage.substr(-1);
|
||||||
|
const pad = (lastChar && lastChar !== ' ') ? ' ' : '';
|
||||||
|
inputMessage += pad + msg;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
args: {
|
||||||
|
...state.args,
|
||||||
|
[windowId]: {
|
||||||
|
...state.args[windowId],
|
||||||
|
inputMessage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SET_CHAT_CHANNEL': {
|
||||||
|
const {
|
||||||
|
windowId,
|
||||||
|
cid,
|
||||||
|
} = action;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
args: {
|
||||||
|
...state.args,
|
||||||
|
[windowId]: {
|
||||||
|
...state.args[windowId],
|
||||||
|
chatChannel: cid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SET_CHAT_INPUT_MSG': {
|
||||||
|
const {
|
||||||
|
windowId,
|
||||||
|
msg,
|
||||||
|
} = action;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
args: {
|
||||||
|
...state.args,
|
||||||
|
[windowId]: {
|
||||||
|
...state.args[windowId],
|
||||||
|
inputMessage: msg,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -134,6 +134,18 @@ tr:nth-child(even) {
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.window {
|
||||||
|
position: fixed;
|
||||||
|
background-color: rgba(226, 226, 226, 0.92);
|
||||||
|
border: solid black;
|
||||||
|
border-width: thin;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
.contextmenu {
|
.contextmenu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user