add blocking of Users
This commit is contained in:
parent
5318b521e7
commit
26ed411129
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Collect api fetch commands for actions here
|
||||
* (chunk and tiles requests in ui/ChunkLoader*.js)
|
||||
* (user settings requests in their components)
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Adds customizeable timeout to fetch
|
||||
* defaults to 8s
|
||||
*/
|
||||
async function fetchWithTimeout(resource, options) {
|
||||
const { timeout = 8000 } = options;
|
||||
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
const response = await fetch(resource, {
|
||||
...options,
|
||||
signal: controller.signal,
|
||||
});
|
||||
clearTimeout(id);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/*
|
||||
* block / unblock user
|
||||
* userId id of user to block
|
||||
* block true if block, false if unblock
|
||||
* return error string or null if successful
|
||||
*/
|
||||
export async function requestBlock(userId: number, block: boolean) {
|
||||
const response = await fetchWithTimeout('api/block', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userId,
|
||||
block,
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await response.json();
|
||||
if (res.errors) {
|
||||
return res.errors[0];
|
||||
}
|
||||
if (response.ok && res.status === 'ok') {
|
||||
return null;
|
||||
}
|
||||
return 'Unknown Error';
|
||||
} catch {
|
||||
return 'Connection Error';
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* start new DM channel with user
|
||||
* query Object with either userId: number or userName: string
|
||||
* return channel Array on success, error string if not
|
||||
*/
|
||||
export async function requestStartDm(query) {
|
||||
const response = await fetchWithTimeout('api/startdm', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(query),
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await response.json();
|
||||
if (res.errors) {
|
||||
return res.errors[0];
|
||||
}
|
||||
if (response.ok && res.channel) {
|
||||
const { channel } = res;
|
||||
return channel;
|
||||
}
|
||||
|
||||
return 'Unknown Error';
|
||||
} catch {
|
||||
return 'Connection Error';
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* set receiving of all DMs on/off
|
||||
* block true if blocking all dms, false if unblocking
|
||||
* return error string or null if successful
|
||||
*/
|
||||
export async function requestBlockDm(block: boolean) {
|
||||
const response = await fetchWithTimeout('api/blockdm', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ block }),
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await response.json();
|
||||
if (res.errors) {
|
||||
return res.errors[0];
|
||||
}
|
||||
if (response.ok && res.status === 'ok') {
|
||||
return null;
|
||||
}
|
||||
return 'Unknown Error';
|
||||
} catch {
|
||||
return 'Connection Error';
|
||||
}
|
||||
}
|
|
@ -7,6 +7,11 @@ import type {
|
|||
} from './types';
|
||||
import type { Cell } from '../core/Cell';
|
||||
import type { ColorIndex } from '../core/Palette';
|
||||
import {
|
||||
requestStartDm,
|
||||
requestBlock,
|
||||
requestBlockDm,
|
||||
} from './fetch';
|
||||
|
||||
export function sweetAlert(
|
||||
title: string,
|
||||
|
@ -490,8 +495,10 @@ export function receiveMe(
|
|||
ranking,
|
||||
dailyRanking,
|
||||
minecraftname,
|
||||
blockDm,
|
||||
canvases,
|
||||
channels,
|
||||
blocked,
|
||||
userlvl,
|
||||
} = me;
|
||||
return {
|
||||
|
@ -504,8 +511,10 @@ export function receiveMe(
|
|||
ranking,
|
||||
dailyRanking,
|
||||
minecraftname,
|
||||
blockDm: !!blockDm,
|
||||
canvases,
|
||||
channels,
|
||||
blocked,
|
||||
userlvl,
|
||||
};
|
||||
}
|
||||
|
@ -599,6 +608,13 @@ function setChatFetching(fetching: boolean): Action {
|
|||
};
|
||||
}
|
||||
|
||||
function setApiFetching(fetching: boolean): Action {
|
||||
return {
|
||||
type: 'SET_API_FETCHING',
|
||||
fetching,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchChatMessages(
|
||||
cid: number,
|
||||
): PromiseAction {
|
||||
|
@ -608,6 +624,9 @@ export function fetchChatMessages(
|
|||
credentials: 'include',
|
||||
});
|
||||
|
||||
/*
|
||||
* timeout in order to not spam api requests and get rate limited
|
||||
*/
|
||||
if (response.ok) {
|
||||
setTimeout(() => { dispatch(setChatFetching(false)); }, 500);
|
||||
const { history } = await response.json();
|
||||
|
@ -755,38 +774,95 @@ export function addChatChannel(channel: Array): Action {
|
|||
};
|
||||
}
|
||||
|
||||
export function blockUser(userId: number, userName: string): Action {
|
||||
return {
|
||||
type: 'BLOCK_USER',
|
||||
userId,
|
||||
userName,
|
||||
};
|
||||
}
|
||||
|
||||
export function unblockUser(userId: number, userName: string): Action {
|
||||
return {
|
||||
type: 'UNBLOCK_USER',
|
||||
userId,
|
||||
userName,
|
||||
};
|
||||
}
|
||||
|
||||
export function blockingDm(blockDm: boolean): Action {
|
||||
return {
|
||||
type: 'SET_BLOCKING_DM',
|
||||
blockDm,
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* query: Object with either userId: number or userName: string
|
||||
*/
|
||||
export function startDm(query): PromiseAction {
|
||||
return async (dispatch) => {
|
||||
const response = await fetch('api/startdm', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(query),
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await response.json();
|
||||
if (res.errors) {
|
||||
dispatch(sweetAlert(
|
||||
'Direct Message Error',
|
||||
res.errors[0],
|
||||
'error',
|
||||
'OK',
|
||||
));
|
||||
dispatch(setApiFetching(true));
|
||||
const res = await requestStartDm(query);
|
||||
if (typeof res === 'string') {
|
||||
dispatch(sweetAlert(
|
||||
'Direct Message Error',
|
||||
res,
|
||||
'error',
|
||||
'OK',
|
||||
));
|
||||
} else {
|
||||
const channelId = res[0];
|
||||
if (channelId) {
|
||||
dispatch(addChatChannel(res));
|
||||
dispatch(setChatChannel(channelId));
|
||||
}
|
||||
if (response.ok) {
|
||||
const { channel } = res;
|
||||
const channelId = channel[0];
|
||||
if (channelId) {
|
||||
await dispatch(addChatChannel(channel));
|
||||
dispatch(setChatChannel(channelId));
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
dispatch(notify('Couldn\'t start DM'));
|
||||
}
|
||||
dispatch(setApiFetching(false));
|
||||
};
|
||||
}
|
||||
|
||||
export function setUserBlock(
|
||||
userId: number,
|
||||
userName: string,
|
||||
block: boolean,
|
||||
) {
|
||||
return async (dispatch) => {
|
||||
dispatch(setApiFetching(true));
|
||||
const res = await requestBlock(userId, block);
|
||||
if (res) {
|
||||
dispatch(sweetAlert(
|
||||
'User Block Error',
|
||||
res,
|
||||
'error',
|
||||
'OK',
|
||||
));
|
||||
} else if (block) {
|
||||
dispatch(blockUser(userId, userName));
|
||||
} else {
|
||||
dispatch(unblockUser(userId, userName));
|
||||
}
|
||||
dispatch(setApiFetching(false));
|
||||
};
|
||||
}
|
||||
|
||||
export function setBlockingDm(
|
||||
block: boolean,
|
||||
) {
|
||||
return async (dispatch) => {
|
||||
dispatch(setApiFetching(true));
|
||||
const res = await requestBlockDm(block);
|
||||
if (res) {
|
||||
dispatch(sweetAlert(
|
||||
'Blocking DMs Error',
|
||||
res,
|
||||
'error',
|
||||
'OK',
|
||||
));
|
||||
} else {
|
||||
dispatch(blockingDm(block));
|
||||
}
|
||||
dispatch(setApiFetching(false));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,9 @@ export type Action =
|
|||
| { type: 'SET_CHAT_FETCHING', fetching: boolean }
|
||||
| { type: 'SET_CHAT_INPUT_MSG', message: string }
|
||||
| { type: 'ADD_CHAT_INPUT_MSG', message: string }
|
||||
| { type: 'BLOCK_USER', userId: number, userName: string }
|
||||
| { type: 'UNBLOCK_USER', userId: number, userName: string }
|
||||
| { type: 'SET_BLOCKING_DM', blockDm: boolean }
|
||||
| { type: 'RECEIVE_ME',
|
||||
name: string,
|
||||
waitSeconds: number,
|
||||
|
@ -84,8 +87,10 @@ export type Action =
|
|||
ranking: number,
|
||||
dailyRanking: number,
|
||||
minecraftname: string,
|
||||
blockDm: boolean,
|
||||
canvases: Object,
|
||||
channels: Object,
|
||||
channels: Array,
|
||||
blocked: Array,
|
||||
userlvl: number,
|
||||
}
|
||||
| { type: 'RECEIVE_STATS', totalRanking: Object, totalDailyRanking: Object }
|
||||
|
|
|
@ -39,10 +39,12 @@ const Chat = ({
|
|||
setChannel,
|
||||
fetchMessages,
|
||||
fetching,
|
||||
blocked,
|
||||
}) => {
|
||||
const listRef = useRef();
|
||||
const [selection, setSelection] = useState(null);
|
||||
const [nameRegExp, setNameRegExp] = useState(null);
|
||||
const [blockedIds, setBlockedIds] = useState([]);
|
||||
|
||||
const { stayScrolled } = useStayScrolled(listRef, {
|
||||
initialScroll: Infinity,
|
||||
|
@ -75,6 +77,14 @@ const Chat = ({
|
|||
setNameRegExp(regExp);
|
||||
}, [ownName]);
|
||||
|
||||
useEffect(() => {
|
||||
const bl = [];
|
||||
for (let i = 0; i < blocked.length; i += 1) {
|
||||
bl.push(blocked[i][0]);
|
||||
}
|
||||
setBlockedIds(bl);
|
||||
}, [blocked.length]);
|
||||
|
||||
function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
const msg = inputMessage.trim();
|
||||
|
@ -102,71 +112,74 @@ const Chat = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<ul
|
||||
className="chatarea"
|
||||
ref={listRef}
|
||||
style={{ flexGrow: 1 }}
|
||||
onMouseUp={() => { setSelection(saveSelection); }}
|
||||
role="presentation"
|
||||
>
|
||||
{
|
||||
(!channelMessages.length)
|
||||
&& (
|
||||
<ChatMessage
|
||||
name="info"
|
||||
msgArray={splitChatMessage('Start chatting here', nameRegExp)}
|
||||
country="xx"
|
||||
uid={0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
channelMessages.map((message) => (
|
||||
<ChatMessage
|
||||
name={message[0]}
|
||||
msgArray={splitChatMessage(message[1], nameRegExp)}
|
||||
country={message[2]}
|
||||
uid={message[3]}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
{(ownName) ? (
|
||||
<div classNam="chatinput">
|
||||
<form
|
||||
onSubmit={(e) => handleSubmit(e)}
|
||||
style={{ display: 'flex', flexDirection: 'row' }}
|
||||
>
|
||||
<input
|
||||
style={{ flexGrow: 1, minWidth: 40 }}
|
||||
value={inputMessage}
|
||||
onChange={(e) => setInputMessage(e.target.value)}
|
||||
id="chatmsginput"
|
||||
maxLength="200"
|
||||
type="text"
|
||||
placeholder="Chat here"
|
||||
/>
|
||||
<button
|
||||
style={{ flexGrow: 0 }}
|
||||
type="submit"
|
||||
>
|
||||
‣
|
||||
</button>
|
||||
<ChannelDropDown />
|
||||
</form>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="modallink"
|
||||
onClick={open}
|
||||
style={{ textAlign: 'center', fontSize: 13 }}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
<div style={{ display: 'relative', width: '100%', height: '100%' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<ul
|
||||
className="chatarea"
|
||||
ref={listRef}
|
||||
style={{ flexGrow: 1 }}
|
||||
onMouseUp={() => { setSelection(saveSelection); }}
|
||||
role="presentation"
|
||||
>
|
||||
You must be logged in to chat
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
(!channelMessages.length)
|
||||
&& (
|
||||
<ChatMessage
|
||||
name="info"
|
||||
msgArray={splitChatMessage('Start chatting here', nameRegExp)}
|
||||
country="xx"
|
||||
uid={0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
channelMessages.map((message) => ((blockedIds.includes(message[3]))
|
||||
? null : (
|
||||
<ChatMessage
|
||||
name={message[0]}
|
||||
msgArray={splitChatMessage(message[1], nameRegExp)}
|
||||
country={message[2]}
|
||||
uid={message[3]}
|
||||
/>
|
||||
)))
|
||||
}
|
||||
</ul>
|
||||
{(ownName) ? (
|
||||
<div classNam="chatinput">
|
||||
<form
|
||||
onSubmit={(e) => handleSubmit(e)}
|
||||
style={{ display: 'flex', flexDirection: 'row' }}
|
||||
>
|
||||
<input
|
||||
style={{ flexGrow: 1, minWidth: 40 }}
|
||||
value={inputMessage}
|
||||
onChange={(e) => setInputMessage(e.target.value)}
|
||||
id="chatmsginput"
|
||||
maxLength="200"
|
||||
type="text"
|
||||
placeholder="Chat here"
|
||||
/>
|
||||
<button
|
||||
style={{ flexGrow: 0 }}
|
||||
type="submit"
|
||||
>
|
||||
‣
|
||||
</button>
|
||||
<ChannelDropDown />
|
||||
</form>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="modallink"
|
||||
onClick={open}
|
||||
style={{ textAlign: 'center', fontSize: 13 }}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
You must be logged in to chat
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -177,13 +190,17 @@ function mapStateToProps(state: State) {
|
|||
const {
|
||||
channels,
|
||||
messages,
|
||||
fetching,
|
||||
inputMessage,
|
||||
blocked,
|
||||
} = state.chat;
|
||||
const {
|
||||
fetchingChat: fetching,
|
||||
} = state.fetching;
|
||||
return {
|
||||
channels,
|
||||
messages,
|
||||
fetching,
|
||||
blocked,
|
||||
inputMessage,
|
||||
chatChannel,
|
||||
ownName: name,
|
||||
|
|
|
@ -18,17 +18,10 @@ const ChatModal = () => (
|
|||
right: 10,
|
||||
}}
|
||||
>
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
<p className="modaltext">Chat with other people here</p>
|
||||
</p>
|
||||
<div
|
||||
className="inarea"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 10,
|
||||
top: 50,
|
||||
left: 10,
|
||||
right: 10,
|
||||
height: '95%',
|
||||
}}
|
||||
>
|
||||
<Chat />
|
||||
|
|
|
@ -10,10 +10,10 @@ const MdToggleButton = ({ value, onToggle }) => (
|
|||
<ToggleButton
|
||||
inactiveLabel={<MdClose />}
|
||||
activeLabel={<MdCheck />}
|
||||
thumbAnimateRange={[-10, 36]}
|
||||
value={value}
|
||||
onToggle={onToggle}
|
||||
/>
|
||||
);
|
||||
|
||||
// thumbAnimateRange={[-10, 36]}
|
||||
export default MdToggleButton;
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Change Mail Form
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
setBlockingDm,
|
||||
setUserBlock,
|
||||
} from '../actions';
|
||||
import MdToggleButtonHover from './MdToggleButtonHover';
|
||||
|
||||
const SocialSettings = ({
|
||||
blocked,
|
||||
fetching,
|
||||
blockDm,
|
||||
setBlockDm,
|
||||
unblock,
|
||||
done,
|
||||
}) => (
|
||||
<div className="inarea">
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
margin: 10,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
flex: 'auto',
|
||||
textAlign: 'left',
|
||||
}}
|
||||
className="modaltitle"
|
||||
>
|
||||
Block all Private Messages
|
||||
</span>
|
||||
<MdToggleButtonHover
|
||||
value={blockDm}
|
||||
onToggle={() => {
|
||||
if (!fetching) {
|
||||
setBlockDm(!blockDm);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="modaldivider" />
|
||||
<p
|
||||
style={{
|
||||
textAlign: 'left',
|
||||
marginLeft: 10,
|
||||
}}
|
||||
className="modaltitle"
|
||||
>Unblock Users</p>
|
||||
{
|
||||
(blocked.length) ? (
|
||||
<span
|
||||
className="unblocklist"
|
||||
>
|
||||
{
|
||||
blocked.map((bl) => (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
if (!fetching) {
|
||||
unblock(bl[0], bl[1]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{bl[1]}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</span>
|
||||
)
|
||||
: (
|
||||
<p className="modaltext">You have no users blocked</p>
|
||||
)
|
||||
}
|
||||
<div className="modaldivider" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={done}
|
||||
style={{ margin: 10 }}
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
function mapStateToProps(state: State) {
|
||||
const { blocked } = state.chat;
|
||||
const { blockDm } = state.user;
|
||||
const { fetchingApi: fetching } = state.fetching;
|
||||
return {
|
||||
blocked,
|
||||
blockDm,
|
||||
fetching,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
setBlockDm(block) {
|
||||
dispatch(setBlockingDm(block));
|
||||
},
|
||||
unblock(userId, userName) {
|
||||
dispatch(setUserBlock(userId, userName, false));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SocialSettings);
|
|
@ -13,6 +13,7 @@ import ChangePassword from './ChangePassword';
|
|||
import ChangeName from './ChangeName';
|
||||
import ChangeMail from './ChangeMail';
|
||||
import DeleteAccount from './DeleteAccount';
|
||||
import SocialSettings from './SocialSettings';
|
||||
|
||||
import { numberToString } from '../core/utils';
|
||||
|
||||
|
@ -45,6 +46,7 @@ class UserArea extends React.Component {
|
|||
changeMailExtended,
|
||||
changePasswdExtended,
|
||||
deleteAccountExtended,
|
||||
socialSettingsExtended,
|
||||
} = this.state;
|
||||
return (
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
|
@ -84,6 +86,7 @@ class UserArea extends React.Component {
|
|||
changeMailExtended: false,
|
||||
changePasswdExtended: false,
|
||||
deleteAccountExtended: false,
|
||||
socialSettingsExtended: false,
|
||||
})}
|
||||
> Change Username</span> |
|
||||
{(mailreg)
|
||||
|
@ -98,6 +101,7 @@ class UserArea extends React.Component {
|
|||
changeMailExtended: true,
|
||||
changePasswdExtended: false,
|
||||
deleteAccountExtended: false,
|
||||
socialSettingsExtended: false,
|
||||
})}
|
||||
> Change Mail</span> |
|
||||
</span>
|
||||
|
@ -111,6 +115,7 @@ class UserArea extends React.Component {
|
|||
changeMailExtended: false,
|
||||
changePasswdExtended: true,
|
||||
deleteAccountExtended: false,
|
||||
socialSettingsExtended: false,
|
||||
})}
|
||||
> Change Password</span> |
|
||||
<span
|
||||
|
@ -122,9 +127,24 @@ class UserArea extends React.Component {
|
|||
changeMailExtended: false,
|
||||
changePasswdExtended: false,
|
||||
deleteAccountExtended: true,
|
||||
socialSettingsExtended: false,
|
||||
})}
|
||||
> Delete Account</span> )
|
||||
<br />(
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={() => this.setState({
|
||||
changeNameExtended: false,
|
||||
changeMailExtended: false,
|
||||
changePasswdExtended: false,
|
||||
deleteAccountExtended: false,
|
||||
socialSettingsExtended: true,
|
||||
})}
|
||||
> Social Settings</span> )
|
||||
</p>
|
||||
<p className="modaltext" />
|
||||
{(changePasswdExtended)
|
||||
&& (
|
||||
<ChangePassword
|
||||
|
@ -156,6 +176,12 @@ class UserArea extends React.Component {
|
|||
done={() => { this.setState({ deleteAccountExtended: false }); }}
|
||||
/>
|
||||
)}
|
||||
{(socialSettingsExtended)
|
||||
&& (
|
||||
<SocialSettings
|
||||
done={() => { this.setState({ socialSettingsExtended: false }); }}
|
||||
/>
|
||||
)}
|
||||
{(typeof window.hcaptcha !== 'undefined')
|
||||
&& (
|
||||
<img
|
||||
|
|
|
@ -12,10 +12,10 @@ import {
|
|||
hideContextMenu,
|
||||
addToChatInputMessage,
|
||||
startDm,
|
||||
setUserBlock,
|
||||
} from '../actions';
|
||||
import type { State } from '../reducers';
|
||||
|
||||
|
||||
const UserContextMenu = ({
|
||||
xPos,
|
||||
yPos,
|
||||
|
@ -23,6 +23,7 @@ const UserContextMenu = ({
|
|||
name,
|
||||
addToInput,
|
||||
dm,
|
||||
block,
|
||||
close,
|
||||
}) => {
|
||||
const wrapperRef = useRef(null);
|
||||
|
@ -53,6 +54,10 @@ const UserContextMenu = ({
|
|||
>
|
||||
<div
|
||||
style={{ borderBottom: 'thin solid' }}
|
||||
onClick={() => {
|
||||
block(uid, name);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
Block
|
||||
</div>
|
||||
|
@ -113,6 +118,9 @@ function mapDispatchToProps(dispatch) {
|
|||
dm(userId) {
|
||||
dispatch(startDm({ userId }));
|
||||
},
|
||||
block(userId, userName) {
|
||||
dispatch(setUserBlock(userId, userName, true));
|
||||
},
|
||||
close() {
|
||||
dispatch(hideContextMenu());
|
||||
},
|
||||
|
|
|
@ -64,6 +64,12 @@ const RegUser = Model.define('User', {
|
|||
defaultValue: false,
|
||||
},
|
||||
|
||||
blockDm: {
|
||||
type: DataType.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
|
||||
discordid: {
|
||||
type: DataType.CHAR(18),
|
||||
allowNull: true,
|
||||
|
|
|
@ -197,6 +197,7 @@ class User {
|
|||
mailVerified: regUser.mailVerified,
|
||||
mcVerified: regUser.mcVerified,
|
||||
minecraftname: regUser.minecraftname,
|
||||
blockDm: regUser.blockDm,
|
||||
totalPixels: regUser.totalPixels,
|
||||
dailyTotalPixels: regUser.dailyTotalPixels,
|
||||
ranking: regUser.ranking,
|
||||
|
|
|
@ -32,7 +32,6 @@ export type CanvasState = {
|
|||
view: Cell,
|
||||
scale: number,
|
||||
viewscale: number,
|
||||
fetchs: number,
|
||||
isHistoricalView: boolean,
|
||||
historicalDate: string,
|
||||
historicalTime: string,
|
||||
|
@ -137,7 +136,6 @@ function getViewFromURL(canvases: Object) {
|
|||
|
||||
const initialState: CanvasState = {
|
||||
...getViewFromURL(DEFAULT_CANVASES),
|
||||
fetchs: 0,
|
||||
isHistoricalView: false,
|
||||
historicalDate: null,
|
||||
historicalTime: null,
|
||||
|
@ -242,35 +240,6 @@ export default function canvasReducer(
|
|||
};
|
||||
}
|
||||
|
||||
case 'REQUEST_BIG_CHUNK': {
|
||||
const {
|
||||
fetchs,
|
||||
} = state;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchs: fetchs + 1,
|
||||
};
|
||||
}
|
||||
|
||||
case 'RECEIVE_BIG_CHUNK': {
|
||||
const { fetchs } = state;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchs: fetchs + 1,
|
||||
};
|
||||
}
|
||||
|
||||
case 'RECEIVE_BIG_CHUNK_FAILURE': {
|
||||
const { fetchs } = state;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchs: fetchs + 1,
|
||||
};
|
||||
}
|
||||
|
||||
case 'SELECT_COLOR': {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -6,19 +6,19 @@ import type { Action } from '../actions/types';
|
|||
|
||||
export type ChatState = {
|
||||
inputMessage: string,
|
||||
// [[cid, name], [cid2, name2],...]
|
||||
// [[cid, name, type, lastMessage], [cid2, name2, type2, lastMessage2],...]
|
||||
channels: Array,
|
||||
// { cid: [message1,message2,message2,...]}
|
||||
// [[userId, userName], [userId2, userName2],...]
|
||||
blocked: Array,
|
||||
// { cid: [message1,message2,message3,...]}
|
||||
messages: Object,
|
||||
// if currently fetching messages
|
||||
fetching: boolean,
|
||||
}
|
||||
|
||||
const initialState: ChatState = {
|
||||
inputMessage: '',
|
||||
channels: [],
|
||||
blocked: [],
|
||||
messages: {},
|
||||
fetching: false,
|
||||
};
|
||||
|
||||
export default function chat(
|
||||
|
@ -30,6 +30,27 @@ export default function chat(
|
|||
return {
|
||||
...state,
|
||||
channels: action.channels,
|
||||
blocked: action.blocked,
|
||||
};
|
||||
}
|
||||
|
||||
case 'BLOCK_USER': {
|
||||
const { userId, userName } = action;
|
||||
return {
|
||||
...state,
|
||||
blocked: [
|
||||
...state.blocked,
|
||||
[userId, userName],
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case 'UNBLOCK_USER': {
|
||||
const { userId } = action;
|
||||
const blocked = state.blocked.filter((bl) => (bl[0] !== userId));
|
||||
return {
|
||||
...state,
|
||||
blocked,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -45,14 +66,6 @@ export default function chat(
|
|||
};
|
||||
}
|
||||
|
||||
case 'SET_CHAT_FETCHING': {
|
||||
const { fetching } = action;
|
||||
return {
|
||||
...state,
|
||||
fetching,
|
||||
};
|
||||
}
|
||||
|
||||
case 'SET_CHAT_INPUT_MSG': {
|
||||
const { message } = action;
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* keeps track of some api fetching states
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type { Action } from '../actions/types';
|
||||
|
||||
export type FetchingState = {
|
||||
fetchingChunks: number,
|
||||
fetchingChat: boolean,
|
||||
fetchinApi: boolean,
|
||||
}
|
||||
|
||||
const initialState: FetchingState = {
|
||||
fetchingChunks: 0,
|
||||
fetchingChat: false,
|
||||
fetchinApi: false,
|
||||
};
|
||||
|
||||
export default function fetching(
|
||||
state: FetchingState = initialState,
|
||||
action: Action,
|
||||
): FetchingState {
|
||||
switch (action.type) {
|
||||
case 'SET_CHAT_FETCHING': {
|
||||
const { fetching: fetchingChat } = action;
|
||||
return {
|
||||
...state,
|
||||
fetchingChat,
|
||||
};
|
||||
}
|
||||
|
||||
case 'SET_API_FETCHING': {
|
||||
const { fetching: fetchinApi } = action;
|
||||
return {
|
||||
...state,
|
||||
fetchinApi,
|
||||
};
|
||||
}
|
||||
|
||||
case 'REQUEST_BIG_CHUNK': {
|
||||
const {
|
||||
fetchingChunks,
|
||||
} = state;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingChunks: fetchingChunks + 1,
|
||||
};
|
||||
}
|
||||
|
||||
case 'RECEIVE_BIG_CHUNK': {
|
||||
const { fetchingChunks } = state;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingChunks: fetchingChunks - 1,
|
||||
};
|
||||
}
|
||||
|
||||
case 'RECEIVE_BIG_CHUNK_FAILURE': {
|
||||
const { fetchingChunks } = state;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingChunks: fetchingChunks - 1,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import modal from './modal';
|
|||
import user from './user';
|
||||
import chat from './chat';
|
||||
import contextMenu from './contextMenu';
|
||||
import fetching from './fetching';
|
||||
|
||||
import type { AudioState } from './audio';
|
||||
import type { CanvasState } from './canvas';
|
||||
|
@ -17,6 +18,7 @@ import type { ModalState } from './modal';
|
|||
import type { UserState } from './user';
|
||||
import type { ChatState } from './chat';
|
||||
import type { ContextMenuState } from './contextMenu';
|
||||
import type { FetchingState } from './fetching';
|
||||
|
||||
export type State = {
|
||||
audio: AudioState,
|
||||
|
@ -26,6 +28,7 @@ export type State = {
|
|||
user: UserState,
|
||||
chat: ChatState,
|
||||
contextMenu: ContextMenuState,
|
||||
fetching: FetchingState,
|
||||
};
|
||||
|
||||
const config = {
|
||||
|
@ -37,6 +40,7 @@ const config = {
|
|||
'modal',
|
||||
'chat',
|
||||
'contextMenu',
|
||||
'fetching',
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -48,4 +52,5 @@ export default persistCombineReducers(config, {
|
|||
user,
|
||||
chat,
|
||||
contextMenu,
|
||||
fetching,
|
||||
});
|
||||
|
|
|
@ -24,6 +24,8 @@ export type UserState = {
|
|||
totalDailyRanking: Object,
|
||||
// minecraft
|
||||
minecraftname: string,
|
||||
// blocking all Dms
|
||||
blockDm: boolean,
|
||||
// if user is using touchscreen
|
||||
isOnMobile: boolean,
|
||||
// small notifications for received cooldown
|
||||
|
@ -45,6 +47,7 @@ const initialState: UserState = {
|
|||
totalRanking: {},
|
||||
totalDailyRanking: {},
|
||||
minecraftname: null,
|
||||
blockDm: false,
|
||||
isOnMobile: false,
|
||||
notification: null,
|
||||
userlvl: 0,
|
||||
|
@ -139,6 +142,7 @@ export default function user(
|
|||
ranking,
|
||||
dailyRanking,
|
||||
minecraftname,
|
||||
blockDm,
|
||||
userlvl,
|
||||
} = action;
|
||||
const messages = (action.messages) ? action.messages : [];
|
||||
|
@ -152,6 +156,7 @@ export default function user(
|
|||
ranking,
|
||||
dailyRanking,
|
||||
minecraftname,
|
||||
blockDm,
|
||||
userlvl,
|
||||
};
|
||||
}
|
||||
|
@ -173,6 +178,14 @@ export default function user(
|
|||
};
|
||||
}
|
||||
|
||||
case 'SET_BLOCKING_DM': {
|
||||
const { blockDm } = action;
|
||||
return {
|
||||
...state,
|
||||
blockDm,
|
||||
};
|
||||
}
|
||||
|
||||
case 'SET_MINECRAFT_NAME': {
|
||||
const { minecraftname } = action;
|
||||
return {
|
||||
|
|
|
@ -8,11 +8,12 @@
|
|||
import type { Request, Response } from 'express';
|
||||
|
||||
import logger from '../../core/logger';
|
||||
import { RegUser, UserBlock } from '../../data/models';
|
||||
import { RegUser, UserBlock, Channel } from '../../data/models';
|
||||
|
||||
async function block(req: Request, res: Response) {
|
||||
let userId = parseInt(req.body.userId, 10);
|
||||
let { userName } = req.body;
|
||||
const { block: blocking } = req.body;
|
||||
const { user } = req;
|
||||
|
||||
const errors = [];
|
||||
|
@ -23,6 +24,9 @@ async function block(req: Request, res: Response) {
|
|||
}
|
||||
query.id = userId;
|
||||
}
|
||||
if (typeof blocking !== 'boolean') {
|
||||
errors.push('Not defined if blocking or unblocking');
|
||||
}
|
||||
if (userName) {
|
||||
query.name = userName;
|
||||
}
|
||||
|
@ -33,7 +37,7 @@ async function block(req: Request, res: Response) {
|
|||
errors.push('You are not logged in');
|
||||
}
|
||||
if (user && userId && user.id === userId) {
|
||||
errors.push('You can not DM yourself.');
|
||||
errors.push('You can not block yourself.');
|
||||
}
|
||||
if (errors.length) {
|
||||
res.status(400);
|
||||
|
@ -61,14 +65,53 @@ async function block(req: Request, res: Response) {
|
|||
userId = targetUser.id;
|
||||
userName = targetUser.name;
|
||||
|
||||
const ret = await UserBlock.findOrCreate({
|
||||
let ret = null;
|
||||
if (blocking) {
|
||||
ret = await UserBlock.findOrCreate({
|
||||
where: {
|
||||
uid: user.id,
|
||||
buid: userId,
|
||||
},
|
||||
raw: true,
|
||||
attributes: ['uid'],
|
||||
});
|
||||
} else {
|
||||
ret = await UserBlock.destroy({
|
||||
where: {
|
||||
uid: user.id,
|
||||
buid: userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* delete possible dm channel
|
||||
*/
|
||||
let dmu1id = null;
|
||||
let dmu2id = null;
|
||||
if (user.id > userId) {
|
||||
dmu1id = userId;
|
||||
dmu2id = user.id;
|
||||
} else {
|
||||
dmu1id = user.id;
|
||||
dmu2id = userId;
|
||||
}
|
||||
|
||||
// TODO test if this removes association too
|
||||
const channel = await Channel.findOne({
|
||||
where: {
|
||||
uid: user.id,
|
||||
buid: userId,
|
||||
type: 1,
|
||||
dmu1id,
|
||||
dmu2id,
|
||||
},
|
||||
raw: true,
|
||||
attributes: ['uid'],
|
||||
});
|
||||
if (channel) {
|
||||
const channelId = channel.id;
|
||||
channel.destroy();
|
||||
}
|
||||
|
||||
// TODO notify websocket
|
||||
|
||||
if (ret) {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
|
@ -76,10 +119,10 @@ async function block(req: Request, res: Response) {
|
|||
} else {
|
||||
res.status(502);
|
||||
res.json({
|
||||
errors: ['Could not block user'],
|
||||
errors: ['Could not (un)block user'],
|
||||
});
|
||||
logger.info(
|
||||
`User ${user.getName()} blocked ${userName}`,
|
||||
`User ${user.getName()} (un)blocked ${userName}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
*
|
||||
* block all private messages
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
|
||||
import logger from '../../core/logger';
|
||||
|
||||
async function blockdm(req: Request, res: Response) {
|
||||
const { block } = req.body;
|
||||
const { user } = req;
|
||||
|
||||
const errors = [];
|
||||
if (typeof block !== 'boolean') {
|
||||
errors.push('Not defined if blocking or unblocking');
|
||||
}
|
||||
if (!user || !user.regUser) {
|
||||
errors.push('You are not logged in');
|
||||
}
|
||||
if (errors.length) {
|
||||
res.status(400);
|
||||
res.json({
|
||||
errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`User ${user.getName()} (un)blocked all dms`,
|
||||
);
|
||||
|
||||
await user.regUser.update({
|
||||
blockDm: block,
|
||||
});
|
||||
|
||||
// TODO notify websocket
|
||||
|
||||
res.json({
|
||||
status: 'ok',
|
||||
});
|
||||
}
|
||||
|
||||
export default blockdm;
|
|
@ -20,6 +20,7 @@ import history from './history';
|
|||
import chatHistory from './chathistory';
|
||||
import startDm from './startdm';
|
||||
import block from './block';
|
||||
import blockdm from './blockdm';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
@ -86,6 +87,8 @@ router.post('/startdm', startDm);
|
|||
|
||||
router.post('/block', block);
|
||||
|
||||
router.post('/blockdm', blockdm);
|
||||
|
||||
router.use('/auth', auth(passport));
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -49,6 +49,7 @@ async function startDm(req: Request, res: Response) {
|
|||
attributes: [
|
||||
'id',
|
||||
'name',
|
||||
'blockDm',
|
||||
],
|
||||
raw: true,
|
||||
});
|
||||
|
@ -59,6 +60,12 @@ async function startDm(req: Request, res: Response) {
|
|||
});
|
||||
return;
|
||||
}
|
||||
if (blockDm) {
|
||||
res.status(401);
|
||||
res.json({
|
||||
errors: ['Target user doesn\'t allo DMs'],
|
||||
});
|
||||
}
|
||||
userId = targetUser.id;
|
||||
userName = targetUser.name;
|
||||
|
||||
|
|
|
@ -74,6 +74,8 @@ a:hover {
|
|||
padding: 4px;
|
||||
margin-top: 4px;
|
||||
border-width: 1px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.tab-list {
|
||||
|
@ -552,6 +554,27 @@ tr:nth-child(even) {
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
.unblocklist {
|
||||
display: inline-block;
|
||||
margin: 5px 0px 15px 0px;
|
||||
width: 95%;
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
max-width: 340px;
|
||||
border: thin solid;
|
||||
}
|
||||
|
||||
.unblocklist > div {
|
||||
padding: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.unblocklist > div:hover {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
background-color: #db0000;
|
||||
}
|
||||
|
||||
.actionbuttons {
|
||||
vertical-align: text-bottom;
|
||||
cursor: pointer;
|
||||
|
|
Loading…
Reference in New Issue