render new message indicator

This commit is contained in:
Roland Osborne 2022-07-11 00:19:59 -07:00
parent c2783fe693
commit 9e3074132d
9 changed files with 170 additions and 53 deletions

View File

@ -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() {
<ArticleContextProvider>
<ProfileContextProvider>
<AccountContextProvider>
<AppContextProvider>
<div style={{ position: 'absolute', width: '100vw', height: '100vh', backgroundColor: '#8fbea7' }}>
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
</div>
<div style={{ position: 'absolute', width: '100vw', height: '100vh' }}>
<Router>
<Routes>
<Route path="/" element={ <Home /> } />
<Route path="/login" element={ <Login /> } />
<Route path="/admin" element={ <Admin /> } />
<Route path="/create" element={ <Create /> } />
<Route path="/user" element={ <User /> }>
<Route path="profile" element={<Profile />} />
<Route path="contact/:guid" element={<Contact />} />
<Route path="conversation/:cardId/:channelId" element={
<ConversationContextProvider>
<Conversation />
</ConversationContextProvider>
} />
<Route path="conversation/:channelId" element={
<ConversationContextProvider>
<Conversation />
</ConversationContextProvider>
} />
</Route>
</Routes>
</Router>
</div>
</AppContextProvider>
<StoreContextProvider>
<AppContextProvider>
<div style={{ position: 'absolute', width: '100vw', height: '100vh', backgroundColor: '#8fbea7' }}>
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
</div>
<div style={{ position: 'absolute', width: '100vw', height: '100vh' }}>
<Router>
<Routes>
<Route path="/" element={ <Home /> } />
<Route path="/login" element={ <Login /> } />
<Route path="/admin" element={ <Admin /> } />
<Route path="/create" element={ <Create /> } />
<Route path="/user" element={ <User /> }>
<Route path="profile" element={<Profile />} />
<Route path="contact/:guid" element={<Contact />} />
<Route path="conversation/:cardId/:channelId" element={
<ConversationContextProvider>
<Conversation />
</ConversationContextProvider>
} />
<Route path="conversation/:channelId" element={
<ConversationContextProvider>
<Conversation />
</ConversationContextProvider>
} />
</Route>
</Routes>
</Router>
</div>
</AppContextProvider>
</StoreContextProvider>
</AccountContextProvider>
</ProfileContextProvider>
</ArticleContextProvider>

View File

@ -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 };

View File

@ -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 (
<ChannelItemWrapper onClick={() => onSelect()}>
<ChannelLogo item={item} />
{state.updated && (
<Marker />
)}
<ChannelLabel item={item} />
</ChannelItemWrapper>
)

View File

@ -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;
`

View File

@ -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 }));
}

View File

@ -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 (
<StoreContext.Provider value={{ state, actions }}>
{children}
</StoreContext.Provider>
);
}

View File

@ -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();
},
}

View File

@ -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();

View File

@ -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 }
}