add popu-up menu
This commit is contained in:
parent
2161fe11a2
commit
ad79ac1746
|
@ -199,6 +199,7 @@ export function receiveChatMessage(
|
||||||
text: string,
|
text: string,
|
||||||
country: string,
|
country: string,
|
||||||
channel: number,
|
channel: number,
|
||||||
|
user: number,
|
||||||
isPing: boolean,
|
isPing: boolean,
|
||||||
): Action {
|
): Action {
|
||||||
return {
|
return {
|
||||||
|
@ -207,6 +208,7 @@ export function receiveChatMessage(
|
||||||
text,
|
text,
|
||||||
country,
|
country,
|
||||||
channel,
|
channel,
|
||||||
|
user,
|
||||||
isPing,
|
isPing,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -705,6 +707,35 @@ export function showCanvasSelectionModal(): Action {
|
||||||
return showModal('CANVAS_SELECTION');
|
return showModal('CANVAS_SELECTION');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showContextMenu(
|
||||||
|
menuType: string,
|
||||||
|
xPos: number,
|
||||||
|
yPos: number,
|
||||||
|
args: Object,
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: 'SHOW_CONTEXT_MENU',
|
||||||
|
menuType,
|
||||||
|
xPos,
|
||||||
|
yPos,
|
||||||
|
args,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
@ -723,6 +754,12 @@ export function hideModal(): Action {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hideContextMenu(): Action {
|
||||||
|
return {
|
||||||
|
type: 'HIDE_CONTEXT_MENU',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function reloadUrl(): Action {
|
export function reloadUrl(): Action {
|
||||||
return {
|
return {
|
||||||
type: 'RELOAD_URL',
|
type: 'RELOAD_URL',
|
||||||
|
|
|
@ -65,11 +65,14 @@ export type Action =
|
||||||
text: string,
|
text: string,
|
||||||
country: string,
|
country: string,
|
||||||
channel: number,
|
channel: number,
|
||||||
|
user: number,
|
||||||
isPing: boolean,
|
isPing: boolean,
|
||||||
}
|
}
|
||||||
| { type: 'RECEIVE_CHAT_HISTORY', cid: number, history: Array }
|
| { type: 'RECEIVE_CHAT_HISTORY', cid: number, history: Array }
|
||||||
| { type: 'SET_CHAT_CHANNEL', channelId: number }
|
| { type: 'SET_CHAT_CHANNEL', channelId: number }
|
||||||
| { type: 'SET_CHAT_FETCHING', fetching: boolean }
|
| { type: 'SET_CHAT_FETCHING', fetching: boolean }
|
||||||
|
| { type: 'SET_CHAT_INPUT_MSG', message: string }
|
||||||
|
| { type: 'ADD_CHAT_INPUT_MSG', message: string }
|
||||||
| { type: 'RECEIVE_ME',
|
| { type: 'RECEIVE_ME',
|
||||||
name: string,
|
name: string,
|
||||||
waitSeconds: number,
|
waitSeconds: number,
|
||||||
|
@ -90,7 +93,14 @@ export type Action =
|
||||||
| { type: 'SET_MAILREG', mailreg: boolean }
|
| { type: 'SET_MAILREG', mailreg: boolean }
|
||||||
| { type: 'REM_FROM_MESSAGES', message: string }
|
| { type: 'REM_FROM_MESSAGES', message: string }
|
||||||
| { type: 'SHOW_MODAL', modalType: string }
|
| { type: 'SHOW_MODAL', modalType: string }
|
||||||
|
| { type: 'SHOW_CONTEXT_MENU',
|
||||||
|
menuType: string,
|
||||||
|
xPos: number,
|
||||||
|
yPos: number,
|
||||||
|
args: Object,
|
||||||
|
}
|
||||||
| { type: 'HIDE_MODAL' }
|
| { type: 'HIDE_MODAL' }
|
||||||
|
| { type: 'HIDE_CONTEXT_MENU' }
|
||||||
| { type: 'RELOAD_URL' }
|
| { type: 'RELOAD_URL' }
|
||||||
| { type: 'SET_HISTORICAL_TIME', date: string, time: string }
|
| { type: 'SET_HISTORICAL_TIME', date: string, time: string }
|
||||||
| { type: 'ON_VIEW_FINISH_CHANGE' };
|
| { type: 'ON_VIEW_FINISH_CHANGE' };
|
||||||
|
|
|
@ -52,9 +52,22 @@ function init() {
|
||||||
ProtocolClient.on('setWsName', (name) => {
|
ProtocolClient.on('setWsName', (name) => {
|
||||||
nameRegExp = new RegExp(`(^|\\s+)(@${name})(\\s+|$)`, 'g');
|
nameRegExp = new RegExp(`(^|\\s+)(@${name})(\\s+|$)`, 'g');
|
||||||
});
|
});
|
||||||
ProtocolClient.on('chatMessage', (name, text, country, channelId) => {
|
ProtocolClient.on('chatMessage', (
|
||||||
|
name,
|
||||||
|
text,
|
||||||
|
country,
|
||||||
|
channelId,
|
||||||
|
userId,
|
||||||
|
) => {
|
||||||
const isPing = (nameRegExp && text.match(nameRegExp));
|
const isPing = (nameRegExp && text.match(nameRegExp));
|
||||||
store.dispatch(receiveChatMessage(name, text, country, channelId, isPing));
|
store.dispatch(receiveChatMessage(
|
||||||
|
name,
|
||||||
|
text,
|
||||||
|
country,
|
||||||
|
channelId,
|
||||||
|
userId,
|
||||||
|
isPing,
|
||||||
|
));
|
||||||
});
|
});
|
||||||
ProtocolClient.on('changedMe', () => {
|
ProtocolClient.on('changedMe', () => {
|
||||||
store.dispatch(fetchMe());
|
store.dispatch(fetchMe());
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
showUserAreaModal,
|
showUserAreaModal,
|
||||||
setChatChannel,
|
setChatChannel,
|
||||||
fetchChatMessages,
|
fetchChatMessages,
|
||||||
|
setChatInputMessage,
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
import ProtocolClient from '../socket/ProtocolClient';
|
import ProtocolClient from '../socket/ProtocolClient';
|
||||||
import { saveSelection, restoreSelection } from '../utils/storeSelection';
|
import { saveSelection, restoreSelection } from '../utils/storeSelection';
|
||||||
|
@ -32,13 +33,13 @@ const Chat = ({
|
||||||
chatChannel,
|
chatChannel,
|
||||||
ownName,
|
ownName,
|
||||||
open,
|
open,
|
||||||
|
inputMessage,
|
||||||
|
setInputMessage,
|
||||||
setChannel,
|
setChannel,
|
||||||
fetchMessages,
|
fetchMessages,
|
||||||
fetching,
|
fetching,
|
||||||
}) => {
|
}) => {
|
||||||
const listRef = useRef();
|
const listRef = useRef();
|
||||||
const inputRef = useRef();
|
|
||||||
const [inputMessage, setInputMessage] = useState('');
|
|
||||||
const [selection, setSelection] = useState(null);
|
const [selection, setSelection] = useState(null);
|
||||||
const [nameRegExp, setNameRegExp] = useState(null);
|
const [nameRegExp, setNameRegExp] = useState(null);
|
||||||
|
|
||||||
|
@ -56,13 +57,15 @@ const Chat = ({
|
||||||
stayScrolled();
|
stayScrolled();
|
||||||
}, [channelMessages.length]);
|
}, [channelMessages.length]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO this removes focus from chat box, fix this
|
||||||
|
*
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO this removes focus from chat box, fix this
|
|
||||||
return;
|
|
||||||
if (channelMessages.length === MAX_CHAT_MESSAGES) {
|
if (channelMessages.length === MAX_CHAT_MESSAGES) {
|
||||||
restoreSelection(selection);
|
restoreSelection(selection);
|
||||||
}
|
}
|
||||||
}, [channelMessages]);
|
}, [channelMessages]);
|
||||||
|
*/
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const regExp = (ownName)
|
const regExp = (ownName)
|
||||||
|
@ -71,16 +74,6 @@ const Chat = ({
|
||||||
setNameRegExp(regExp);
|
setNameRegExp(regExp);
|
||||||
}, [ownName]);
|
}, [ownName]);
|
||||||
|
|
||||||
function padToInputMessage(txt) {
|
|
||||||
const lastChar = inputMessage.substr(-1);
|
|
||||||
const pad = (lastChar && lastChar !== ' ');
|
|
||||||
let newMsg = inputMessage;
|
|
||||||
if (pad) newMsg += ' ';
|
|
||||||
newMsg += txt;
|
|
||||||
setInputMessage(newMsg);
|
|
||||||
inputRef.current.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSubmit(e) {
|
function handleSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const msg = inputMessage.trim();
|
const msg = inputMessage.trim();
|
||||||
|
@ -123,7 +116,7 @@ const Chat = ({
|
||||||
name="info"
|
name="info"
|
||||||
msgArray={splitChatMessage('Start chatting here', nameRegExp)}
|
msgArray={splitChatMessage('Start chatting here', nameRegExp)}
|
||||||
country="xx"
|
country="xx"
|
||||||
insertText={(txt) => padToInputMessage(txt)}
|
uid={0}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -133,7 +126,7 @@ const Chat = ({
|
||||||
name={message[0]}
|
name={message[0]}
|
||||||
msgArray={splitChatMessage(message[1], nameRegExp)}
|
msgArray={splitChatMessage(message[1], nameRegExp)}
|
||||||
country={message[2]}
|
country={message[2]}
|
||||||
insertText={(txt) => padToInputMessage(txt)}
|
uid={message[3]}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -148,7 +141,7 @@ const Chat = ({
|
||||||
style={{ flexGrow: 1, minWidth: 40 }}
|
style={{ flexGrow: 1, minWidth: 40 }}
|
||||||
value={inputMessage}
|
value={inputMessage}
|
||||||
onChange={(e) => setInputMessage(e.target.value)}
|
onChange={(e) => setInputMessage(e.target.value)}
|
||||||
ref={inputRef}
|
id="chatmsginput"
|
||||||
maxLength="200"
|
maxLength="200"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Chat here"
|
placeholder="Chat here"
|
||||||
|
@ -198,11 +191,17 @@ const Chat = ({
|
||||||
function mapStateToProps(state: State) {
|
function mapStateToProps(state: State) {
|
||||||
const { name } = state.user;
|
const { name } = state.user;
|
||||||
const { chatChannel } = state.gui;
|
const { chatChannel } = state.gui;
|
||||||
const { channels, messages, fetching } = state.chat;
|
const {
|
||||||
|
channels,
|
||||||
|
messages,
|
||||||
|
fetching,
|
||||||
|
inputMessage,
|
||||||
|
} = state.chat;
|
||||||
return {
|
return {
|
||||||
channels,
|
channels,
|
||||||
messages,
|
messages,
|
||||||
fetching,
|
fetching,
|
||||||
|
inputMessage,
|
||||||
chatChannel,
|
chatChannel,
|
||||||
ownName: name,
|
ownName: name,
|
||||||
};
|
};
|
||||||
|
@ -219,6 +218,9 @@ function mapDispatchToProps(dispatch) {
|
||||||
fetchMessages(channelId) {
|
fetchMessages(channelId) {
|
||||||
dispatch(fetchChatMessages(channelId));
|
dispatch(fetchChatMessages(channelId));
|
||||||
},
|
},
|
||||||
|
setInputMessage(message) {
|
||||||
|
dispatch(setChatInputMessage(message));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { showContextMenu } from '../actions';
|
||||||
import { colorFromText, setBrightness } from '../core/utils';
|
import { colorFromText, setBrightness } from '../core/utils';
|
||||||
|
|
||||||
|
|
||||||
function ChatMessage({
|
function ChatMessage({
|
||||||
name,
|
name,
|
||||||
msgArray,
|
uid,
|
||||||
country,
|
country,
|
||||||
insertText,
|
msgArray,
|
||||||
|
openUserContextMenu,
|
||||||
darkMode,
|
darkMode,
|
||||||
}) {
|
}) {
|
||||||
if (!name || !msgArray) {
|
if (!name || !msgArray) {
|
||||||
|
@ -54,8 +56,17 @@ function ChatMessage({
|
||||||
}}
|
}}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onClick={() => {
|
onClick={(event) => {
|
||||||
insertText(`@${name} `);
|
const {
|
||||||
|
clientX,
|
||||||
|
clientY,
|
||||||
|
} = event;
|
||||||
|
openUserContextMenu(
|
||||||
|
clientX,
|
||||||
|
clientY,
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
|
@ -103,4 +114,15 @@ function mapStateToProps(state: State) {
|
||||||
return { darkMode };
|
return { darkMode };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps)(ChatMessage);
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
openUserContextMenu(xPos, yPos, uid, name) {
|
||||||
|
dispatch(showContextMenu('USER', xPos, yPos, {
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatMessage);
|
||||||
|
|
|
@ -14,11 +14,28 @@ import PalselButton from './PalselButton';
|
||||||
import Palette from './Palette';
|
import Palette from './Palette';
|
||||||
import HistorySelect from './HistorySelect';
|
import HistorySelect from './HistorySelect';
|
||||||
import Mobile3DControls from './Mobile3DControls';
|
import Mobile3DControls from './Mobile3DControls';
|
||||||
|
import UserContextMenu from './UserContextMenu';
|
||||||
|
|
||||||
|
|
||||||
const UI = ({ isHistoricalView, is3D, isOnMobile }) => {
|
const CONTEXT_MENUS = {
|
||||||
|
USER: <UserContextMenu />,
|
||||||
|
/* other context menus */
|
||||||
|
};
|
||||||
|
|
||||||
|
const UI = ({
|
||||||
|
isHistoricalView,
|
||||||
|
is3D,
|
||||||
|
isOnMobile,
|
||||||
|
menuOpen,
|
||||||
|
menuType,
|
||||||
|
}) => {
|
||||||
if (isHistoricalView) {
|
if (isHistoricalView) {
|
||||||
return <HistorySelect />;
|
return (
|
||||||
|
<div>
|
||||||
|
<HistorySelect />
|
||||||
|
{(menuOpen && menuType) ? CONTEXT_MENUS[menuType] : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -28,6 +45,7 @@ const UI = ({ isHistoricalView, is3D, isOnMobile }) => {
|
||||||
{(is3D && isOnMobile) ? <Mobile3DControls /> : null}
|
{(is3D && isOnMobile) ? <Mobile3DControls /> : null}
|
||||||
<CoolDownBox />
|
<CoolDownBox />
|
||||||
<NotifyBox />
|
<NotifyBox />
|
||||||
|
{(menuOpen && menuType) ? CONTEXT_MENUS[menuType] : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -40,10 +58,16 @@ function mapStateToProps(state: State) {
|
||||||
const {
|
const {
|
||||||
isOnMobile,
|
isOnMobile,
|
||||||
} = state.user;
|
} = state.user;
|
||||||
|
const {
|
||||||
|
menuOpen,
|
||||||
|
menuType,
|
||||||
|
} = state.contextMenu;
|
||||||
return {
|
return {
|
||||||
isHistoricalView,
|
isHistoricalView,
|
||||||
is3D,
|
is3D,
|
||||||
isOnMobile,
|
isOnMobile,
|
||||||
|
menuOpen,
|
||||||
|
menuType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
120
src/components/UserContextMenu.jsx
Normal file
120
src/components/UserContextMenu.jsx
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, {
|
||||||
|
useRef, useEffect,
|
||||||
|
} from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
hideContextMenu,
|
||||||
|
addToChatInputMessage,
|
||||||
|
setChatInputMessage,
|
||||||
|
} from '../actions';
|
||||||
|
import type { State } from '../reducers';
|
||||||
|
|
||||||
|
|
||||||
|
const UserContextMenu = ({
|
||||||
|
xPos,
|
||||||
|
yPos,
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
setInput,
|
||||||
|
addToInput,
|
||||||
|
close,
|
||||||
|
}) => {
|
||||||
|
const wrapperRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleClickOutside(event) {
|
||||||
|
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
document.addEventListener('touchstart', handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
document.addEventListener('touchstart', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [wrapperRef]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={wrapperRef}
|
||||||
|
className="contextmenu"
|
||||||
|
style={{
|
||||||
|
left: xPos,
|
||||||
|
top: yPos,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{ borderBottom: 'thin solid' }}
|
||||||
|
>
|
||||||
|
Block
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => {
|
||||||
|
setInput('loool');
|
||||||
|
}}
|
||||||
|
style={{ borderBottom: 'thin solid' }}
|
||||||
|
>
|
||||||
|
DM
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => {
|
||||||
|
addToInput(`@${name} `);
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Ping
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state: State) {
|
||||||
|
const {
|
||||||
|
xPos,
|
||||||
|
yPos,
|
||||||
|
args,
|
||||||
|
} = state.contextMenu;
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
uid,
|
||||||
|
} = args;
|
||||||
|
return {
|
||||||
|
xPos,
|
||||||
|
yPos,
|
||||||
|
name,
|
||||||
|
uid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
addToInput(text) {
|
||||||
|
dispatch(addToChatInputMessage(text));
|
||||||
|
const input = document.getElementById('chatmsginput');
|
||||||
|
if (input) {
|
||||||
|
input.focus();
|
||||||
|
input.select();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setInput(text) {
|
||||||
|
dispatch(setChatInputMessage(text));
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
dispatch(hideContextMenu());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(UserContextMenu);
|
|
@ -68,6 +68,7 @@ class ChatMessageBuffer {
|
||||||
name,
|
name,
|
||||||
message,
|
message,
|
||||||
flag,
|
flag,
|
||||||
|
uid,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,6 +81,7 @@ class ChatMessageBuffer {
|
||||||
as: 'user',
|
as: 'user',
|
||||||
foreignKey: 'uid',
|
foreignKey: 'uid',
|
||||||
attributes: [
|
attributes: [
|
||||||
|
'id',
|
||||||
'name',
|
'name',
|
||||||
'flag',
|
'flag',
|
||||||
],
|
],
|
||||||
|
@ -101,11 +103,13 @@ class ChatMessageBuffer {
|
||||||
message,
|
message,
|
||||||
'user.name': name,
|
'user.name': name,
|
||||||
'user.flag': flag,
|
'user.flag': flag,
|
||||||
|
'user.id': uid,
|
||||||
} = messagesModel[i];
|
} = messagesModel[i];
|
||||||
messages.push([
|
messages.push([
|
||||||
name,
|
name,
|
||||||
message,
|
message,
|
||||||
flag,
|
flag,
|
||||||
|
uid,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
return messages;
|
return messages;
|
||||||
|
|
|
@ -5,6 +5,7 @@ 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, name], [cid2, name2],...]
|
// [[cid, name], [cid2, name2],...]
|
||||||
channels: Array,
|
channels: Array,
|
||||||
// { cid: [message1,message2,message2,...]}
|
// { cid: [message1,message2,message2,...]}
|
||||||
|
@ -14,6 +15,7 @@ export type ChatState = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: ChatState = {
|
const initialState: ChatState = {
|
||||||
|
inputMessage: '',
|
||||||
channels: [],
|
channels: [],
|
||||||
messages: {},
|
messages: {},
|
||||||
fetching: false,
|
fetching: false,
|
||||||
|
@ -39,9 +41,33 @@ 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,
|
name, text, country, channel, user,
|
||||||
} = action;
|
} = action;
|
||||||
if (!state.messages[channel]) {
|
if (!state.messages[channel]) {
|
||||||
return state;
|
return state;
|
||||||
|
@ -50,7 +76,7 @@ export default function chat(
|
||||||
...state.messages,
|
...state.messages,
|
||||||
[channel]: [
|
[channel]: [
|
||||||
...state.messages[channel],
|
...state.messages[channel],
|
||||||
[name, text, country],
|
[name, text, country, user],
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
if (messages[channel].length > MAX_CHAT_MESSAGES) {
|
if (messages[channel].length > MAX_CHAT_MESSAGES) {
|
||||||
|
|
57
src/reducers/contextMenu.js
Normal file
57
src/reducers/contextMenu.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/**
|
||||||
|
* https://stackoverflow.com/questions/35623656/how-can-i-display-a-modal-dialog-in-redux-that-performs-asynchronous-actions/35641680#35641680
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Action } from '../actions/types';
|
||||||
|
|
||||||
|
export type ContextMenuState = {
|
||||||
|
menuOpen: boolean,
|
||||||
|
menuType: ?string,
|
||||||
|
xPos: number,
|
||||||
|
yPos: number,
|
||||||
|
args: Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialState: ContextMenuState = {
|
||||||
|
menuOpen: false,
|
||||||
|
menuType: null,
|
||||||
|
xPos: 0,
|
||||||
|
yPos: 0,
|
||||||
|
args: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default function contextMenu(
|
||||||
|
state: ModalState = initialState,
|
||||||
|
action: Action,
|
||||||
|
): ContextMenuState {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'SHOW_CONTEXT_MENU': {
|
||||||
|
const {
|
||||||
|
menuType, xPos, yPos, args,
|
||||||
|
} = action;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
menuType,
|
||||||
|
xPos,
|
||||||
|
yPos,
|
||||||
|
menuOpen: true,
|
||||||
|
args: {
|
||||||
|
...args,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'HIDE_CONTEXT_MENU': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
menuOpen: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import gui from './gui';
|
||||||
import modal from './modal';
|
import modal from './modal';
|
||||||
import user from './user';
|
import user from './user';
|
||||||
import chat from './chat';
|
import chat from './chat';
|
||||||
|
import contextMenu from './contextMenu';
|
||||||
|
|
||||||
import type { AudioState } from './audio';
|
import type { AudioState } from './audio';
|
||||||
import type { CanvasState } from './canvas';
|
import type { CanvasState } from './canvas';
|
||||||
|
@ -15,6 +16,7 @@ import type { GUIState } from './gui';
|
||||||
import type { ModalState } from './modal';
|
import type { ModalState } from './modal';
|
||||||
import type { UserState } from './user';
|
import type { UserState } from './user';
|
||||||
import type { ChatState } from './chat';
|
import type { ChatState } from './chat';
|
||||||
|
import type { ContextMenuState } from './contextMenu';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
audio: AudioState,
|
audio: AudioState,
|
||||||
|
@ -23,12 +25,19 @@ export type State = {
|
||||||
modal: ModalState,
|
modal: ModalState,
|
||||||
user: UserState,
|
user: UserState,
|
||||||
chat: ChatState,
|
chat: ChatState,
|
||||||
|
contextMenu: ContextMenuState,
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
key: 'primary',
|
key: 'primary',
|
||||||
storage: localForage,
|
storage: localForage,
|
||||||
blacklist: ['user', 'canvas', 'modal', 'chat'],
|
blacklist: [
|
||||||
|
'user',
|
||||||
|
'canvas',
|
||||||
|
'modal',
|
||||||
|
'chat',
|
||||||
|
'contextMenu',
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default persistCombineReducers(config, {
|
export default persistCombineReducers(config, {
|
||||||
|
@ -38,4 +47,5 @@ export default persistCombineReducers(config, {
|
||||||
modal,
|
modal,
|
||||||
user,
|
user,
|
||||||
chat,
|
chat,
|
||||||
|
contextMenu,
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,8 +24,6 @@ export default function modal(
|
||||||
action: Action,
|
action: Action,
|
||||||
): ModalState {
|
): ModalState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
// clear hover when placing a pixel
|
|
||||||
// fixes a bug with iPad
|
|
||||||
case 'SHOW_MODAL': {
|
case 'SHOW_MODAL': {
|
||||||
const { modalType } = action;
|
const { modalType } = action;
|
||||||
const chatOpen = (modalType === 'CHAT') ? false : state.chatOpen;
|
const chatOpen = (modalType === 'CHAT') ? false : state.chatOpen;
|
||||||
|
|
|
@ -169,10 +169,10 @@ class ProtocolClient extends EventEmitter {
|
||||||
const data = JSON.parse(message);
|
const data = JSON.parse(message);
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
if (data.length === 4) {
|
if (data.length === 5) {
|
||||||
// Ordinary array: Chat message
|
// Ordinary array: Chat message
|
||||||
const [name, text, country, channelId] = data;
|
const [name, text, country, channelId, userId] = data;
|
||||||
this.emit('chatMessage', name, text, country, channelId);
|
this.emit('chatMessage', name, text, country, channelId, userId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// string = name
|
// string = name
|
||||||
|
|
|
@ -169,7 +169,7 @@ class SocketServer extends WebSocketEvents {
|
||||||
id: number,
|
id: number,
|
||||||
country: string,
|
country: string,
|
||||||
) {
|
) {
|
||||||
const text = JSON.stringify([name, message, country, channelId]);
|
const text = JSON.stringify([name, message, country, channelId, id]);
|
||||||
this.wss.clients.forEach((ws) => {
|
this.wss.clients.forEach((ws) => {
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
if (chatProvider.userHasChannelAccess(ws.user, channelId)) {
|
if (chatProvider.userHasChannelAccess(ws.user, channelId)) {
|
||||||
|
|
|
@ -124,6 +124,26 @@ tr:nth-child(even) {
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contextmenu {
|
||||||
|
position: absolute;
|
||||||
|
width: 90px;
|
||||||
|
font-size: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: rgba(226, 226, 226);
|
||||||
|
border: solid black;
|
||||||
|
border-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu > div {
|
||||||
|
border-width: thin;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu > div:hover {
|
||||||
|
background-color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.chatbox.show {
|
.chatbox.show {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
width: 350px;
|
width: 350px;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user