mirror of
https://github.com/balzack/databag.git
synced 2025-02-16 05:29:15 +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 { CardContextProvider } from 'context/CardContext';
|
||||||
import { ChannelContextProvider } from 'context/ChannelContext';
|
import { ChannelContextProvider } from 'context/ChannelContext';
|
||||||
import { ConversationContextProvider } from 'context/ConversationContext';
|
import { ConversationContextProvider } from 'context/ConversationContext';
|
||||||
|
import { StoreContextProvider } from 'context/StoreContext';
|
||||||
import { Home } from './Home/Home';
|
import { Home } from './Home/Home';
|
||||||
import { Admin } from './Admin/Admin';
|
import { Admin } from './Admin/Admin';
|
||||||
import { Login } from './Login/Login';
|
import { Login } from './Login/Login';
|
||||||
@ -27,35 +28,37 @@ function App() {
|
|||||||
<ArticleContextProvider>
|
<ArticleContextProvider>
|
||||||
<ProfileContextProvider>
|
<ProfileContextProvider>
|
||||||
<AccountContextProvider>
|
<AccountContextProvider>
|
||||||
<AppContextProvider>
|
<StoreContextProvider>
|
||||||
<div style={{ position: 'absolute', width: '100vw', height: '100vh', backgroundColor: '#8fbea7' }}>
|
<AppContextProvider>
|
||||||
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
|
<div style={{ position: 'absolute', width: '100vw', height: '100vh', backgroundColor: '#8fbea7' }}>
|
||||||
</div>
|
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
|
||||||
<div style={{ position: 'absolute', width: '100vw', height: '100vh' }}>
|
</div>
|
||||||
<Router>
|
<div style={{ position: 'absolute', width: '100vw', height: '100vh' }}>
|
||||||
<Routes>
|
<Router>
|
||||||
<Route path="/" element={ <Home /> } />
|
<Routes>
|
||||||
<Route path="/login" element={ <Login /> } />
|
<Route path="/" element={ <Home /> } />
|
||||||
<Route path="/admin" element={ <Admin /> } />
|
<Route path="/login" element={ <Login /> } />
|
||||||
<Route path="/create" element={ <Create /> } />
|
<Route path="/admin" element={ <Admin /> } />
|
||||||
<Route path="/user" element={ <User /> }>
|
<Route path="/create" element={ <Create /> } />
|
||||||
<Route path="profile" element={<Profile />} />
|
<Route path="/user" element={ <User /> }>
|
||||||
<Route path="contact/:guid" element={<Contact />} />
|
<Route path="profile" element={<Profile />} />
|
||||||
<Route path="conversation/:cardId/:channelId" element={
|
<Route path="contact/:guid" element={<Contact />} />
|
||||||
<ConversationContextProvider>
|
<Route path="conversation/:cardId/:channelId" element={
|
||||||
<Conversation />
|
<ConversationContextProvider>
|
||||||
</ConversationContextProvider>
|
<Conversation />
|
||||||
} />
|
</ConversationContextProvider>
|
||||||
<Route path="conversation/:channelId" element={
|
} />
|
||||||
<ConversationContextProvider>
|
<Route path="conversation/:channelId" element={
|
||||||
<Conversation />
|
<ConversationContextProvider>
|
||||||
</ConversationContextProvider>
|
<Conversation />
|
||||||
} />
|
</ConversationContextProvider>
|
||||||
</Route>
|
} />
|
||||||
</Routes>
|
</Route>
|
||||||
</Router>
|
</Routes>
|
||||||
</div>
|
</Router>
|
||||||
</AppContextProvider>
|
</div>
|
||||||
|
</AppContextProvider>
|
||||||
|
</StoreContextProvider>
|
||||||
</AccountContextProvider>
|
</AccountContextProvider>
|
||||||
</ProfileContextProvider>
|
</ProfileContextProvider>
|
||||||
</ArticleContextProvider>
|
</ArticleContextProvider>
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { useContext, useState, useEffect, useRef } from 'react';
|
import { useContext, useState, useEffect, useRef } from 'react';
|
||||||
import { useNavigate, useLocation, useParams } from "react-router-dom";
|
import { useNavigate, useLocation, useParams } from "react-router-dom";
|
||||||
import { ConversationContext } from 'context/ConversationContext';
|
import { ConversationContext } from 'context/ConversationContext';
|
||||||
import { CardContext } from 'context/CardContext';
|
import { StoreContext } from 'context/StoreContext';
|
||||||
import { ChannelContext } from 'context/ChannelContext';
|
|
||||||
|
|
||||||
export function useConversation() {
|
export function useConversation() {
|
||||||
|
|
||||||
@ -18,6 +17,7 @@ export function useConversation() {
|
|||||||
const { cardId, channelId } = useParams();
|
const { cardId, channelId } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const conversation = useContext(ConversationContext);
|
const conversation = useContext(ConversationContext);
|
||||||
|
const store = useContext(StoreContext);
|
||||||
|
|
||||||
const updateState = (value) => {
|
const updateState = (value) => {
|
||||||
setState((s) => ({ ...s, ...value }));
|
setState((s) => ({ ...s, ...value }));
|
||||||
@ -62,6 +62,11 @@ export function useConversation() {
|
|||||||
members: conversation.state.members,
|
members: conversation.state.members,
|
||||||
topics,
|
topics,
|
||||||
});
|
});
|
||||||
|
if (conversation.state.init) {
|
||||||
|
const channel = conversation.state.channelId;
|
||||||
|
const card = conversation.state.cardId;
|
||||||
|
store.actions.setValue(`${channel}::${card}`, conversation.state.revision);
|
||||||
|
}
|
||||||
}, [conversation]);
|
}, [conversation]);
|
||||||
|
|
||||||
return { state, actions };
|
return { state, actions };
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useChannelItem } from './useChannelItem.hook';
|
import { useChannelItem } from './useChannelItem.hook';
|
||||||
import { ChannelItemWrapper } from './ChannelItem.styled';
|
import { ChannelItemWrapper, Marker } from './ChannelItem.styled';
|
||||||
import { ChannelLogo } from './ChannelLogo/ChannelLogo';
|
import { ChannelLogo } from './ChannelLogo/ChannelLogo';
|
||||||
import { ChannelLabel } from './ChannelLabel/ChannelLabel';
|
import { ChannelLabel } from './ChannelLabel/ChannelLabel';
|
||||||
|
|
||||||
export function ChannelItem({ item }) {
|
export function ChannelItem({ item }) {
|
||||||
|
|
||||||
let { state, actions } = useChannelItem();
|
let { state, actions } = useChannelItem(item);
|
||||||
|
|
||||||
const onSelect = () => {
|
const onSelect = () => {
|
||||||
actions.select(item);
|
actions.select(item);
|
||||||
@ -15,6 +15,9 @@ export function ChannelItem({ item }) {
|
|||||||
return (
|
return (
|
||||||
<ChannelItemWrapper onClick={() => onSelect()}>
|
<ChannelItemWrapper onClick={() => onSelect()}>
|
||||||
<ChannelLogo item={item} />
|
<ChannelLogo item={item} />
|
||||||
|
{state.updated && (
|
||||||
|
<Marker />
|
||||||
|
)}
|
||||||
<ChannelLabel item={item} />
|
<ChannelLabel item={item} />
|
||||||
</ChannelItemWrapper>
|
</ChannelItemWrapper>
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { List } from 'antd';
|
import { List } from 'antd';
|
||||||
|
import { StarTwoTone } from '@ant-design/icons';
|
||||||
|
|
||||||
export const ChannelItemWrapper = styled(List.Item)`
|
export const ChannelItemWrapper = styled(List.Item)`
|
||||||
padding-left: 16px;
|
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 { useContext, useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { StoreContext } from 'context/StoreContext';
|
||||||
|
|
||||||
export function useChannelItem() {
|
export function useChannelItem(item) {
|
||||||
|
|
||||||
const [state, setState] = useState({
|
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) => {
|
const updateState = (value) => {
|
||||||
setState((s) => ({ ...s, ...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 { GroupContext } from './GroupContext';
|
||||||
import { CardContext } from './CardContext';
|
import { CardContext } from './CardContext';
|
||||||
import { ChannelContext } from './ChannelContext';
|
import { ChannelContext } from './ChannelContext';
|
||||||
|
import { StoreContext } from './StoreContext';
|
||||||
|
|
||||||
async function appCreate(username, password, token, updateState, setWebsocket) {
|
async function appCreate(username, password, token, updateState, setWebsocket) {
|
||||||
await addAccount(username, password, token);
|
await addAccount(username, password, token);
|
||||||
@ -58,6 +59,7 @@ export function useAppContext() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const storeContext = useContext(StoreContext);
|
||||||
const accountContext = useContext(AccountContext);
|
const accountContext = useContext(AccountContext);
|
||||||
const profileContext = useContext(ProfileContext);
|
const profileContext = useContext(ProfileContext);
|
||||||
const channelContext = useContext(ChannelContext);
|
const channelContext = useContext(ChannelContext);
|
||||||
@ -79,6 +81,7 @@ export function useAppContext() {
|
|||||||
const userActions = {
|
const userActions = {
|
||||||
logout: () => {
|
logout: () => {
|
||||||
appLogout(updateState, clearWebsocket);
|
appLogout(updateState, clearWebsocket);
|
||||||
|
storeContext.actions.clear();
|
||||||
resetData();
|
resetData();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ export function useConversationContext() {
|
|||||||
contacts: null,
|
contacts: null,
|
||||||
members: new Set(),
|
members: new Set(),
|
||||||
topics: new Map(),
|
topics: new Map(),
|
||||||
|
revision: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const EVENT_OPEN = 1;
|
const EVENT_OPEN = 1;
|
||||||
@ -44,6 +45,10 @@ export function useConversationContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getSubject = (conversation) => {
|
const getSubject = (conversation) => {
|
||||||
|
if (!conversation) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let subject = JSON.parse(conversation.data.channelDetail.data).subject;
|
let subject = JSON.parse(conversation.data.channelDetail.data).subject;
|
||||||
if (subject) {
|
if (subject) {
|
||||||
@ -56,6 +61,10 @@ export function useConversationContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getContacts = (conversation) => {
|
const getContacts = (conversation) => {
|
||||||
|
if (!conversation) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let members = [];
|
let members = [];
|
||||||
if (conversation.guid) {
|
if (conversation.guid) {
|
||||||
members.push(card.actions.getCardByGuid(conversation.guid)?.data?.cardProfile?.handle);
|
members.push(card.actions.getCardByGuid(conversation.guid)?.data?.cardProfile?.handle);
|
||||||
@ -70,6 +79,9 @@ export function useConversationContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getMembers = (conversation) => {
|
const getMembers = (conversation) => {
|
||||||
|
if (!conversation) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
let members = new Set();
|
let members = new Set();
|
||||||
if (conversation.guid) {
|
if (conversation.guid) {
|
||||||
members.add(conversation.guid);
|
members.add(conversation.guid);
|
||||||
@ -174,23 +186,26 @@ export function useConversationContext() {
|
|||||||
if (channelView.current.init && deltaRevision != channelView.current.revision) {
|
if (channelView.current.init && deltaRevision != channelView.current.revision) {
|
||||||
let delta = await getTopicDelta(channelView.current.revision, null, channelView.current.begin, null);
|
let delta = await getTopicDelta(channelView.current.revision, null, channelView.current.begin, null);
|
||||||
await setTopicDelta(delta.topics, curView);
|
await setTopicDelta(delta.topics, curView);
|
||||||
channelView.current.revision = delta.revision
|
channelView.current.revision = delta.revision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let chan = getChannel();
|
|
||||||
let subject = getSubject(chan);
|
if (curView == view.current) {
|
||||||
let contacts = getContacts(chan);
|
let chan = getChannel();
|
||||||
let members = getMembers(chan);
|
let subject = getSubject(chan);
|
||||||
updateState({
|
let contacts = getContacts(chan);
|
||||||
init: true,
|
let members = getMembers(chan);
|
||||||
subject,
|
updateState({
|
||||||
contacts,
|
init: true,
|
||||||
members,
|
subject,
|
||||||
topics: topics.current,
|
contacts,
|
||||||
});
|
members,
|
||||||
|
topics: topics.current,
|
||||||
|
revision: channelView.current.revision,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.log(err);
|
|
||||||
if (!channelView.current.error) {
|
if (!channelView.current.error) {
|
||||||
window.alert("This converstaion failed to update");
|
window.alert("This converstaion failed to update");
|
||||||
channelView.current.error = true;
|
channelView.current.error = true;
|
||||||
@ -199,7 +214,6 @@ export function useConversationContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateConversation = async () => {
|
const updateConversation = async () => {
|
||||||
|
|
||||||
if (!card.state.init || !channel.state.init) {
|
if (!card.state.init || !channel.state.init) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -208,11 +222,21 @@ export function useConversationContext() {
|
|||||||
serialize.current++;
|
serialize.current++;
|
||||||
|
|
||||||
while (events.current.length > 0) {
|
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 });
|
updateState({ loading: false });
|
||||||
|
|
||||||
serialize.current--;
|
serialize.current--;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -224,10 +248,11 @@ export function useConversationContext() {
|
|||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
setConversationId: (cardId, channelId) => {
|
setConversationId: (cardId, channelId) => {
|
||||||
|
|
||||||
view.current += 1;
|
view.current += 1;
|
||||||
updateState({ loading: true });
|
updateState({ init: false, loading: true });
|
||||||
events.current = [{ type: EVENT_OPEN, data: { cardId, channelId }}];
|
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();
|
topics.current = new Map();
|
||||||
updateConversation();
|
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