mirror of
https://github.com/balzack/databag.git
synced 2025-02-14 12:39:17 +00:00
render new message indicator
This commit is contained in:
parent
c2783fe693
commit
9e3074132d
@ -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>
|
||||
|
@ -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 };
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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;
|
||||
`
|
||||
|
@ -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 }));
|
||||
}
|
||||
|
14
net/web/src/context/StoreContext.js
Normal file
14
net/web/src/context/StoreContext.js
Normal 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>
|
||||
);
|
||||
}
|
||||
|
@ -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();
|
||||
},
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
44
net/web/src/context/useStoreContext.hook.js
Normal file
44
net/web/src/context/useStoreContext.hook.js
Normal 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 }
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user