fixing bugs that got introduced in the past two commits

This commit is contained in:
HF 2020-11-28 02:04:40 +01:00
parent ac464ba5a7
commit b79a12f931
37 changed files with 651 additions and 240 deletions

View File

@ -97,7 +97,7 @@ Configuration takes place in the environment variables that are defined in ecosy
Notes:
- to be able to use USE_PROXYCHECK, you have to have an account on proxycheck.io or getipintel or another checker setup and you might set some proxies in`proxies.json that get used for making proxycheck requests. Look into `src/isProxy.js` to see how things work, but keep in mind that this isn't neccessarily how pixelplanet.fun uses it.
- Admins are users with 0cd and access to `./admintools` for image-upload and whatever
- Admins are users with 0cd and access to `Admintools`in their User Menu for image-upload and whatever
- You can find out the id of a user by looking into the logs (i.e. `info: {ip} / {id} wants to place 2 in (1701, -8315)`) when he places a pixel or by checking the MySql Users database
- If you use gmail as mail transport, make sure that less-secure apps are allowed to access it in your settings [here](https://myaccount.google.com/lesssecureapps)

View File

@ -486,7 +486,6 @@ export function receivePixelUpdate(
export function loginUser(
me: Object,
): Action {
console.log('login', me);
return {
type: 'LOGIN',
...me,
@ -794,6 +793,20 @@ export function removeChatChannel(cid: number): Action {
};
}
export function muteChatChannel(cid: number): Action {
return {
type: 'MUTE_CHAT_CHANNEL',
cid,
};
}
export function unmuteChatChannel(cid: number): Action {
return {
type: 'UNMUTE_CHAT_CHANNEL',
cid,
};
}
/*
* query: Object with either userId: number or userName: string
*/

View File

@ -72,6 +72,8 @@ export type Action =
| { type: 'SET_CHAT_CHANNEL', cid: number }
| { type: 'ADD_CHAT_CHANNEL', channel: Object }
| { type: 'REMOVE_CHAT_CHANNEL', cid: number }
| { type: 'MUTE_CHAT_CHANNEL', cid: number }
| { type: 'UNMUTE_CHAT_CHANNEL', cid: number }
| { type: 'SET_CHAT_FETCHING', fetching: boolean }
| { type: 'SET_CHAT_INPUT_MSG', message: string }
| { type: 'ADD_CHAT_INPUT_MSG', message: string }

View File

@ -19,7 +19,6 @@ import ChatBox from './ChatBox';
import Menu from './Menu';
import UI from './UI';
import ExpandMenuButton from './ExpandMenuButton';
import MinecraftTPButton from './MinecraftTPButton';
import ModalRoot from './ModalRoot';
const App = () => (
@ -34,7 +33,6 @@ const App = () => (
<OnlineBox />
<CoordinatesBox />
<ExpandMenuButton />
<MinecraftTPButton />
<UI />
<ModalRoot />
</IconContext.Provider>

View File

@ -11,6 +11,8 @@ import { connect } from 'react-redux';
import {
hideContextMenu,
setLeaveChannel,
muteChatChannel,
unmuteChatChannel,
} from '../actions';
import type { State } from '../reducers';
@ -20,6 +22,9 @@ const UserContextMenu = ({
cid,
channels,
leave,
muteArr,
mute,
unmute,
close,
}) => {
const wrapperRef = useRef(null);
@ -41,6 +46,8 @@ const UserContextMenu = ({
};
}, [wrapperRef]);
const isMuted = muteArr.includes(cid);
return (
<div
ref={wrapperRef}
@ -50,8 +57,19 @@ const UserContextMenu = ({
top: yPos,
}}
>
<div>
Mute
<div
role="button"
onClick={() => {
if (isMuted) {
unmute(cid);
} else {
mute(cid);
}
}}
tabIndex={0}
style={{ borderTop: 'none' }}
>
{`${(isMuted) ? '✔' : '✘'} Mute`}
</div>
{(channels[cid][1] !== 0)
&& (
@ -62,7 +80,6 @@ const UserContextMenu = ({
close();
}}
tabIndex={0}
style={{ borderTop: 'thin solid' }}
>
Close
</div>
@ -83,11 +100,13 @@ function mapStateToProps(state: State) {
const {
cid,
} = args;
const { mute: muteArr } = state.chatRead;
return {
xPos,
yPos,
cid,
channels,
muteArr,
};
}
@ -99,6 +118,12 @@ function mapDispatchToProps(dispatch) {
leave(cid) {
dispatch(setLeaveChannel(cid));
},
mute(cid) {
dispatch(muteChatChannel(cid));
},
unmute(cid) {
dispatch(unmuteChatChannel(cid));
},
};
}

View File

@ -19,7 +19,9 @@ import {
const ChannelDropDown = ({
channels,
chatChannel,
chatRead,
unread,
chatNotify,
mute,
setChannel,
}) => {
const [show, setShow] = useState(false);
@ -27,7 +29,9 @@ const ChannelDropDown = ({
// 1: DMs
const [type, setType] = useState(0);
const [offset, setOffset] = useState(0);
const [unreadAny, setUnreadAny] = useState(false);
const [chatChannelName, setChatChannelName] = useState('...');
const [hasDm, setHasDm] = useState(false);
const wrapperRef = useRef(null);
const buttonRef = useRef(null);
@ -44,6 +48,10 @@ const ChannelDropDown = ({
}
}, []);
const handleWindowResize = useCallback(() => {
setShow(false);
}, []);
useLayoutEffect(() => {
if (show) {
if (channels[chatChannel]) {
@ -52,12 +60,45 @@ const ChannelDropDown = ({
}
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('touchstart', handleClickOutside);
window.addEventListener('resize', handleWindowResize);
} else {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchstart', handleClickOutside);
window.removeEventListener('resize', handleWindowResize);
}
}, [show]);
useEffect(() => {
const cids = Object.keys(channels);
let i = 0;
while (i < cids.length) {
const cid = cids[i];
if (
channels[cid][1] !== 0
&& unread[cid]
&& !mute.includes(cid)
) {
setUnreadAny(true);
break;
}
i += 1;
}
if (i === cids.length) {
setUnreadAny(false);
}
}, [unread]);
useEffect(() => {
const cids = Object.keys(channels);
for (let i = 0; i < cids.length; i += 1) {
if (channels[cids[i]][1] === 1) {
setHasDm(true);
return;
}
}
setHasDm(false);
}, [channels]);
useEffect(() => {
if (channels[chatChannel]) {
setChatChannelName(channels[chatChannel][0]);
@ -70,12 +111,14 @@ const ChannelDropDown = ({
>
<div
ref={buttonRef}
style={{
width: 50,
}}
role="button"
tabIndex={-1}
onClick={() => setShow(!show)}
className="channelbtn"
className={`channelbtn${(show) ? ' selected' : ''}`}
>
{(unreadAny && chatNotify && !show) && (
<div style={{ top: -4 }} className="chnunread">⦿</div>
)}
{chatChannelName}
</div>
{(show)
@ -84,23 +127,41 @@ const ChannelDropDown = ({
ref={wrapperRef}
style={{
position: 'absolute',
bottom: offset + 5,
bottom: offset,
right: 9,
}}
className="channeldd"
>
<div>
<div
className="chntop"
>
<span
style={{ borderLeft: 'none' }}
className={`chntype${(type === 0) ? ' selected' : ''}`}
onClick={() => setType(0)}
role="button"
tabIndex={-1}
>
<MdChat />
</span>
|
<span
onClick={() => setType(1)}
>
<FaUserFriends />
</span>
{(hasDm)
&& (
<span
className={
`chntype${
(type === 1) ? ' selected' : ''
}`
}
onClick={() => setType(1)}
role="button"
tabIndex={-1}
>
{(unreadAny && chatNotify && type !== 1) && (
<div className="chnunread">⦿</div>
)}
<FaUserFriends />
</span>
)}
</div>
<div
className="channeldds"
@ -116,8 +177,7 @@ const ChannelDropDown = ({
}
return false;
}).map((cid) => {
const [name,, lastTs] = channels[cid];
console.log(`name ${name} lastTC ${lastTs} compare to ${chatRead[cid]}`);
const [name] = channels[cid];
return (
<div
onClick={() => setChannel(cid)}
@ -126,10 +186,12 @@ const ChannelDropDown = ({
(cid === chatChannel) ? ' selected' : ''
}`
}
role="button"
tabIndex={-1}
>
{
(chatRead[cid] < lastTs) ? (
<span className="chnunread"></span>
(unread[cid] && chatNotify && !mute.includes(cid)) ? (
<div className="chnunread">⦿</div>
) : null
}
{name}
@ -145,15 +207,19 @@ const ChannelDropDown = ({
};
function mapStateToProps(state: State) {
const { channels } = state.chat;
const {
chatChannel,
chatRead,
} = state.gui;
const { channels } = state.chat;
unread,
mute,
} = state.chatRead;
const { chatNotify } = state.audio;
return {
channels,
chatChannel,
chatRead,
unread,
mute,
chatNotify,
};
}

View File

@ -12,7 +12,6 @@ import { connect } from 'react-redux';
import type { State } from '../reducers';
import ChatMessage from './ChatMessage';
import ChannelDropDown from './ChannelDropDown';
import { MAX_CHAT_MESSAGES } from '../core/constants';
import {
showUserAreaModal,
@ -23,7 +22,6 @@ import {
showContextMenu,
} from '../actions';
import ProtocolClient from '../socket/ProtocolClient';
import { saveSelection, restoreSelection } from '../utils/storeSelection';
import splitChatMessage from '../core/chatMessageFilter';
function escapeRegExp(string) {
@ -48,7 +46,6 @@ const Chat = ({
}) => {
const listRef = useRef();
const targetRef = useRef();
const [selection, setSelection] = useState(null);
const [nameRegExp, setNameRegExp] = useState(null);
const [blockedIds, setBlockedIds] = useState([]);
const [btnSize, setBtnSize] = useState(20);
@ -59,7 +56,7 @@ const Chat = ({
});
const channelMessages = messages[chatChannel] || [];
if (!messages[chatChannel] && !fetching) {
if (channels[chatChannel] && !messages[chatChannel] && !fetching) {
fetchMessages(chatChannel);
}
@ -67,16 +64,6 @@ const Chat = ({
stayScrolled();
}, [channelMessages.length]);
/*
* TODO this removes focus from chat box, fix this
*
useEffect(() => {
if (channelMessages.length === MAX_CHAT_MESSAGES) {
restoreSelection(selection);
}
}, [channelMessages]);
*/
useEffect(() => {
const regExp = (ownName)
? new RegExp(`(^|\\s)(@${escapeRegExp(ownName)})(\\s|$)`, 'g')
@ -168,7 +155,6 @@ const Chat = ({
className="chatarea"
ref={listRef}
style={{ flexGrow: 1 }}
onMouseUp={() => { setSelection(saveSelection); }}
role="presentation"
>
{
@ -204,6 +190,7 @@ const Chat = ({
style={{ flexGrow: 1, minWidth: 40 }}
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
autoComplete="off"
id="chatmsginput"
maxLength="200"
type="text"
@ -235,7 +222,7 @@ const Chat = ({
function mapStateToProps(state: State) {
const { name } = state.user;
const { chatChannel } = state.gui;
const { chatChannel } = state.chatRead;
const {
channels,
messages,

View File

@ -3,24 +3,77 @@
* @flow
*/
import React from 'react';
import React, {
useState, useEffect,
} from 'react';
import { connect } from 'react-redux';
import { MdForum } from 'react-icons/md';
import { showChatModal } from '../actions';
const ChatButton = ({ open }) => (
<div
id="chatbutton"
className="actionbuttons"
onClick={open}
role="button"
tabIndex={0}
>
<MdForum />
</div>: null
);
const ChatButton = ({
chatOpen,
modalOpen,
chatNotify,
channels,
unread,
mute,
open,
}) => {
const [unreadAny, setUnreadAny] = useState(false);
/*
* almost the same as in ChannelDropDown
* just cares about chatNotify too
*/
useEffect(() => {
if (!chatNotify || modalOpen || chatOpen) {
setUnreadAny(false);
return;
}
const cids = Object.keys(channels);
let i = 0;
while (i < cids.length) {
const cid = cids[i];
if (
channels[cid][1] !== 0
&& unread[cid]
&& !mute.includes(cid)
) {
setUnreadAny(true);
break;
}
i += 1;
}
if (i === cids.length) {
setUnreadAny(false);
}
});
return (
<div
id="chatbutton"
className="actionbuttons"
onClick={open}
role="button"
tabIndex={0}
>
{(unreadAny) && (
<div
style={{
position: 'fixed',
bottom: 27,
right: 62,
top: 'unset',
}}
className="chnunread"
>⦿</div>
)}
<MdForum />
</div>: null
);
};
function mapDispatchToProps(dispatch) {
return {
@ -30,4 +83,29 @@ function mapDispatchToProps(dispatch) {
};
}
export default connect(null, mapDispatchToProps)(ChatButton);
function mapStateToProps(state) {
const {
chatOpen,
modalOpen,
} = state.modal;
const {
chatNotify,
} = state.audio;
const {
channels,
} = state.chat;
const {
unread,
mute,
} = state.chatRead;
return {
chatOpen,
modalOpen,
chatNotify,
channels,
unread,
mute,
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatButton);

View File

@ -11,7 +11,13 @@ import HelpButton from './HelpButton';
import SettingsButton from './SettingsButton';
import LogInButton from './LogInButton';
import DownloadButton from './DownloadButton';
import MinecraftButton from './MinecraftButton';
/*
* removed MinecraftButton cause it didn't get used in over a year
* also CSS rule got removed
* and MinecraftModal from ModalRoot
* and MinecraftTPButton from App
* (support for it will be otherwise still kept)
*/
function Menu({
menuOpen,
@ -37,7 +43,6 @@ function Menu({
<SettingsButton />
<LogInButton />
<DownloadButton />
<MinecraftButton />
<HelpButton />
</div>
)

View File

@ -21,7 +21,6 @@ import RegisterModal from './RegisterModal';
import CanvasSelectModal from './CanvasSelectModal';
import ChatModal from './ChatModal';
import ForgotPasswordModal from './ForgotPasswordModal';
import MinecraftModal from './MinecraftModal';
const MODAL_COMPONENTS = {
@ -32,7 +31,6 @@ const MODAL_COMPONENTS = {
REGISTER: RegisterModal,
FORGOT_PASSWORD: ForgotPasswordModal,
CHAT: ChatModal,
MINECRAFT: MinecraftModal,
CANVAS_SELECTION: CanvasSelectModal,
/* other modals */
};

View File

@ -135,7 +135,7 @@ function SettingsModal({
<SettingsItem
title="Disable Game Sounds"
// eslint-disable-next-line max-len
description="All sound effects except Chat Notification will be disabled."
description="All sound effects will be disabled."
keyBind="M"
value={isMuted}
onToggle={onMute}

View File

@ -70,7 +70,7 @@ const SocialSettings = ({
}
}}
>
{bl[1]}
{`${bl[1]}`}
</div>
))
}

View File

@ -13,6 +13,7 @@ import {
addToChatInputMessage,
startDm,
setUserBlock,
setChatChannel,
} from '../actions';
import type { State } from '../reducers';
@ -24,6 +25,9 @@ const UserContextMenu = ({
addToInput,
dm,
block,
channels,
fetching,
setChannel,
close,
}) => {
const wrapperRef = useRef(null);
@ -56,13 +60,13 @@ const UserContextMenu = ({
}}
>
<div
style={{ borderBottom: 'thin solid' }}
onClick={() => {
block(uid, name);
close();
}}
role="button"
tabIndex={-1}
style={{ borderTop: 'none' }}
>
Block
</div>
@ -70,11 +74,24 @@ const UserContextMenu = ({
role="button"
tabIndex={0}
onClick={() => {
dm(uid);
// TODO if DM Channel with user already exist, just switch
/*
* if dm channel already exists,
* just switch
*/
const cids = Object.keys(channels);
for (let i = 0; i < cids.length; i += 1) {
const cid = cids[i];
if (channels[cid].length === 4 && channels[cid][3] === uid) {
setChannel(cid);
close();
return;
}
}
if (!fetching) {
dm(uid);
}
close();
}}
style={{ borderBottom: 'thin solid' }}
>
DM
</div>
@ -98,15 +115,23 @@ function mapStateToProps(state: State) {
yPos,
args,
} = state.contextMenu;
const {
channels,
} = state.chat;
const {
name,
uid,
} = args;
const {
fetchingApi: fetching,
} = state.fetching;
return {
xPos,
yPos,
channels,
name,
uid,
fetching,
};
}
@ -129,6 +154,9 @@ function mapDispatchToProps(dispatch) {
close() {
dispatch(hideContextMenu());
},
setChannel(channelId) {
dispatch(setChatChannel(channelId));
},
};
}

View File

@ -211,10 +211,7 @@ class PixelPlainterControls {
this.store,
this.viewport,
this.renderer,
[
this.clickTapStartCoords[0],
this.clickTapStartCoords[1],
],
this.clickTapStartCoords,
);
}, 800);
}

View File

@ -105,28 +105,22 @@ export class ChatProvider {
userId,
channelId,
channelArray,
notify = true,
) {
/*
* since UserId and ChannelId are primary keys,
* this will throw if already exists
*/
const relation = await UserChannel.create({
UserId: userId,
ChannelId: channelId,
}, {
const [, created] = await UserChannel.findOrCreate({
where: {
UserId: userId,
ChannelId: channelId,
},
raw: true,
});
console.log('HEREEEEE HHEEERRREEE');
console.log(relation);
webSockets.broadcastAddChatChannel(
userId,
channelId,
channelArray,
notify,
);
if (created) {
webSockets.broadcastAddChatChannel(
userId,
channelId,
channelArray,
);
}
}
userHasChannelAccess(user, cid, write = false) {
@ -134,7 +128,7 @@ export class ChatProvider {
if (!write || user.regUser) {
return true;
}
} else if (user.regUser && user.channelIds.includes(cid)) {
} else if (user.regUser && user.channels[cid]) {
return true;
}
return false;
@ -146,7 +140,7 @@ export class ChatProvider {
}
const channelArray = user.channels[cid];
if (channelArray && channelArray.length === 4) {
return user.channels[cid][4];
return user.channels[cid][3];
}
return null;
}

View File

@ -87,6 +87,10 @@ export const CHAT_CHANNELS = [
name: 'en',
}, {
name: 'int',
}, {
name: 'pol',
}, {
name: 'art',
},
];

View File

@ -30,11 +30,13 @@ const Message = Model.define('Message', {
Message.belongsTo(Channel, {
as: 'channel',
foreignKey: 'cid',
onDelete: 'cascade',
});
Message.belongsTo(RegUser, {
as: 'user',
foreignKey: 'uid',
onDelete: 'cascade',
});
export default Message;

View File

@ -64,10 +64,11 @@ const RegUser = Model.define('User', {
defaultValue: false,
},
blockDm: {
type: DataType.BOOLEAN,
// currently just blockDm
blocks: {
type: DataType.TINYINT,
allowNull: false,
defaultValue: false,
defaultValue: 0,
},
discordid: {
@ -120,6 +121,10 @@ const RegUser = Model.define('User', {
mcVerified(): boolean {
return this.verified & 0x02;
},
blockDm(): boolean {
return this.blocks & 0x01;
},
},
setterMethods: {
@ -133,6 +138,11 @@ const RegUser = Model.define('User', {
this.setDataValue('verified', val);
},
blockDm(num: boolean) {
const val = (num) ? (this.blocks | 0x01) : (this.blocks & ~0x01);
this.setDataValue('blocks', val);
},
password(value: string) {
if (value) this.setDataValue('password', generateHash(value));
},

View File

@ -29,7 +29,6 @@ class User {
this.id = id;
this.ip = ip;
this.channels = {};
this.channelIds = [];
this.blocked = [];
this.ipSub = getIPv6Subnet(ip);
this.wait = null;
@ -64,7 +63,6 @@ class User {
dmu1,
dmu2,
} = reguser.channel[i];
this.channelIds.push(id);
if (type === 1) {
/* in DMs:
* the name is the name of the other user
@ -72,19 +70,19 @@ class User {
*/
const name = (dmu1.id === this.id) ? dmu2.name : dmu1.name;
const dmu = (dmu1.id === this.id) ? dmu2.id : dmu1.id;
this.channels[id] = [
this.addChannel(id, [
name,
type,
lastTs,
dmu,
];
]);
} else {
const { name } = reguser.channel[i];
this.channels[id] = [
this.addChannel(id, [
name,
type,
lastTs,
];
]);
}
}
}
@ -99,6 +97,14 @@ class User {
}
}
addChannel(cid, channelArray) {
this.channels[cid] = channelArray;
}
removeChannel(cid) {
delete this.channels[cid];
}
getName() {
return (this.regUser) ? this.regUser.name : null;
}

View File

@ -54,7 +54,7 @@ export default function chat(
const channels = { ...state.channels };
const messages = { ...state.messages };
const keys = Object.keys(channels);
for (let i = 0; i < messages.length; i += 1) {
for (let i = 0; i < keys.length; i += 1) {
const cid = keys[i];
if (channels[cid][1] === 0) {
delete messages[cid];
@ -81,7 +81,7 @@ export default function chat(
*/
const channels = { ...state.channels };
const chanKeys = Object.keys(channels);
for (let i = 0; i < chanKeys; i += 1) {
for (let i = 0; i < chanKeys.length; i += 1) {
const cid = chanKeys[i];
if (channels[cid][1] === 1 && channels[cid][3] === userId) {
delete channels[cid];
@ -109,7 +109,10 @@ export default function chat(
case 'ADD_CHAT_CHANNEL': {
const { channel } = action;
console.log('adding channel', channel);
const [cid] = Object.keys(channel);
if (state.channels[cid]) {
return state;
}
return {
...state,
channels: {
@ -121,11 +124,17 @@ export default function chat(
case 'REMOVE_CHAT_CHANNEL': {
const { cid } = action;
if (!state.channels[cid]) {
return state;
}
const channels = { ...state.channels };
const messages = { ...state.messages };
delete messages[cid];
delete channels[cid];
return {
...state,
channels,
messages,
};
}

153
src/reducers/chatRead.js Normal file
View File

@ -0,0 +1,153 @@
/*
* local save state for chat stuff
*
* @flow
*/
import type { Action } from '../actions/types';
const TIME_DIFF_THREASHOLD = 15000;
export type ChatReadState = {
// channels that are muted
// [cid, cid2, ...]
mute: Array,
// timestamps of last read
// {cid: lastTs, ...}
readTs: Object,
// booleans if channel is unread
// {cid: unread, ...}
unread: Object,
// selected chat channel
chatChannel: number,
};
const initialState: ChatReadState = {
mute: [],
readTs: {},
unread: {},
chatChannel: 1,
};
export default function chatRead(
state: ModalState = initialState,
action: Action,
): ChatReadState {
switch (action.type) {
case 'RECEIVE_ME':
case 'LOGIN': {
const { channels } = action;
const cids = Object.keys(channels);
const readTs = {};
const unread = {};
for (let i = 0; i < cids.length; i += 1) {
const cid = cids[i];
if (!state.readTs[cid]) {
readTs[cid] = 0;
} else {
readTs[cid] = state.readTs[cid];
}
unread[cid] = (channels[cid][2] > readTs[cid]);
}
return {
...state,
readTs,
unread,
};
}
case 'SET_CHAT_CHANNEL': {
const { cid } = action;
return {
...state,
chatChannel: cid,
readTs: {
...state.readTs,
[cid]: Date.now() + TIME_DIFF_THREASHOLD,
},
unread: {
...state.unread,
[cid]: false,
},
};
}
case 'ADD_CHAT_CHANNEL': {
const [cid] = Object.keys(action.channel);
return {
...state,
readTs: {
...state.readTs,
[cid]: state.readTs[cid] || 0,
},
unread: {
...state.unread,
[cid]: true,
},
};
}
case 'REMOVE_CHAT_CHANNEL': {
const { cid } = action;
if (!state.readTs[cid]) {
return state;
}
const readTs = { ...state.readTs };
delete readTs[cid];
const unread = { ...state.unread };
delete unread[cid];
return {
...state,
readTs,
unread,
};
}
case 'RECEIVE_CHAT_MESSAGE': {
const { channel: cid } = action;
const { chatChannel } = state;
// eslint-disable-next-line eqeqeq
const readTs = (chatChannel == cid)
? {
...state.readTs,
// 15s treshold for desync
[cid]: Date.now() + TIME_DIFF_THREASHOLD,
} : state.readTs;
// eslint-disable-next-line eqeqeq
const unread = (chatChannel != cid)
? {
...state.unread,
[cid]: true,
} : state.unread;
return {
...state,
readTs,
unread,
};
}
case 'MUTE_CHAT_CHANNEL': {
const { cid } = action;
return {
...state,
mute: [
...state.mute,
cid,
],
};
}
case 'UNMUTE_CHAT_CHANNEL': {
const { cid } = action;
const mute = state.mute.filter((id) => (id !== cid));
return {
...state,
mute,
};
}
default:
return state;
}
}

View File

@ -15,10 +15,6 @@ export type GUIState = {
compactPalette: boolean,
paletteOpen: boolean,
menuOpen: boolean,
chatChannel: number,
// timestamps of last read post per channel
// { 1: Date.now() }
chatRead: {},
style: string,
};
@ -33,8 +29,6 @@ const initialState: GUIState = {
compactPalette: false,
paletteOpen: true,
menuOpen: false,
chatChannel: 1,
chatRead: {},
style: 'default',
};
@ -108,19 +102,6 @@ export default function gui(
};
}
case 'SET_CHAT_CHANNEL': {
const { cid } = action;
return {
...state,
chatChannel: cid,
chatRead: {
...state.chatRead,
cid: Date.now(),
},
};
}
case 'SELECT_COLOR': {
const {
compactPalette,
@ -145,43 +126,6 @@ export default function gui(
};
}
case 'RECEIVE_ME':
case 'LOGIN': {
const { channels } = action;
const cids = Object.keys(channels);
const chatRead = { ...state.chatRead };
for (let i = 0; i < cids.length; i += 1) {
const cid = cids[i];
chatRead[cid] = 0;
}
return {
...state,
chatRead,
};
}
case 'ADD_CHAT_CHANNEL': {
const [cid] = Object.keys(action.channel);
return {
...state,
chatRead: {
...state.chatRead,
[cid]: 0,
},
};
}
case 'REMOVE_CHAT_CHANNEL': {
const { cid } = action;
const chatRead = { ...state.chatRead };
delete chatRead[cid];
return {
...state,
chatRead,
};
}
case 'PLACE_PIXEL': {
let { pixelsPlaced } = state;
pixelsPlaced += 1;

View File

@ -9,6 +9,7 @@ import modal from './modal';
import user from './user';
import chat from './chat';
import contextMenu from './contextMenu';
import chatRead from './chatRead';
import fetching from './fetching';
import type { AudioState } from './audio';
@ -28,6 +29,7 @@ export type State = {
user: UserState,
chat: ChatState,
contextMenu: ContextMenuState,
chatRead: ChatReadState,
fetching: FetchingState,
};
@ -52,5 +54,6 @@ export default persistCombineReducers(config, {
user,
chat,
contextMenu,
chatRead,
fetching,
});

View File

@ -108,8 +108,8 @@ async function block(req: Request, res: Response) {
if (channel) {
const channelId = channel.id;
channel.destroy();
webSockets.broadcastRemoveChatChannel(user.id, channelId, false);
webSockets.broadcastRemoveChatChannel(userId, channelId, true);
webSockets.broadcastRemoveChatChannel(user.id, channelId);
webSockets.broadcastRemoveChatChannel(userId, channelId);
}
if (ret) {

View File

@ -45,10 +45,10 @@ async function blockdm(req: Request, res: Response) {
const channel = channels[i];
if (channel.type === 1) {
const channelId = channel.id;
channel.destroy();
const { dmu1id, dmu2id } = channel;
webSockets.broadcastRemoveChatChannel(dmu1id, channelId, true);
webSockets.broadcastRemoveChatChannel(dmu2id, channelId, true);
channel.destroy();
webSockets.broadcastRemoveChatChannel(dmu1id, channelId);
webSockets.broadcastRemoveChatChannel(dmu2id, channelId);
}
}

View File

@ -65,7 +65,7 @@ async function leaveChan(req: Request, res: Response) {
user.regUser.removeChannel(channel);
webSockets.broadcastRemoveChatChannel(user.id, channelId, false);
webSockets.broadcastRemoveChatChannel(user.id, channelId);
res.json({
status: 'ok',

View File

@ -47,12 +47,6 @@ async function startDm(req: Request, res: Response) {
const targetUser = await RegUser.findOne({
where: query,
attributes: [
'id',
'name',
'blockDm',
],
raw: true,
});
if (!targetUser) {
res.status(401);
@ -61,14 +55,15 @@ async function startDm(req: Request, res: Response) {
});
return;
}
userId = targetUser.id;
userName = targetUser.name;
if (targetUser.blockDm) {
res.status(401);
res.json({
errors: ['Target user doesn\'t allo DMs'],
errors: [`${userName} doesn't allow DMs`],
});
return;
}
userId = targetUser.id;
userName = targetUser.name;
/*
* check if blocked
@ -76,7 +71,7 @@ async function startDm(req: Request, res: Response) {
if (await isUserBlockedBy(user.id, userId)) {
res.status(401);
res.json({
errors: ['You are blocked by this user'],
errors: [`${userName} has blocked you.`],
});
return;
}
@ -106,10 +101,19 @@ async function startDm(req: Request, res: Response) {
raw: true,
});
const ChannelId = channel[0].id;
const curTime = Date.now();
const promises = [
ChatProvider.addUserToChannel(user.id, ChannelId, false),
ChatProvider.addUserToChannel(userId, ChannelId, true),
ChatProvider.addUserToChannel(
user.id,
ChannelId,
[userName, 1, curTime, userId],
),
ChatProvider.addUserToChannel(
userId,
ChannelId,
[user.getName(), 1, curTime, user.id],
),
];
await Promise.all(promises);

View File

@ -104,7 +104,7 @@ class SocketServer extends WebSocketEvents {
});
ws.on('message', (message) => {
if (typeof message === 'string') {
SocketServer.onTextMessage(message, ws);
this.onTextMessage(message, ws);
} else {
this.onBinaryMessage(message, ws);
}
@ -175,13 +175,19 @@ class SocketServer extends WebSocketEvents {
});
}
/*
* keep in mind that a user could
* be connected from multiple devices
*/
findWsByUserId(userId) {
const { clients } = this.wss;
for (let i = 0; i < clients.length; i += 1) {
const ws = clients[i];
const it = this.wss.clients.keys();
let client = it.next();
while (!client.done) {
const ws = client.value;
if (ws.user.id === userId && ws.readyState === WebSocket.OPEN) {
return ws;
}
client = it.next();
}
return null;
}
@ -190,35 +196,31 @@ class SocketServer extends WebSocketEvents {
userId: number,
channelId: number,
channelArray: Array,
notify: boolean,
) {
const ws = this.findWsByUserId(userId);
if (ws) {
ws.user.channels[channelId] = channelArray;
const text = JSON.stringify([
'addch', {
[channelId]: channelArray,
},
]);
if (notify) {
this.wss.clients.forEach((ws) => {
if (ws.user.id === userId && ws.readyState === WebSocket.OPEN) {
ws.user.addChannel(channelId, channelArray);
const text = JSON.stringify([
'addch', {
[channelId]: channelArray,
},
]);
ws.send(text);
}
}
});
}
broadcastRemoveChatChannel(
userId: number,
channelId: number,
notify: boolean,
) {
const ws = this.findWsByUserId(userId);
if (ws) {
delete ws.user.channels[channelId];
const text = JSON.stringify('remch', channelId);
if (notify) {
this.wss.clients.forEach((ws) => {
if (ws.user.id === userId && ws.readyState === WebSocket.OPEN) {
ws.user.removeChannel(channelId);
const text = JSON.stringify(['remch', channelId]);
ws.send(text);
}
}
});
}
broadcastPixelBuffer(canvasId: number, chunkid, data: Buffer) {
@ -286,7 +288,7 @@ class SocketServer extends WebSocketEvents {
webSockets.broadcastOnlineCounter(online);
}
static async onTextMessage(text, ws) {
async onTextMessage(text, ws) {
/*
* all client -> server text messages are
* chat messages in [message, channelId] format
@ -333,13 +335,11 @@ class SocketServer extends WebSocketEvents {
*/
const dmUserId = chatProvider.checkIfDm(user, channelId);
if (dmUserId) {
console.log('is dm');
const dmWs = this.findWsByUserId(dmUserId);
if (!dmWs
if (!dmWs
|| !chatProvider.userHasChannelAccess(dmWs.user, channelId)
) {
console.log('adding channel')
ChatProvider.addUserToChannel(
await ChatProvider.addUserToChannel(
dmUserId,
channelId,
[ws.name, 1, Date.now(), user.id],

View File

@ -28,14 +28,12 @@ class WebSocketEvents {
userId: number,
channelId: number,
channelArray: Array,
notify: boolean,
) {
}
broadcastRemoveChatChannel(
userId: number,
channelId: number,
notify: boolean,
) {
}

View File

@ -94,20 +94,17 @@ class WebSockets {
* @param userId numerical id of user
* @param channelId numerical id of chat channel
* @param channelArray array with channel info [name, type, lastTs]
* @param notify if user should get notified over websocket
* (i.e. false if the user already gets it via api response)
*/
broadcastAddChatChannel(
userId: number,
channelId: number,
channelArray: Array,
notify: boolean = true,
) {
this.listeners.forEach(
(listener) => listener.broadcastAddChatChannel(
userId,
channelId,
channelArray,
notify,
),
);
}
@ -116,19 +113,16 @@ class WebSockets {
* broadcast Removing chat channel from user
* @param userId numerical id of user
* @param channelId numerical id of chat channel
* @param notify if user should get notified over websocket
* (i.e. false if the user already gets it via api response)
*/
broadcastRemoveChatChannel(
userId: number,
channelId: number,
notify: boolean = true,
) {
this.listeners.forEach(
(listener) => listener.broadcastRemoveChatChannel(
userId,
channelId,
notify,
),
);
}

View File

@ -200,21 +200,31 @@ export default (store) => (next) => (action) => {
}
case 'RECEIVE_CHAT_MESSAGE': {
if (!chatNotify) break;
if (mute || !chatNotify) break;
const { isPing } = action;
const { chatChannel } = state.gui;
// eslint-disable-next-line eqeqeq
if (!isPing && action.channel != chatChannel) {
break;
}
const { isPing, channel } = action;
const { mute: muteCh, chatChannel } = state.chatRead;
if (muteCh.includes(channel)) break;
if (muteCh.includes(`${channel}`)) break;
const { channels } = state.chat;
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
oscillatorNode.frequency.setValueAtTime(310, context.currentTime);
const freq = (isPing) ? 540 : 355;
/*
* ping if user mention or
* message in DM channel that is not currently open
*/
const freq = (isPing
|| (
channels[channel]
&& channels[channel][1] === 1
// eslint-disable-next-line eqeqeq
&& channel != chatChannel
)
) ? 540 : 355;
oscillatorNode.frequency.exponentialRampToValueAtTime(
freq,
context.currentTime + 0.025,

View File

@ -19,7 +19,7 @@ export default (store) => (next) => (action) => {
}
case 'SET_NAME':
case 'LOGIN:':
case 'LOGIN':
case 'LOGOUT': {
ProtocolClient.reconnect();
break;

View File

@ -22,10 +22,14 @@ tr:nth-child(odd) {
color: #ff91a6;
}
.actionbuttons:hover, .menu > div:hover {
.actionbuttons:hover, .menu > div:hover, .channeldd, .contextmenu {
background: linear-gradient(160deg, #61dcea , #ffb1e1, #ecffec, #ffb1e1, #61dcea);
}
.chn, .chntype, .contextmenu > div {
background-color: #ebebeb80;
}
#chatbutton {
background: linear-gradient(135deg, orange , yellow, green, aqua, blue, violet);
}

View File

@ -35,6 +35,25 @@ tr:nth-child(even) {
border-radius: 8px;
}
.channeldd, .contextmenu {
background-color: #535356;
color: #efefef;
border-radius: 8px;
}
.chntop {
margin-top: 4px;
}
.chn, .chntype, .contextmenu > div {
background-color: #5f5f5f;
}
.chn.selected, .chn:hover, .chntype.selected, .chntype:hover,
.contextmenu > div:hover {
background-color: #404040;
}
.actionbuttons, .coorbox, .onlinebox, .cooldownbox, #historyselect {
background-color: rgba(59, 59, 59, 0.8);
color: #f4f4f4;

View File

@ -65,6 +65,20 @@ tr:nth-child(even) {
background-color: hsla(216, 4%, 74%, .3);
}
.channeldd, .contextmenu {
background-color: #535356;
color: #efefef;
}
.chn, .chntype, .contextmenu > div {
background-color: #5f5f5f;
}
.chn.selected, .chn:hover, .chntype.selected, .chntype:hover,
.contextmenu > div:hover {
background-color: #404040;
}
.modalinfo {
color: #ddd;
}

View File

@ -134,15 +134,20 @@ tr:nth-child(even) {
background-color: rgba(226, 226, 226);
border: solid black;
border-width: thin;
color: #212121;
box-shadow: 0 0 2px 2px rgba(0,0,0,.2);
}
.contextmenu > div {
border-width: thin;
margin: 2px;
height: 18px;
padding: 3px 2px 0px 0px;
background-color: #ebebeb;
border-top: thin solid #b1b1b2;
}
.contextmenu > div:hover {
background-color: white;
background-color: #c9c9c9;
cursor: pointer;
}
@ -155,29 +160,42 @@ tr:nth-child(even) {
}
.channelbtn {
background-color: #d0d0d0;
position: relative;
background-color: #ebebeb;
text-align: center;
border-style: solid;
border-width: thin;
border-radius: 4px;
}
.channelbtn:hover {
cursor: pointer;
background-color: white;
width: 50px;
height: 100%;
white-space: nowrap;
font-size: 14px;
overflow-x: hidden;
color: #212121;
}
.channeldd {
background-color: rgba(226, 226, 226);
color: #212121;
border: solid black;
border-width: thin;
width: 90px;
box-shadow: 0 0 2px 2px rgba(0,0,0,.2);
}
.channeldds {
height: 120px;
overflow-y: scroll;
overflow-y: auto;
overflow-x: hidden;
margin: 2px;
}
.chntop {
display: flex;
height: 24px;
border-bottom: solid thin;
}
.actionbuttons, .coorbox, .onlinebox, .cooldownbox, #palettebox {
position: fixed;
background-color: rgba(226, 226, 226, 0.80);
@ -224,10 +242,6 @@ tr:nth-child(even) {
}
#helpbutton {
left: 16px;
top: 221px;
}
#minecraftbutton {
left: 16px;
top: 180px;
}
@ -485,15 +499,43 @@ tr:nth-child(even) {
cursor: pointer;
}
.chn.selected, .chnunread {
font-weight: bold;
font-size: 17px;
.chn {
position: relative;
background-color: #ebebeb;
white-space: nowrap;
border-bottom: solid thin #b1b1b2;
padding: 1px;
font-size: 15px;
height: 22px;
}
.chn.selected, .chn:hover, .channelbtn.selected, .channelbtn:hover {
cursor: pointer;
background-color: #c9c9c9;
}
.chnunread {
position: absolute;
top: -1px;
right: 1px;
font-weight: bold;
font-size: 12px;
color: red;
}
.chntype {
position: relative;
flex: auto;
text-align: center;
background-color: #ebebeb;
border-left: solid thin #b1b1b2;
}
.chntype.selected, .chntype:hover {
cursor: pointer;
font-size: 110%;
background-color: #c9c9c9;
}
.usermessages {
font-size: 14px;
font-weight: 500;

View File

@ -1,7 +1,11 @@
.chatbox {
.chatbox, .channeldd, .contextmenu {
border-radius: 8px;
}
.chntop {
margin-top: 4px;
}
.actionbuttons, .coorbox, .onlinebox, .cooldownbox, #historyselect {
border-radius: 21px;
}