import { useEffect, useState, useRef, useContext } from 'react'; import { CardContext } from 'context/CardContext'; import { ChannelContext } from 'context/ChannelContext'; export function useConversationContext() { const COUNT = 32; const [state, setState] = useState({ offsync: false, topics: new Map(), card: null, channel: null, topicRevision: null, }); const card = useContext(CardContext); const channel = useContext(ChannelContext); const reset = useRef(false); const loadMore = useRef(false); const force = useRef(false); const syncing = useRef(false); const marker = useRef(null); const setTopicRevision = useRef(null); const curTopicRevision = useRef(null); const setDetailRevision = useRef(null); const curDetailRevision = useRef(null); const conversationId = useRef(null); const topics = useRef(new Map()); const updateState = (value) => { setState((s) => ({ ...s, ...value })) } const getTopicDelta = async (cardId, channelId, revision, count, begin, end) => { if (cardId) { return await card.actions.getTopics(cardId, channelId, revision, count, begin, end); } return await channel.actions.getTopics(channelId, revision, count, begin, end); } const getTopic = async (cardId, channelId, topicId) => { if (cardId) { return await card.actions.getTopic(cardId, channelId, topicId); } return await channel.actions.getTopic(channelId, topicId); } const removeChannel = async (cardId, channelId) => { if (cardId) { await card.actions.removeChannel(cardId, channelId); await card.actions.resync(); } else { await channel.actions.removeChannel(channelId); await channel.actions.resync(); } }; const setChannelSubject = async (cardId, channelId, type, subject) => { if (cardId) { console.log('cannot update channel subject'); } else { await channel.actions.setChannelSubject(channelId, type, subject); } } const setChannelCard = async (cardId, channelId, id) => { if (cardId) { console.log('cannot update channel card'); } else { await channel.actions.setChannelCard(channelId, id); await channel.actions.resync(); } } const clearChannelCard = async (cardId, channelId, id) => { if (cardId) { console.log('cannot update channel card'); } else { await channel.actions.clearChannelCard(channelId, id); await channel.actions.resync(); } }; const addTopic = async (cardId, channelId, type, message, files) => { if (cardId) { await card.actions.addTopic(cardId, channelId, type, message, files); } else { await channel.actions.addTopic(channelId, type, message, files); } resync(); }; const removeTopic = async (cardId, channelId, topicId) => { if (cardId) { await card.actions.removeTopic(cardId, channelId, topicId); } else { await channel.actions.removeTopic(channelId, topicId); } await resync(); }; const setTopicSubject = async (cardId, channelId, topicId, type, subject) => { if (cardId) { await card.actions.setTopicSubject(cardId, channelId, topicId, type, subject); } else { await channel.actions.setTopicSubject(channelId, topicId, type, subject); } await resync(); }; const getTopicAssetUrl = (cardId, channelId, topicId, assetId) => { if (cardId) { return card.actions.getTopicAssetUrl(cardId, channelId, topicId, assetId); } else { return channel.actions.getTopicAssetUrl(channelId, topicId, assetId); } }; const setChannelRevision = (cardId, channelId) => { let setChannel; if (cardId) { const setCard = card.state.cards.get(cardId); setChannel = setCard?.channels.get(channelId); } else { setChannel = channel.state.channels.get(channelId); } if (setChannel) { const { topicRevision, detailRevision } = setChannel.data; if (curTopicRevision.current !== topicRevision || curDetailRevision.current !== detailRevision) { curTopicRevision.current = topicRevision; curDetailRevision.current = detailRevision; } } else { console.log('conversation not found'); } } const resync = async () => { try { force.current = true; await sync(); } catch (err) { console.log(err); } }; const sync = async () => { if (!syncing.current && (reset.current || force.current || loadMore.current || setDetailRevision.current !== curDetailRevision.current || setTopicRevision.current !== curTopicRevision.current)) { const more = loadMore.current; const update = force.current; const topicRevision = more ? setTopicRevision.current : curTopicRevision.current; const detailRevision = curDetailRevision.current; syncing.current = true; force.current = false; loadMore.current = false; if (reset.current) { reset.current = false; marker.current = null; setTopicRevision.current = null; setDetailRevision.current = null; topics.current = new Map(); updateState({ offsync: false, channel: null, topics: new Map() }); } if (conversationId.current) { const { cardId, channelId } = conversationId.current; // sync channel details if (setDetailRevision.current !== detailRevision) { let channelSync; let cardSync; if (cardId) { cardSync = card.state.cards.get(cardId); channelSync = cardSync?.channels.get(channelId); } else { channelSync = channel.state.channels.get(channelId); } if (channelSync) { setDetailRevision.current = detailRevision; updateState({ card: cardSync, channel: channelSync }); } else { syncing.current = false; console.log("converstaion not found"); return; } } try { // sync channel topics if (update || more || setTopicRevision.current !== topicRevision) { let delta; if (!marker.current) { delta = await getTopicDelta(cardId, channelId, null, COUNT, null, null); } else if (more) { delta = await getTopicDelta(cardId, channelId, null, COUNT, null, marker.current); } else { delta = await getTopicDelta(cardId, channelId, setTopicRevision.current, null, marker.current, null); } for (let topic of delta?.topics) { if (topic.data == null) { topics.current.delete(topic.id); } else { let cur = topics.current.get(topic.id); if (cur == null) { cur = { id: topic.id, data: {} }; } if (topic.data.detailRevision !== cur.data.detailRevision) { if(topic.data.topicDetail) { cur.data.topicDetail = topic.data.topicDetail; cur.data.detailRevision = topic.data.detailRevision; } else { const slot = await getTopic(cardId, channelId, topic.id); cur.data.topicDetail = slot.data.topicDetail; cur.data.detailRevision = slot.data.detailRevision; } } cur.revision = topic.revision; topics.current.set(topic.id, cur); } } marker.current = delta.marker ? delta.marker : marker.current; setTopicRevision.current = topicRevision; updateState({ offsync: false, topicRevision: topicRevision, topics: topics.current }); } } catch (err) { console.log(err); updateState({ offsync: true }); syncing.current = false; return; } syncing.current = false; await sync(); } } }; useEffect(() => { if (conversationId.current) { const { cardId, channelId } = conversationId.current; setChannelRevision(cardId, channelId); sync(); } // eslint-disable-next-line }, [card.state, channel.state]); const actions = { setChannel: async (cardId, channelId) => { conversationId.current = { cardId, channelId }; setChannelRevision(cardId, channelId); reset.current = true; await sync(); }, clearChannel: async () => { conversationId.current = null; curDetailRevision.current = null; curTopicRevision.current = null; reset.current = true; await sync(); }, removeChannel: async () => { const { cardId, channelId } = conversationId.current; await removeChannel(cardId, channelId); }, setChannelSubject: async (type, subject) => { const { cardId, channelId } = conversationId.current; await setChannelSubject(cardId, channelId, type, subject); }, setChannelCard: async (id) => { const { cardId, channelId } = conversationId.current; await setChannelCard(cardId, channelId, id); }, clearChannelCard: async (id) => { const { cardId, channelId } = conversationId.current; await clearChannelCard(cardId, channelId, id); }, addTopic: async (type, message, files) => { const { cardId, channelId } = conversationId.current; await addTopic(cardId, channelId, type, message, files); }, removeTopic: async (topicId) => { const { cardId, channelId } = conversationId.current; await removeTopic(cardId, channelId, topicId); }, setTopicSubject: async (topicId, type, subject) => { const { cardId, channelId } = conversationId.current; await setTopicSubject(cardId, channelId, topicId, type, subject); }, getTopicAssetUrl: (assetId, topicId) => { const { cardId, channelId } = conversationId.current; return getTopicAssetUrl(cardId, channelId, topicId, assetId); }, loadMore: async () => { loadMore.current = true; await sync(); }, resync: async () => { force.current = true; await sync(); }, } return { state, actions } }