From 9e3074132d570d798e9f6441d53b251533066dc6 Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Mon, 11 Jul 2022 00:19:59 -0700 Subject: [PATCH] render new message indicator --- net/web/src/App.js | 61 +++++++++--------- .../User/Conversation/useConversation.hook.js | 9 ++- .../Channels/ChannelItem/ChannelItem.jsx | 7 ++- .../ChannelItem/ChannelItem.styled.js | 6 ++ .../ChannelItem/useChannelItem.hook.jsx | 16 ++++- net/web/src/context/StoreContext.js | 14 +++++ net/web/src/context/useAppContext.hook.js | 3 + .../context/useConversationContext.hook.js | 63 +++++++++++++------ net/web/src/context/useStoreContext.hook.js | 44 +++++++++++++ 9 files changed, 170 insertions(+), 53 deletions(-) create mode 100644 net/web/src/context/StoreContext.js create mode 100644 net/web/src/context/useStoreContext.hook.js diff --git a/net/web/src/App.js b/net/web/src/App.js index c7d34b5d..95ee4030 100644 --- a/net/web/src/App.js +++ b/net/web/src/App.js @@ -7,6 +7,7 @@ import { GroupContextProvider } from 'context/GroupContext'; import { CardContextProvider } from 'context/CardContext'; import { ChannelContextProvider } from 'context/ChannelContext'; import { ConversationContextProvider } from 'context/ConversationContext'; +import { StoreContextProvider } from 'context/StoreContext'; import { Home } from './Home/Home'; import { Admin } from './Admin/Admin'; import { Login } from './Login/Login'; @@ -27,35 +28,37 @@ function App() { - -
- -
-
- - - } /> - } /> - } /> - } /> - }> - } /> - } /> - - - - } /> - - - - } /> - - - -
-
+ + +
+ +
+
+ + + } /> + } /> + } /> + } /> + }> + } /> + } /> + + + + } /> + + + + } /> + + + +
+
+
diff --git a/net/web/src/User/Conversation/useConversation.hook.js b/net/web/src/User/Conversation/useConversation.hook.js index f83d553b..1b50ee1f 100644 --- a/net/web/src/User/Conversation/useConversation.hook.js +++ b/net/web/src/User/Conversation/useConversation.hook.js @@ -1,8 +1,7 @@ import { useContext, useState, useEffect, useRef } from 'react'; import { useNavigate, useLocation, useParams } from "react-router-dom"; import { ConversationContext } from 'context/ConversationContext'; -import { CardContext } from 'context/CardContext'; -import { ChannelContext } from 'context/ChannelContext'; +import { StoreContext } from 'context/StoreContext'; export function useConversation() { @@ -18,6 +17,7 @@ export function useConversation() { const { cardId, channelId } = useParams(); const navigate = useNavigate(); const conversation = useContext(ConversationContext); + const store = useContext(StoreContext); const updateState = (value) => { setState((s) => ({ ...s, ...value })); @@ -62,6 +62,11 @@ export function useConversation() { members: conversation.state.members, topics, }); + if (conversation.state.init) { + const channel = conversation.state.channelId; + const card = conversation.state.cardId; + store.actions.setValue(`${channel}::${card}`, conversation.state.revision); + } }, [conversation]); return { state, actions }; diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.jsx b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.jsx index f6fe4f5d..9dd8cb1a 100644 --- a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.jsx +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.jsx @@ -1,12 +1,12 @@ import React, { useEffect, useState } from 'react' import { useChannelItem } from './useChannelItem.hook'; -import { ChannelItemWrapper } from './ChannelItem.styled'; +import { ChannelItemWrapper, Marker } from './ChannelItem.styled'; import { ChannelLogo } from './ChannelLogo/ChannelLogo'; import { ChannelLabel } from './ChannelLabel/ChannelLabel'; export function ChannelItem({ item }) { - let { state, actions } = useChannelItem(); + let { state, actions } = useChannelItem(item); const onSelect = () => { actions.select(item); @@ -15,6 +15,9 @@ export function ChannelItem({ item }) { return ( onSelect()}> + {state.updated && ( + + )} ) diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.styled.js b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.styled.js index fafffe8f..17d2116a 100644 --- a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.styled.js +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.styled.js @@ -1,5 +1,6 @@ import styled from 'styled-components'; import { List } from 'antd'; +import { StarTwoTone } from '@ant-design/icons'; export const ChannelItemWrapper = styled(List.Item)` padding-left: 16px; @@ -16,3 +17,8 @@ export const ChannelItemWrapper = styled(List.Item)` } `; +export const Marker = styled(StarTwoTone)` + position: relative; + left: -16px; + top: 8px; +` diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/useChannelItem.hook.jsx b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/useChannelItem.hook.jsx index eede3d5e..c792b329 100644 --- a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/useChannelItem.hook.jsx +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/useChannelItem.hook.jsx @@ -1,11 +1,25 @@ import { useContext, useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import { StoreContext } from 'context/StoreContext'; -export function useChannelItem() { +export function useChannelItem(item) { const [state, setState] = useState({ + updated: false, }); + const store = useContext(StoreContext); + + useEffect(() => { + let key = `${item.id}::${item.cardId}`; + if (store.state[key] == item.revision) { + updateState({ updated: false }); + } + else { + updateState({ updated: true }); + } + }, [store]); + const updateState = (value) => { setState((s) => ({ ...s, ...value })); } diff --git a/net/web/src/context/StoreContext.js b/net/web/src/context/StoreContext.js new file mode 100644 index 00000000..02bcdaf8 --- /dev/null +++ b/net/web/src/context/StoreContext.js @@ -0,0 +1,14 @@ +import { createContext } from 'react'; +import { useStoreContext } from './useStoreContext.hook'; + +export const StoreContext = createContext({}); + +export function StoreContextProvider({ children }) { + const { state, actions } = useStoreContext(); + return ( + + {children} + + ); +} + diff --git a/net/web/src/context/useAppContext.hook.js b/net/web/src/context/useAppContext.hook.js index 0c2d5f27..b6c860cc 100644 --- a/net/web/src/context/useAppContext.hook.js +++ b/net/web/src/context/useAppContext.hook.js @@ -10,6 +10,7 @@ import { ArticleContext } from './ArticleContext'; import { GroupContext } from './GroupContext'; import { CardContext } from './CardContext'; import { ChannelContext } from './ChannelContext'; +import { StoreContext } from './StoreContext'; async function appCreate(username, password, token, updateState, setWebsocket) { await addAccount(username, password, token); @@ -58,6 +59,7 @@ export function useAppContext() { }) } + const storeContext = useContext(StoreContext); const accountContext = useContext(AccountContext); const profileContext = useContext(ProfileContext); const channelContext = useContext(ChannelContext); @@ -79,6 +81,7 @@ export function useAppContext() { const userActions = { logout: () => { appLogout(updateState, clearWebsocket); + storeContext.actions.clear(); resetData(); }, } diff --git a/net/web/src/context/useConversationContext.hook.js b/net/web/src/context/useConversationContext.hook.js index a37a01f4..570f56ee 100644 --- a/net/web/src/context/useConversationContext.hook.js +++ b/net/web/src/context/useConversationContext.hook.js @@ -15,6 +15,7 @@ export function useConversationContext() { contacts: null, members: new Set(), topics: new Map(), + revision: null, }); const EVENT_OPEN = 1; @@ -44,6 +45,10 @@ export function useConversationContext() { } const getSubject = (conversation) => { + if (!conversation) { + return null; + } + try { let subject = JSON.parse(conversation.data.channelDetail.data).subject; if (subject) { @@ -56,6 +61,10 @@ export function useConversationContext() { } const getContacts = (conversation) => { + if (!conversation) { + return null; + } + let members = []; if (conversation.guid) { members.push(card.actions.getCardByGuid(conversation.guid)?.data?.cardProfile?.handle); @@ -70,6 +79,9 @@ export function useConversationContext() { } const getMembers = (conversation) => { + if (!conversation) { + return null; + } let members = new Set(); if (conversation.guid) { members.add(conversation.guid); @@ -174,23 +186,26 @@ export function useConversationContext() { if (channelView.current.init && deltaRevision != channelView.current.revision) { let delta = await getTopicDelta(channelView.current.revision, null, channelView.current.begin, null); await setTopicDelta(delta.topics, curView); - channelView.current.revision = delta.revision + channelView.current.revision = delta.revision; } } - let chan = getChannel(); - let subject = getSubject(chan); - let contacts = getContacts(chan); - let members = getMembers(chan); - updateState({ - init: true, - subject, - contacts, - members, - topics: topics.current, - }); + + if (curView == view.current) { + let chan = getChannel(); + let subject = getSubject(chan); + let contacts = getContacts(chan); + let members = getMembers(chan); + updateState({ + init: true, + subject, + contacts, + members, + topics: topics.current, + revision: channelView.current.revision, + }); + } } catch (err) { - console.log(err); if (!channelView.current.error) { window.alert("This converstaion failed to update"); channelView.current.error = true; @@ -199,7 +214,6 @@ export function useConversationContext() { } const updateConversation = async () => { - if (!card.state.init || !channel.state.init) { return; } @@ -208,11 +222,21 @@ export function useConversationContext() { serialize.current++; while (events.current.length > 0) { - await setTopics(events.current[0]); - events.current.shift(); + + // collapse updates + while (events.current.length > 1) { + if(events.current[0].type == EVENT_UPDATE && events.current[1].type == EVENT_UPDATE) { + events.current.shift(); + } + else { + break; + } + } + + const ev = events.current.shift(); + await setTopics(ev); } updateState({ loading: false }); - serialize.current--; } }; @@ -224,10 +248,11 @@ export function useConversationContext() { const actions = { setConversationId: (cardId, channelId) => { + view.current += 1; - updateState({ loading: true }); + updateState({ init: false, loading: true }); events.current = [{ type: EVENT_OPEN, data: { cardId, channelId }}]; - updateState({ init: false, subject: null, cardId, channelId, topics: new Map() }); + updateState({ subject: null, cardId, channelId, topics: new Map() }); topics.current = new Map(); updateConversation(); diff --git a/net/web/src/context/useStoreContext.hook.js b/net/web/src/context/useStoreContext.hook.js new file mode 100644 index 00000000..bd0c5f89 --- /dev/null +++ b/net/web/src/context/useStoreContext.hook.js @@ -0,0 +1,44 @@ +import { useEffect, useState, useRef, useContext } from 'react'; + +export function useStoreContext() { + + const [state, setState] = useState({}); + + const resetState = () => { + setState((s) => { + localStorage.setItem('store', JSON.stringify({})); + return {} + }); + }; + + const updateState = (value) => { + setState((s) => { + const store = { ...s, ...value }; + localStorage.setItem('store', JSON.stringify(store)); + return store; + }); + }; + + useEffect(() => { + const store = localStorage.getItem('store'); + if (store != null) { + updateState({ ...JSON.parse(store) }); + } + }, []); + + const actions = { + clear: () => { + resetState(); + }, + setValue: (key, value) => { + updateState({ [key]: value }); + }, + getValue: (key) => { + return state[key]; + } + } + + return { state, actions } +} + +