diff --git a/src/actions/types.js b/src/actions/types.js index 1c4bf4d..3769a4c 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -90,7 +90,7 @@ export type Action = minecraftname: string, blockDm: boolean, canvases: Object, - channels: Array, + channels: Object, blocked: Array, userlvl: number, } diff --git a/src/components/ChannelContextMenu.jsx b/src/components/ChannelContextMenu.jsx index d2e1b81..a12ea3e 100644 --- a/src/components/ChannelContextMenu.jsx +++ b/src/components/ChannelContextMenu.jsx @@ -4,7 +4,7 @@ */ import React, { - useRef, useEffect, useState, useLayoutEffect, + useRef, useEffect, } from 'react'; import { connect } from 'react-redux'; @@ -23,7 +23,6 @@ const UserContextMenu = ({ close, }) => { const wrapperRef = useRef(null); - const [channelArray, setChannelArray] = useState([]); useEffect(() => { const handleClickOutside = (event) => { @@ -42,20 +41,6 @@ const UserContextMenu = ({ }; }, [wrapperRef]); - useLayoutEffect(() => { - for (let i = 0; i < channels.length; i += 1) { - const chan = channels[i]; - /* - * [cid, name, type, lastMessage] - */ - // eslint-disable-next-line eqeqeq - if (chan[0] == cid) { - setChannelArray(chan); - break; - } - } - }, [channels.length]); - return (
✔✘ Mute
- {(channelArray[2] !== 0) + {(channels[cid][1] !== 0) && (
{ + useLayoutEffect(() => { if (show) { + if (channels[chatChannel]) { + const chType = (channels[chatChannel][1] === 1) ? 1 : 0; + setType(chType); + } document.addEventListener('mousedown', handleClickOutside); document.addEventListener('touchstart', handleClickOutside); } else { @@ -55,12 +59,10 @@ const ChannelDropDown = ({ }, [show]); useEffect(() => { - for (let i = 0; i < channels.length; i += 1) { - if (channels[i][0] === chatChannel) { - setChatChannelName(channels[i][1]); - } + if (channels[chatChannel]) { + setChatChannelName(channels[chatChannel][0]); } - }, [chatChannel, channels]); + }, [chatChannel]); return (
{ - channels.filter((ch) => { - const chType = ch[2]; + Object.keys(channels).filter((cid) => { + const chType = channels[cid][1]; if (type === 1 && chType === 1) { return true; } @@ -113,24 +115,27 @@ const ChannelDropDown = ({ return true; } return false; - }).map((ch) => ( -
setChannel(ch[0])} - style={(ch[0] === chatChannel) ? { - fontWeight: 'bold', - fontSize: 17, - } : null} - className={ - `chn${ - (ch[0] === chatChannel) ? ' selected' : '' - }${ - (chatRead[ch[0]] < ch[3]) ? ' unread' : '' - }` - } - > - {ch[1]} -
- )) + }).map((cid) => { + const [name,, lastTs] = channels[cid]; + console.log(`name ${name} lastTC ${lastTs} compare to ${chatRead[cid]}`); + return ( +
setChannel(cid)} + className={ + `chn${ + (cid === chatChannel) ? ' selected' : '' + }` + } + > + { + (chatRead[cid] < lastTs) ? ( + + ) : null + } + {name} +
+ ); + }) }
diff --git a/src/components/Chat.jsx b/src/components/Chat.jsx index 607c4dc..ad4c07e 100644 --- a/src/components/Chat.jsx +++ b/src/components/Chat.jsx @@ -114,16 +114,11 @@ const Chat = ({ * set channel to first available one */ useEffect(() => { - let i = 0; - while (i < channels.length) { - // eslint-disable-next-line eqeqeq - if (channels[i][0] == chatChannel) { - break; + if (!channels[chatChannel]) { + const cids = Object.keys(channels); + if (cids.length) { + setChannel(cids[0]); } - i += 1; - } - if (i && i === channels.length) { - setChannel(channels[0][0]); } }, [chatChannel, channels]); diff --git a/src/core/ChatMessageBuffer.js b/src/core/ChatMessageBuffer.js index 9e988ff..50fe2f8 100644 --- a/src/core/ChatMessageBuffer.js +++ b/src/core/ChatMessageBuffer.js @@ -4,9 +4,10 @@ * * @flow */ +import Sequelize from 'sequelize'; import logger from './logger'; -import { RegUser, Message } from '../data/models'; +import { RegUser, Message, Channel } from '../data/models'; const MAX_BUFFER_TIME = 120000; @@ -62,6 +63,13 @@ class ChatMessageBuffer { uid, message, }); + Channel.update({ + lastMessage: Sequelize.literal('CURRENT_TIMESTAMP'), + }, { + where: { + id: cid, + }, + }); const messages = this.buffer.get(cid); if (messages) { messages.push([ diff --git a/src/core/ChatProvider.js b/src/core/ChatProvider.js index 52a303d..1f26e98 100644 --- a/src/core/ChatProvider.js +++ b/src/core/ChatProvider.js @@ -10,8 +10,7 @@ import { CHAT_CHANNELS, EVENT_USER_NAME, INFO_USER_NAME } from './constants'; export class ChatProvider { constructor() { - this.defaultChannels = []; - this.defaultChannelIds = []; + this.defaultChannels = {}; this.enChannelId = 0; this.intChannelId = 0; this.infoUserId = 1; @@ -48,8 +47,6 @@ export class ChatProvider { async initialize() { // find or create default channels - this.defaultChannels.length = 0; - this.defaultChannelIds.length = 0; for (let i = 0; i < CHAT_CHANNELS.length; i += 1) { const { name } = CHAT_CHANNELS[i]; // eslint-disable-next-line no-await-in-loop @@ -66,13 +63,11 @@ export class ChatProvider { if (name === 'en') { this.enChannelId = id; } - this.defaultChannels.push([ - id, + this.defaultChannels[id] = [ name, type, lastTs, - ]); - this.defaultChannelIds.push(id); + ]; } // find or create default users let name = INFO_USER_NAME; @@ -106,7 +101,7 @@ export class ChatProvider { } userHasChannelAccess(user, cid, write = false) { - if (this.defaultChannelIds.includes(cid)) { + if (this.defaultChannels[cid]) { if (!write || user.regUser) { return true; } diff --git a/src/core/me.js b/src/core/me.js index 4f1dbf7..2a3ae05 100644 --- a/src/core/me.js +++ b/src/core/me.js @@ -31,10 +31,10 @@ export default async function getMe(user) { delete userdata.mcVerified; userdata.canvases = canvases; - userdata.channels = [ + userdata.channels = { ...chatProvider.defaultChannels, ...userdata.channels, - ]; + }; return userdata; } diff --git a/src/data/models/User.js b/src/data/models/User.js index d482be1..8b6ee1b 100644 --- a/src/data/models/User.js +++ b/src/data/models/User.js @@ -28,7 +28,7 @@ class User { // id should stay null if unregistered this.id = id; this.ip = ip; - this.channels = []; + this.channels = {}; this.channelIds = []; this.blocked = []; this.ipSub = getIPv6Subnet(ip); @@ -70,12 +70,11 @@ class User { name = (dmu1.id === this.id) ? dmu2.name : dmu1.name; } this.channelIds.push(id); - this.channels.push([ - id, + this.channels[id] = [ name, type, lastTs, - ]); + ]; } } if (reguser.blocked) { diff --git a/src/reducers/chat.js b/src/reducers/chat.js index 19262db..abb7bb6 100644 --- a/src/reducers/chat.js +++ b/src/reducers/chat.js @@ -6,7 +6,21 @@ import type { Action } from '../actions/types'; export type ChatState = { inputMessage: string, - // [[cid, name, type, lastMessage], [cid2, name2, type2, lastMessage2],...] + /* + * { + * cid: [ + * name, + * type, + * lastTs, + * ], + * cid2: [ + * name, + * type, + * lastTs, + * ], + * ... + * } + */ channels: Array, // [[uId, userName], [userId2, userName2],...] blocked: Array, @@ -16,7 +30,7 @@ export type ChatState = { const initialState: ChatState = { inputMessage: '', - channels: [], + channels: {}, blocked: [], messages: {}, }; @@ -56,22 +70,19 @@ export default function chat( case 'ADD_CHAT_CHANNEL': { const { channel } = action; - const cid = channel[0]; - const channels = state.channels - .filter((ch) => (ch[0] !== cid)); - channels.push(channel); return { ...state, - channels, + channels: { + ...state.channels, + ...channel, + }, }; } case 'REMOVE_CHAT_CHANNEL': { const { cid } = action; - const channels = state.channels.filter( - // eslint-disable-next-line eqeqeq - (chan) => (chan[0] != cid), - ); + const channels = { ...state.channels }; + delete channels[cid]; return { ...state, channels, @@ -106,7 +117,7 @@ export default function chat( const { name, text, country, channel, user, } = action; - if (!state.messages[channel]) { + if (!state.messages[channel] || !state.channels[channel]) { return state; } const messages = { @@ -119,8 +130,19 @@ export default function chat( if (messages[channel].length > MAX_CHAT_MESSAGES) { messages[channel].shift(); } + + /* + * update timestamp of last message + */ + const channelArray = [...state.channels[channel]]; + channelArray[2] = Date.now(); + return { ...state, + channels: { + ...state.channels, + [channel]: channelArray, + }, messages, }; } diff --git a/src/reducers/gui.js b/src/reducers/gui.js index cf80355..7294793 100644 --- a/src/reducers/gui.js +++ b/src/reducers/gui.js @@ -109,9 +109,15 @@ export default function gui( } case 'SET_CHAT_CHANNEL': { + const { cid } = action; + return { ...state, - chatChannel: action.cid, + chatChannel: cid, + chatRead: { + ...state.chatRead, + cid: Date.now(), + } }; } @@ -139,6 +145,42 @@ export default function gui( }; } + case 'RECEIVE_ME': { + 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; diff --git a/src/routes/api/startdm.js b/src/routes/api/startdm.js index 7ddb2e8..a053b68 100644 --- a/src/routes/api/startdm.js +++ b/src/routes/api/startdm.js @@ -105,7 +105,6 @@ async function startDm(req: Request, res: Response) { raw: true, }); const ChannelId = channel[0].id; - const { lastMessage } = channel[0]; const promises = [ UserChannel.findOrCreate({ @@ -127,12 +126,13 @@ async function startDm(req: Request, res: Response) { // TODO: inform websocket to add channelId to user res.json({ - channel: [ - ChannelId, - userName, - 1, - lastMessage, - ], + channel: { + [ChannelId]: [ + userName, + 1, + Date.now(), + ], + }, }); } diff --git a/src/styles/default.css b/src/styles/default.css index 1a29016..31aeaf1 100644 --- a/src/styles/default.css +++ b/src/styles/default.css @@ -485,6 +485,15 @@ tr:nth-child(even) { cursor: pointer; } +.chn.selected, .chnunread { + font-weight: bold; + font-size: 17px; +} + +.chnunread { + color: red; +} + .usermessages { font-size: 14px; font-weight: 500; diff --git a/src/web.js b/src/web.js index 9baaec0..5b18aad 100644 --- a/src/web.js +++ b/src/web.js @@ -194,7 +194,7 @@ app.get('/', async (req, res) => { // ip config // ----------------------------------------------------------------------------- // use this if models changed: -const promise = models.sync({ alter: { drop: true } }) +const promise = models.sync({ alter: { drop: false } }) // const promise = models.sync() .catch((err) => logger.error(err.stack)); promise.then(() => {