mirror of
https://github.com/balzack/databag.git
synced 2025-02-14 12:39:17 +00:00
refactor of channel view in webapp
This commit is contained in:
parent
5625fe4e89
commit
bdb77871cd
@ -1,22 +1,26 @@
|
||||
import CryptoJS from 'crypto-js';
|
||||
import { JSEncrypt } from 'jsencrypt'
|
||||
|
||||
export function isUnsealed(subject, sealKey) {
|
||||
export function getChannelSeals(subject) {
|
||||
const { seals } = JSON.parse(subject);
|
||||
return seals;
|
||||
}
|
||||
|
||||
export function isUnsealed(seals, sealKey) {
|
||||
for (let i = 0; i < seals?.length; i++) {
|
||||
if (seals[i].publicKey === sealKey.public) {
|
||||
return sealKey.private != null;
|
||||
if (seals[i].publicKey === sealKey?.public) {
|
||||
return sealKey?.private != null;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getContentKey(subject, sealKey) {
|
||||
for (let i = 0; i < seal?.length; i++) {
|
||||
if (seal[i].publicKey === sealKey.public) {
|
||||
export function getContentKey(seals, sealKey) {
|
||||
for (let i = 0; i < seals?.length; i++) {
|
||||
if (seals[i].publicKey === sealKey.public) {
|
||||
let crypto = new JSEncrypt();
|
||||
crypto.setPrivateKey(sealKey.private);
|
||||
return crypto.decrypt(seal.sealedKey);
|
||||
return crypto.decrypt(seals[i].sealedKey);
|
||||
|
||||
}
|
||||
}
|
||||
@ -36,20 +40,23 @@ export function encryptChannelSubject(subject, publicKeys) {
|
||||
publicKeys.forEach(key => {
|
||||
crypto.setPublicKey(key);
|
||||
const sealedKey = crypto.encrypt(keyHex);
|
||||
seals.push({ publicKey, sealedKey });
|
||||
seals.push({ key, sealedKey });
|
||||
});
|
||||
|
||||
return { subjectEncrypted, subjectIv, seals };
|
||||
}
|
||||
|
||||
export function decryptChannelSubject(subject, sealKey) {
|
||||
const { subjectEncrypted, subjectIv, seals } = JSON.parse(subject);
|
||||
const contentKey = getContentKey(seals, sealKey);
|
||||
export function decryptChannelSubject(subject, contentKey) {
|
||||
const { subjectEncrypted, subjectIv } = JSON.parse(subject);
|
||||
const iv = CryptoJS.enc.Hex.parse(subjectIv);
|
||||
const key = CryptoJS.enc.Hex.parse(contentKey);
|
||||
const enc = CryptoJS.enc.Base64.parse(subjectEncrypted);
|
||||
const cipher = CryptoJS.lib.CipherParams.create({ ciphertext: enc, iv: iv });
|
||||
const dec = CryptoJS.AES.decrypt(cipher, key, { iv: iv });
|
||||
const str = dec.toString(CryptoJS.enc.Utf8);
|
||||
if (!str) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(dec.toString(CryptoJS.enc.Utf8));
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,6 @@ export function useCardContext() {
|
||||
const revision = curRevision.current;
|
||||
const delta = await getCards(token, setRevision.current);
|
||||
for (let card of delta) {
|
||||
|
||||
if (card.data) {
|
||||
let cur = cards.current.get(card.id);
|
||||
if (cur == null) {
|
||||
@ -172,7 +171,7 @@ export function useCardContext() {
|
||||
const syncCardArticles = async (card) => {}
|
||||
|
||||
const syncCardChannels = async (card) => {
|
||||
const { cardProfile, cardDetail } = card.data;
|
||||
const { cardProfile, cardDetail, setNotifiedView, setNotifiedChannel } = card.data;
|
||||
const node = cardProfile.node;
|
||||
const token = `${cardProfile.guid}.${cardDetail.token}`;
|
||||
let delta;
|
||||
@ -181,7 +180,6 @@ export function useCardContext() {
|
||||
delta = await getContactChannels(node, token);
|
||||
}
|
||||
else {
|
||||
const { setNotifiedView, setNotifiedChannel } = card.data;
|
||||
delta = await getContactChannels(node, token, setNotifiedView, setNotifiedChannel);
|
||||
}
|
||||
for (let channel of delta) {
|
||||
@ -197,7 +195,6 @@ export function useCardContext() {
|
||||
else {
|
||||
cur.data.channelDetail = await getContactChannelDetail(node, token, channel.id);
|
||||
}
|
||||
cur.data.unsealedSubject = null;
|
||||
cur.data.detailRevision = channel.data.detailRevision;
|
||||
}
|
||||
if (cur.data.topicRevision !== channel.data.topicRevision) {
|
||||
@ -207,7 +204,6 @@ export function useCardContext() {
|
||||
else {
|
||||
cur.data.channelSummary = await getContactChannelSummary(node, token, channel.id);
|
||||
}
|
||||
cur.data.unsealedSummary = null;
|
||||
cur.data.topicRevision = channel.data.topicRevision;
|
||||
}
|
||||
cur.revision = channel.revision;
|
||||
@ -278,26 +274,6 @@ export function useCardContext() {
|
||||
let node = cardProfile.node;
|
||||
await removeContactChannel(node, token, channelId);
|
||||
},
|
||||
unsealChannelSubject: async (cardId, channelId, unsealed, revision) => {
|
||||
const card = cards.current.get(cardId);
|
||||
const channel = card.channels.get(channelId);
|
||||
if (channel.revision === revision) {
|
||||
channel.data.unsealedSubject = unsealed;
|
||||
card.channels.set(channelId, channel);
|
||||
cards.current.set(cardId, card);
|
||||
updateState({ cards: cards.current });
|
||||
}
|
||||
},
|
||||
unsealChannelSummary: async (cardId, channelId, unsealed, revision) => {
|
||||
const card = cards.current.get(cardId);
|
||||
const channel = card.channels.get(channelId);
|
||||
if (channel.revision === revision) {
|
||||
channel.data.unsealedSummary = unsealed;
|
||||
card.channels.set(channelId, channel);
|
||||
cards.current.set(cardId, card);
|
||||
updateState({ cards: cards.current });
|
||||
}
|
||||
},
|
||||
addTopic: async (cardId, channelId, type, message, files) => {
|
||||
let { cardProfile, cardDetail } = cards.current.get(cardId).data;
|
||||
let token = cardProfile.guid + '.' + cardDetail.token;
|
||||
|
@ -65,7 +65,6 @@ export function useChannelContext() {
|
||||
let detail = await getChannelDetail(token, channel.id);
|
||||
cur.data.channelDetail = detail;
|
||||
}
|
||||
cur.data.unsealedSubject = null;
|
||||
cur.data.detailRevision = channel.data.detailRevision;
|
||||
}
|
||||
if (cur.data.topicRevision !== channel.data.topicRevision) {
|
||||
@ -76,7 +75,6 @@ export function useChannelContext() {
|
||||
let summary = await getChannelSummary(token, channel.id);
|
||||
cur.data.channelSummary = summary;
|
||||
}
|
||||
cur.data.unsealedSummary = null;
|
||||
cur.data.topicRevision = channel.data.topicRevision;
|
||||
}
|
||||
cur.revision = channel.revision;
|
||||
@ -134,22 +132,6 @@ export function useChannelContext() {
|
||||
clearChannelCard: async (channelId, cardId) => {
|
||||
return await clearChannelCard(access.current, channelId, cardId);
|
||||
},
|
||||
unsealChannelSubject: async (channelId, unsealed, revision) => {
|
||||
const channel = channels.current.get(channelId);
|
||||
if (channel.revision === revision) {
|
||||
channel.data.unsealedSubject = unsealed;
|
||||
channels.current.set(channelId, channel);
|
||||
updateState({ channels: channels.current });
|
||||
}
|
||||
},
|
||||
unsealChannelSummary: async (channelId, unsealed, revision) => {
|
||||
const channel = channels.current.get(channelId);
|
||||
if (channel.revision === revision) {
|
||||
channel.data.unsealedSummary = unsealed;
|
||||
channels.current.set(channelId, channel);
|
||||
updateState({ channels: channels.current });
|
||||
}
|
||||
},
|
||||
addTopic: async (channelId, type, message, files) => {
|
||||
if (files?.length) {
|
||||
const topicId = await addChannelTopic(access.current, channelId, null, null, null);
|
||||
|
@ -9,6 +9,7 @@ export function useConversationContext() {
|
||||
offsync: false,
|
||||
topics: new Map(),
|
||||
channel: null,
|
||||
topicRevision: null,
|
||||
});
|
||||
|
||||
const card = useContext(CardContext);
|
||||
@ -231,7 +232,6 @@ export function useConversationContext() {
|
||||
cur.data.topicDetail = slot.data.topicDetail;
|
||||
cur.data.detailRevision = slot.data.detailRevision;
|
||||
}
|
||||
cur.data.unsealedSubject = null;
|
||||
}
|
||||
cur.revision = topic.revision;
|
||||
topics.current.set(topic.id, cur);
|
||||
@ -240,7 +240,7 @@ export function useConversationContext() {
|
||||
|
||||
marker.current = delta.marker;
|
||||
setTopicRevision.current = topicRevision;
|
||||
updateState({ offsync: false, topics: topics.current });
|
||||
updateState({ offsync: false, topicRevision: topicRevision, topics: topics.current });
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
@ -311,14 +311,6 @@ export function useConversationContext() {
|
||||
const { cardId, channelId } = conversationId.current;
|
||||
return getTopicAssetUrl(cardId, channelId, topicId, assetId);
|
||||
},
|
||||
unsealTopicSubject: (topicId, unsealed) => {
|
||||
const topic = topics.current.get(topicId);
|
||||
if (topic) {
|
||||
topic.data.unsealedSubject = unsealed;
|
||||
topics.current.set(topicId, topic);
|
||||
updateState({ topics: topics.current });
|
||||
}
|
||||
},
|
||||
loadMore: async () => {
|
||||
loadMore.current = true;
|
||||
await sync();
|
||||
|
@ -55,8 +55,8 @@ export function Channels({ open, active }) {
|
||||
{ state.channels.length > 0 && (
|
||||
<List local={{ emptyText: '' }} itemLayout="horizontal" dataSource={state.channels} gutter="0"
|
||||
renderItem={item => (
|
||||
<ChannelItem cardId={item.cardId} channelId={item.channelId}
|
||||
filter={state.filter} openChannel={open} active={active} />
|
||||
<ChannelItem item={item} openChannel={open}
|
||||
active={active.card === item.cardId && active.channel === item.channelId} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
@ -2,33 +2,29 @@ import { Tooltip } from 'antd';
|
||||
import { ChannelItemWrapper, Markup } from './ChannelItem.styled';
|
||||
import { Logo } from 'logo/Logo';
|
||||
import { UnlockOutlined, LockFilled } from '@ant-design/icons';
|
||||
import { useChannelItem } from './useChannelItem.hook';
|
||||
|
||||
export function ChannelItem({ cardId, channelId, filter, openChannel, active }) {
|
||||
|
||||
const { state } = useChannelItem(cardId, channelId, filter, active);
|
||||
export function ChannelItem({ item, openChannel, active }) {
|
||||
|
||||
return (
|
||||
<ChannelItemWrapper style={{ display: (!state.set || !state.visible) ? 'none' : null }}
|
||||
onClick={() => openChannel(channelId, cardId)}>
|
||||
<div class={state.active ? 'active' : 'idle'}>
|
||||
<ChannelItemWrapper onClick={() => openChannel(item.channelId, item.cardId)}>
|
||||
<div class={active ? 'active' : 'idle'}>
|
||||
<div class="item">
|
||||
<div class="avatar">
|
||||
<Logo url={state.logo} img={state.img} width={32} height={32} radius={8} />
|
||||
<Logo url={item.logo} img={item.img} width={32} height={32} radius={8} />
|
||||
</div>
|
||||
<div class="details">
|
||||
<div class="subject">
|
||||
{ state.locked && !state.unlocked && (
|
||||
{ item.locked && !item.unlocked && (
|
||||
<LockFilled style={{ paddingRight: 8 }}/>
|
||||
)}
|
||||
{ state.locked && state.unlocked && (
|
||||
{ item.locked && item.unlocked && (
|
||||
<UnlockOutlined style={{ paddingRight: 8 }}/>
|
||||
)}
|
||||
<span>{ state.subject }</span>
|
||||
<span>{ item.subject }</span>
|
||||
</div>
|
||||
<div class="message">{ state.message }</div>
|
||||
<div class="message">{ item.message }</div>
|
||||
</div>
|
||||
{ state.updatedFlag && (
|
||||
{ item.updatedFlag && (
|
||||
<Tooltip placement="topRight" title="New Message">
|
||||
<Markup />
|
||||
</Tooltip>
|
||||
|
@ -1,253 +0,0 @@
|
||||
import { useContext, useState, useEffect, useRef } from 'react';
|
||||
import { StoreContext } from 'context/StoreContext';
|
||||
import { ChannelContext } from 'context/ChannelContext';
|
||||
import { CardContext } from 'context/CardContext';
|
||||
import { AccountContext } from 'context/AccountContext';
|
||||
import { ProfileContext } from 'context/ProfileContext';
|
||||
import { getCardByGuid } from 'context/cardUtil';
|
||||
|
||||
export function useChannelItem(cardId, channelId, filter, active) {
|
||||
|
||||
const [state, setState] = useState({
|
||||
set: false,
|
||||
visible: false,
|
||||
active: false,
|
||||
locked: false,
|
||||
unlocked: false,
|
||||
updateFlag: false,
|
||||
img: null,
|
||||
logo: null,
|
||||
subject: null,
|
||||
message: null,
|
||||
});
|
||||
|
||||
const card = useContext(CardContext);
|
||||
const channel = useContext(ChannelContext);
|
||||
const account = useContext(AccountContext);
|
||||
const store = useContext(StoreContext);
|
||||
const profile = useContext(ProfileContext);
|
||||
|
||||
const updateState = (value) => {
|
||||
setState((s) => ({ ...s, ...value }));
|
||||
}
|
||||
|
||||
const syncing = useRef(false);
|
||||
const setCardId = useRef();
|
||||
const setChannelId = useRef();
|
||||
const setCardRevision = useRef();
|
||||
const setChannelRevision = useRef();
|
||||
const setSealKey = useRef();
|
||||
|
||||
const actions = {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
sync();
|
||||
// eslint-disable-next-line
|
||||
}, [card.state, channel.state, store.state, account.state]);
|
||||
|
||||
useEffect(() => {
|
||||
if (cardId === active?.card && channelId === active?.channel) {
|
||||
updateState({ active: true });
|
||||
}
|
||||
else {
|
||||
updateState({ active: false });
|
||||
}
|
||||
}, [cardId, channelId, active]);
|
||||
|
||||
useEffect(() => {
|
||||
if (filter) {
|
||||
if (state.subject) {
|
||||
updateState({ visible: state.subject.toUpperCase().includes(filter) });
|
||||
}
|
||||
else {
|
||||
updateState({ visible: false });
|
||||
}
|
||||
}
|
||||
else {
|
||||
updateState({ visible: true });
|
||||
}
|
||||
}, [filter, state.subject]);
|
||||
|
||||
const sync = async () => {
|
||||
if (!syncing.current) {
|
||||
syncing.current = true;
|
||||
|
||||
if (cardId !== setCardId.current || channelId !== setChannelId.current) {
|
||||
await setChannel(cardId, channelId);
|
||||
syncing.current = false;
|
||||
await sync();
|
||||
return;
|
||||
}
|
||||
if (cardId) {
|
||||
const contact = card.state.cards.get(cardId);
|
||||
if (contact?.revision !== setCardRevision.current) {
|
||||
await setChannel(cardId, channelId);
|
||||
syncing.current = false;
|
||||
await sync();
|
||||
return;
|
||||
}
|
||||
const conversation = contact.channels.get(channelId);
|
||||
if (conversation?.revision !== setChannelRevision.current) {
|
||||
await setChannel(cardId, channelId);
|
||||
syncing.current = false;
|
||||
await sync();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const conversation = channel.state.channels.get(channelId);
|
||||
if (conversation?.revision !== setChannelRevision.current) {
|
||||
await setChannel(cardId, channelId);
|
||||
syncing.current = false;
|
||||
await sync();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const key = account.state.sealKey;
|
||||
if (key?.pulic !== setSealKey.current?.public || key?.private !== setSealKey.current?.private) {
|
||||
await setChannel(cardId, channelId);
|
||||
syncing.current = false;
|
||||
await sync();
|
||||
return;
|
||||
}
|
||||
|
||||
syncing.current = false;
|
||||
}
|
||||
}
|
||||
|
||||
const setChannel = (cardId, channelId) => {
|
||||
if (cardId) {
|
||||
const cardItem = card.state.cards.get(cardId);
|
||||
const channelItem = cardItem?.channels?.get(channelId);
|
||||
setChannelItem(cardItem, channelItem);
|
||||
setCardRevision.current = cardItem?.revision;
|
||||
setChannelRevision.current = channelItem?.revision;
|
||||
}
|
||||
else {
|
||||
const channelItem = channel.state.channels.get(channelId);
|
||||
setChannelItem(null, channelItem);
|
||||
setChannelRevision.current = channelItem?.revision;
|
||||
}
|
||||
setSealKey.current = account.state.sealKey;
|
||||
setChannelId.current = channelId;
|
||||
setCardId.current = cardId;
|
||||
};
|
||||
|
||||
const setChannelItem = (cardItem, channelItem) => {
|
||||
|
||||
if (!channelItem) {
|
||||
updateState({ set: false });
|
||||
return;
|
||||
}
|
||||
const chan = { set: true };
|
||||
|
||||
// set updated flag
|
||||
const key = `${cardId}:${channelId}`
|
||||
const login = store.state['login:timestamp'];
|
||||
if (!chan.updated || !login || chan.updated < login) {
|
||||
chan.updatedFlag = false;
|
||||
}
|
||||
else if (store.state[key] && store.state[key] === channelItem.revision) {
|
||||
chan.updatedFlag = false;
|
||||
}
|
||||
else {
|
||||
chan.updatedFlag = true;
|
||||
}
|
||||
|
||||
// extract member info
|
||||
let memberCount = 0;
|
||||
let names = [];
|
||||
let img = null;
|
||||
let logo = null;
|
||||
if (cardItem) {
|
||||
const profile = cardItem?.data?.cardProfile;
|
||||
if (profile?.name) {
|
||||
names.push(profile.name);
|
||||
}
|
||||
if (profile?.imageSet) {
|
||||
img = null;
|
||||
logo = card.actions.getCardImageUrl(cardId);
|
||||
}
|
||||
else {
|
||||
img = 'avatar';
|
||||
logo = null;
|
||||
}
|
||||
memberCount++;
|
||||
}
|
||||
for (let guid of channelItem?.data?.channelDetail?.members) {
|
||||
if (guid !== profile.state.identity.guid) {
|
||||
const contact = getCardByGuid(card.state.cards, guid);
|
||||
const profile = contact?.data?.cardProfile;
|
||||
if (profile?.name) {
|
||||
names.push(profile.name);
|
||||
}
|
||||
if (profile?.imageSet) {
|
||||
img = null;
|
||||
logo = card.actions.getCardImageUrl(contact.id);
|
||||
}
|
||||
else {
|
||||
img = 'avatar';
|
||||
logo = null;
|
||||
}
|
||||
memberCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// set logo and label
|
||||
if (memberCount === 0) {
|
||||
chan.img = 'solution';
|
||||
chan.label = 'Notes';
|
||||
}
|
||||
else if (memberCount === 1) {
|
||||
chan.logo = logo;
|
||||
chan.img = img;
|
||||
chan.label = names.join(',');
|
||||
}
|
||||
else {
|
||||
chan.img = 'appstore';
|
||||
chan.label = names.join(',');
|
||||
}
|
||||
|
||||
// set subject
|
||||
const detail = channelItem.data?.channelDetail;
|
||||
if (detail?.dataType === 'sealedchannel') {
|
||||
// handle sealed subject
|
||||
chan.locked = true;
|
||||
chan.unlocked = false;
|
||||
}
|
||||
else if (detail?.dataType === 'superbasic') {
|
||||
chan.locked = false;
|
||||
chan.unlocked = true;
|
||||
try {
|
||||
const data = JSON.parse(detail.data);
|
||||
chan.subject = data.subject;
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
if (chan.subject == null) {
|
||||
chan.subject = chan.label;
|
||||
}
|
||||
|
||||
// set message
|
||||
const topic = channel.data?.channelSummary?.lastTopic;
|
||||
if (topic?.dataType === 'sealedtopic') {
|
||||
// handle sealed topic
|
||||
}
|
||||
else if (topic?.dataType === 'superbasictopic') {
|
||||
try {
|
||||
const data = JSON.parse(topic.data);
|
||||
chan.message = data.text;
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
updateState({ ...chan });
|
||||
}
|
||||
|
||||
return { state, actions };
|
||||
}
|
@ -1,17 +1,21 @@
|
||||
import { useContext, useState, useEffect } from 'react';
|
||||
import { useContext, useState, useRef, useEffect } from 'react';
|
||||
import { StoreContext } from 'context/StoreContext';
|
||||
import { ChannelContext } from 'context/ChannelContext';
|
||||
import { CardContext } from 'context/CardContext';
|
||||
import { AccountContext } from 'context/AccountContext';
|
||||
import { ViewportContext } from 'context/ViewportContext';
|
||||
import { ProfileContext } from 'context/ProfileContext';
|
||||
import { getCardByGuid } from 'context/cardUtil';
|
||||
import { isUnsealed, getChannelSeals, getContentKey, decryptChannelSubject } from 'context/sealUtil';
|
||||
|
||||
export function useChannels() {
|
||||
|
||||
const [filter, setFilter] = useState();
|
||||
|
||||
const [state, setState] = useState({
|
||||
display: null,
|
||||
channels: [],
|
||||
sealable: false,
|
||||
filter: null,
|
||||
busy: false,
|
||||
|
||||
showAdd: false,
|
||||
@ -20,16 +24,126 @@ export function useChannels() {
|
||||
seal: false,
|
||||
});
|
||||
|
||||
const profile = useContext(ProfileContext);
|
||||
const card = useContext(CardContext);
|
||||
const channel = useContext(ChannelContext);
|
||||
const account = useContext(AccountContext);
|
||||
const store = useContext(StoreContext);
|
||||
const viewport = useContext(ViewportContext);
|
||||
|
||||
const channels = useRef(new Map());
|
||||
|
||||
const updateState = (value) => {
|
||||
setState((s) => ({ ...s, ...value }));
|
||||
}
|
||||
|
||||
const syncChannelDetail = (item, cardValue, channelValue) => {
|
||||
|
||||
// extract member info
|
||||
let memberCount = 0;
|
||||
let names = [];
|
||||
let img = null;
|
||||
let logo = null;
|
||||
if (cardValue) {
|
||||
const profile = cardValue?.data?.cardProfile;
|
||||
if (profile?.name) {
|
||||
names.push(profile.name);
|
||||
}
|
||||
if (profile?.imageSet) {
|
||||
img = null;
|
||||
logo = card.actions.getCardImageUrl(cardValue.data.profileRevision);
|
||||
}
|
||||
else {
|
||||
img = 'avatar';
|
||||
logo = null;
|
||||
}
|
||||
memberCount++;
|
||||
}
|
||||
for (let guid of channelValue?.data?.channelDetail?.members) {
|
||||
if (guid !== profile.state.identity.guid) {
|
||||
const contact = getCardByGuid(card.state.cards, guid);
|
||||
const profile = contact?.data?.cardProfile;
|
||||
if (profile?.name) {
|
||||
names.push(profile.name);
|
||||
}
|
||||
if (profile?.imageSet) {
|
||||
img = null;
|
||||
logo = card.actions.getCardImageUrl(contact.id);
|
||||
}
|
||||
else {
|
||||
img = 'avatar';
|
||||
logo = null;
|
||||
}
|
||||
memberCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// set logo and label
|
||||
if (memberCount === 0) {
|
||||
item.img = 'solution';
|
||||
item.label = 'Notes';
|
||||
}
|
||||
else if (memberCount === 1) {
|
||||
item.logo = logo;
|
||||
item.img = img;
|
||||
item.label = names.join(',');
|
||||
}
|
||||
else {
|
||||
item.img = 'appstore';
|
||||
item.label = names.join(',');
|
||||
}
|
||||
|
||||
// set subject
|
||||
const detail = channelValue.data?.channelDetail;
|
||||
if (detail?.dataType === 'sealed') {
|
||||
item.locked = true;
|
||||
try {
|
||||
const { sealKey } = account.state;
|
||||
const seals = getChannelSeals(detail.data);
|
||||
if (isUnsealed(seals, sealKey)) {
|
||||
item.unlocked = true;
|
||||
if (!item.contentKey) {
|
||||
item.contentKey = getContentKey(seals, sealKey);
|
||||
}
|
||||
const unsealed = decryptChannelSubject(detail.data, item.contentKey);
|
||||
item.subject = unsealed?.subject;
|
||||
}
|
||||
else {
|
||||
item.unlocked = false;
|
||||
item.contentKey = null;
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
item.unlocked = false;
|
||||
}
|
||||
}
|
||||
else if (detail?.dataType === 'superbasic') {
|
||||
item.locked = false;
|
||||
item.unlocked = true;
|
||||
try {
|
||||
const data = JSON.parse(detail.data);
|
||||
item.subject = data.subject;
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
if (item.subject == null) {
|
||||
item.subject = item.label;
|
||||
}
|
||||
|
||||
// set updated revision
|
||||
item.detailRevision = channelValue.data.detailRevision;
|
||||
}
|
||||
|
||||
const syncChannelSummary = (item, channelValue) => {
|
||||
item.updated = channelValue.data?.channelSummary?.lastTopic?.created;
|
||||
|
||||
// set updated revision
|
||||
item.topicRevision = channelValue.data.topicRevision;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const { seal, sealKey } = account.state;
|
||||
if (seal?.publicKey && sealKey?.public && sealKey?.private && seal.publicKey === sealKey.public) {
|
||||
@ -38,21 +152,69 @@ export function useChannels() {
|
||||
else {
|
||||
updateState({ seal: false, sealable: false });
|
||||
}
|
||||
}, [account]);
|
||||
}, [account.state]);
|
||||
|
||||
useEffect(() => {
|
||||
const merged = [];
|
||||
const login = store.state['login:timestamp'];
|
||||
const conversations = new Map();
|
||||
const { sealKey } = account.state;
|
||||
card.state.cards.forEach((cardValue, cardId) => {
|
||||
cardValue.channels.forEach((channelValue, channelId) => {
|
||||
const updated = channelValue.data?.channelSummary?.lastTopic?.created;
|
||||
merged.push({ updated, cardId, channelId });
|
||||
const key = `${channelId}::${cardId}`;
|
||||
const { detailRevision, topicRevision } = channelValue.data;
|
||||
let item = channels.current.get(key);
|
||||
if (!item) {
|
||||
item = { cardId, channelId };
|
||||
}
|
||||
if (item.detailRevision !== detailRevision ||
|
||||
item.sealKey !== sealKey) {
|
||||
syncChannelDetail(item, cardValue, channelValue);
|
||||
}
|
||||
if (item.topicRevision !== topicRevision ||
|
||||
item.sealKey !== sealKey) {
|
||||
syncChannelSummary(item, channelValue);
|
||||
}
|
||||
item.sealKey = sealKey;
|
||||
const revision = store.state[key];
|
||||
console.log("> ", channelValue.id, topicRevision, revision);
|
||||
if (login && item.updated && item.updated > login && topicRevision !== revision) {
|
||||
item.updatedFlag = true;
|
||||
}
|
||||
else {
|
||||
item.updatedFlag = false;
|
||||
}
|
||||
conversations.set(key, item);
|
||||
});
|
||||
});
|
||||
channel.state.channels.forEach((channelValue, channelId) => {
|
||||
const updated = channelValue.data?.channelSummary?.lastTopic?.created;
|
||||
merged.push({ updated, channelId });
|
||||
const key = `${channelId}::${undefined}`;
|
||||
const { detailRevision, topicRevision } = channelValue.data;
|
||||
let item = channels.current.get(key);
|
||||
if (!item) {
|
||||
item = { channelId };
|
||||
}
|
||||
if (item.detailRevision !== detailRevision ||
|
||||
item.sealKey !== sealKey) {
|
||||
syncChannelDetail(item, null, channelValue);
|
||||
}
|
||||
if (item.topicRevision !== topicRevision ||
|
||||
item.sealKey !== sealKey) {
|
||||
syncChannelSummary(item, channelValue);
|
||||
}
|
||||
item.sealKey = sealKey;
|
||||
const revision = store.state[key];
|
||||
console.log("> ", channelValue.id, topicRevision, revision);
|
||||
if (login && item.updated && item.updated > login && topicRevision !== revision) {
|
||||
item.updatedFlag = true;
|
||||
}
|
||||
else {
|
||||
item.updatedFlag = false;
|
||||
}
|
||||
conversations.set(key, item);
|
||||
});
|
||||
channels.current = conversations;
|
||||
|
||||
const merged = Array.from(conversations.values());
|
||||
merged.sort((a, b) => {
|
||||
const aUpdated = a.updated;
|
||||
const bUpdated = b.updated;
|
||||
@ -65,10 +227,15 @@ export function useChannels() {
|
||||
return -1;
|
||||
});
|
||||
|
||||
updateState({ channels: merged });
|
||||
const filtered = merged.filter((item) => {
|
||||
const subject = item.subject?.toUpperCase();
|
||||
return !filter || subject?.includes(filter);
|
||||
});
|
||||
|
||||
updateState({ channels: filtered });
|
||||
|
||||
// eslint-disable-next-line
|
||||
}, [channel, card]);
|
||||
}, [account.state, store.state, card.state, channel.state, filter]);
|
||||
|
||||
useEffect(() => {
|
||||
updateState({ display: viewport.state.display });
|
||||
|
@ -10,6 +10,7 @@ import { Carousel } from 'carousel/Carousel';
|
||||
|
||||
export function AddTopic({ cardId, channelId, sealed, sealKey }) {
|
||||
|
||||
const [modal, modalContext] = Modal.useModal();
|
||||
const { state, actions } = useAddTopic(cardId, channelId);
|
||||
const attachImage = useRef(null);
|
||||
const attachAudio = useRef(null);
|
||||
@ -30,7 +31,7 @@ export function AddTopic({ cardId, channelId, sealed, sealKey }) {
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
Modal.error({
|
||||
modal.error({
|
||||
title: 'Failed to Post Message',
|
||||
content: 'Please try again.',
|
||||
});
|
||||
@ -90,6 +91,7 @@ export function AddTopic({ cardId, channelId, sealed, sealKey }) {
|
||||
|
||||
return (
|
||||
<AddTopicWrapper>
|
||||
{ modalContext }
|
||||
<input type='file' name="asset" accept="image/*" ref={attachImage} onChange={e => onSelectImage(e)} style={{display: 'none'}}/>
|
||||
<input type='file' name="asset" accept="audio/*" ref={attachAudio} onChange={e => onSelectAudio(e)} style={{display: 'none'}}/>
|
||||
<input type='file' name="asset" accept="video/*" ref={attachVideo} onChange={e => onSelectVideo(e)} style={{display: 'none'}}/>
|
||||
|
@ -116,7 +116,7 @@ export function useConversation(cardId, channelId) {
|
||||
}
|
||||
const { error, loadingInit, loadingMore, subject, logoUrl, logoImg } = conversation.state;
|
||||
updateState({ topics, error, loadingInit, loadingMore, subject, logoUrl, logoImg });
|
||||
store.actions.setValue(`${channelId}::${cardId}`, Number(conversation.state.revision));
|
||||
store.actions.setValue(`${channelId}::${cardId}`, Number(conversation.state.topicRevision));
|
||||
// eslint-disable-next-line
|
||||
}, [conversation]);
|
||||
|
||||
|
@ -18,8 +18,6 @@ function ChannelView() {
|
||||
|
||||
rendered.push(
|
||||
<div key={entry.id} data-testid="channel">
|
||||
<span data-testid="unsealed-subject">{ entry.data.unsealedSubject }</span>
|
||||
<span data-testid="unsealed-summary">{ entry.data.unsealedSummary }</span>
|
||||
<span data-testid="detail">{ entry.data.channelDetail.data }</span>
|
||||
<span data-testid="summary">{ entry.data.channelSummary.data }</span>
|
||||
</div>);
|
||||
@ -232,16 +230,6 @@ test('add, update and remove channel', async () => {
|
||||
expect(screen.getByTestId('channels').attributes.count.value).toBe(count.toString());
|
||||
});
|
||||
|
||||
await act( async () => {
|
||||
await channelContext.actions.unsealChannelSubject('123', 'unsealedsubject', 3);
|
||||
await channelContext.actions.unsealChannelSummary('123', 'unsealedsummary', 3);
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(screen.getByTestId('unsealed-subject').textContent).toBe('unsealedsubject');
|
||||
expect(screen.getByTestId('unsealed-summary').textContent).toBe('unsealedsummary');
|
||||
});
|
||||
|
||||
fetchChannels = [ { id: '123', revision: 3 } ];
|
||||
|
||||
await act( async () => {
|
||||
|
@ -84,15 +84,16 @@ beforeEach(() => {
|
||||
beginSet = false;
|
||||
|
||||
const mockFetch = jest.fn().mockImplementation((url, options) => {
|
||||
|
||||
const params = url.split('/');
|
||||
if (params[2].startsWith('channels?agent')) {
|
||||
if (params[2]?.startsWith('channels?agent')) {
|
||||
return Promise.resolve({
|
||||
url: 'getChannels',
|
||||
status: statusChannels,
|
||||
json: () => Promise.resolve(fetchChannels),
|
||||
});
|
||||
}
|
||||
if (params[2].startsWith('channels?contact')) {
|
||||
else if (params[4]?.startsWith('channels?contact')) {
|
||||
return Promise.resolve({
|
||||
url: 'getCardChannels',
|
||||
status: statusCardChannels,
|
||||
|
Loading…
Reference in New Issue
Block a user