From 9e23a2689d681910a2c0a518975b5e3c765eb5db Mon Sep 17 00:00:00 2001 From: balzack Date: Mon, 23 Dec 2024 22:55:10 -0800 Subject: [PATCH] implementing blocked card restore --- .../web/src/conversation/Conversation.tsx | 4 +- app/client/web/src/details/Details.tsx | 9 +-- app/client/web/src/message/Message.tsx | 8 ++- app/client/web/src/profile/Profile.tsx | 6 +- .../web/src/settings/Settings.module.css | 50 +++++++++++++++++ app/client/web/src/settings/Settings.tsx | 56 +++++++++++++++++-- .../web/src/settings/useSettings.hook.ts | 12 ++++ app/sdk/src/api.ts | 7 ++- app/sdk/src/contact.ts | 36 ++++-------- app/sdk/src/content.ts | 7 +++ app/sdk/src/focus.ts | 8 ++- app/sdk/src/settings.ts | 22 ++++++++ app/sdk/src/stream.ts | 12 ++-- 13 files changed, 188 insertions(+), 49 deletions(-) diff --git a/app/client/web/src/conversation/Conversation.tsx b/app/client/web/src/conversation/Conversation.tsx index eaa3c344..ad31ab96 100644 --- a/app/client/web/src/conversation/Conversation.tsx +++ b/app/client/web/src/conversation/Conversation.tsx @@ -3,7 +3,7 @@ import { Focus } from 'databag-client-sdk' import classes from './Conversation.module.css' import { useConversation } from './useConversation.hook'; import { IconSend, IconTextSize, IconTextColor, IconVideo, IconFile, IconDisc, IconCamera, IconX, IconSettings, IconHome, IconServer, IconShield, IconLock, IconExclamationCircle } from '@tabler/icons-react' -import { Menu, Divider, Text, Textarea, ActionIcon, Loader } from '@mantine/core' +import { CloseButton, Menu, Divider, Text, Textarea, ActionIcon, Loader } from '@mantine/core' import { Message } from '../message/Message'; import { modals } from '@mantine/modals' import { ImageFile } from './imageFile/ImageFile'; @@ -187,7 +187,7 @@ export function Conversation({ openDetails }: { openDetails: ()=>void }) { )}
- +
diff --git a/app/client/web/src/details/Details.tsx b/app/client/web/src/details/Details.tsx index f772fb76..0fd1b300 100644 --- a/app/client/web/src/details/Details.tsx +++ b/app/client/web/src/details/Details.tsx @@ -5,6 +5,7 @@ import { IconUserCog, IconEyeOff, IconAlertHexagon, IconMessageX, IconLogout2, I import { Switch, Button, Modal, Divider, Text, Textarea, Image, TextInput, ActionIcon } from '@mantine/core' import { Card } from '../card/Card'; import { modals } from '@mantine/modals' +import { useDisclosure } from '@mantine/hooks' export function Details({ showClose, close }: { showClose: boolean, close: () => void }) { const { state, actions } = useDetails() @@ -12,7 +13,7 @@ export function Details({ showClose, close }: { showClose: boolean, close: () => const [removing, setRemoving] = useState(false); const [blocking, setBlocking] = useState(false); const [reporting, setReporting] = useState(false); - const [showModal, setShowModal] = useState(false); + const [showModal, { open: setShowModal, close: clearShowModal }] = useDisclosure(false) const undo = () => { actions.undoSubject(); @@ -291,7 +292,7 @@ export function Details({ showClose, close }: { showClose: boolean, close: () => {state.strings.remove}
- setShowModal(true)}> + {state.strings.members} @@ -324,7 +325,7 @@ export function Details({ showClose, close }: { showClose: boolean, close: () => { state.strings.syncError }
)} - setShowModal(false)} overlayProps={{ backgroundOpacity: 0.65, blur: 3 }} centered> +
{ members.length > 0 && (
@@ -337,7 +338,7 @@ export function Details({ showClose, close }: { showClose: boolean, close: () =>
)}
-
diff --git a/app/client/web/src/message/Message.tsx b/app/client/web/src/message/Message.tsx index 67b22417..e8a2f8a5 100644 --- a/app/client/web/src/message/Message.tsx +++ b/app/client/web/src/message/Message.tsx @@ -208,8 +208,12 @@ export function Message({ topic, card, profile, host }: { topic: Topic, card: Ca { (host || profile) && ( )} - - + { !profile && ( + + )} + { !profile && ( + + )}
diff --git a/app/client/web/src/profile/Profile.tsx b/app/client/web/src/profile/Profile.tsx index c254ef94..919ea552 100644 --- a/app/client/web/src/profile/Profile.tsx +++ b/app/client/web/src/profile/Profile.tsx @@ -18,7 +18,7 @@ import { IconCancel, IconDeviceFloppy, } from '@tabler/icons-react' -import { Text, Image, ActionIcon } from '@mantine/core' +import { CloseButton, Text, Image, ActionIcon } from '@mantine/core' export type ProfileParams = { guid: string @@ -239,9 +239,9 @@ export function Profile({ params, showClose, close }: { params: ProfileParams; s return (
- {showClose && } + {showClose && } {`${state.handle}${state.node ? '/' + state.node : ''}`} - {showClose && } + {showClose && }
diff --git a/app/client/web/src/settings/Settings.module.css b/app/client/web/src/settings/Settings.module.css index 5c0b223d..09e8f132 100644 --- a/app/client/web/src/settings/Settings.module.css +++ b/app/client/web/src/settings/Settings.module.css @@ -278,3 +278,53 @@ } } } + +.empty { + width: 600px; + height: 128px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--mantine-color-text-7); + border-radius: 4px; + overflow: auto; + background: var(--mantine-color-surface-1); + + .emptyLabel { + color: var(--mantine-color-text-6); + } +} +.blocked { + width: 600px; + min-height: 128px; + max-height: 256px; + border: 1px solid var(--mantine-color-text-7); + border-radius: 4px; + overflow: auto; + background: var(--mantine-color-surface-1); + + .blockedItem { + display: flex; + align-items: center; + padding-left: 16px; + padding-right: 16px; + padding-top: 8px; + padding-bottom: 8px; + border-bottom: 1px solid var(--mantine-color-text-8); + + .blockedValue { + flex-grow: 1; + font-size: 14px; + padding-right: 16px; + } + } +} + +.controls { + display: flex; + width: 100%; + justify-content: flex-end; + padding-top: 8px; + +} + diff --git a/app/client/web/src/settings/Settings.tsx b/app/client/web/src/settings/Settings.tsx index 914a497e..846b6133 100644 --- a/app/client/web/src/settings/Settings.tsx +++ b/app/client/web/src/settings/Settings.tsx @@ -1,5 +1,5 @@ import { useSettings } from './useSettings.hook' -import { Modal, Textarea, TextInput, PasswordInput, Radio, Group, Select, Switch, Text, PinInput, Image, Button, UnstyledButton } from '@mantine/core' +import { Modal, Textarea, TextInput, PasswordInput, ActionIcon, Radio, Group, Select, Switch, Text, PinInput, Image, Button, UnstyledButton } from '@mantine/core' import classes from './Settings.module.css' import { IconLock, @@ -11,6 +11,7 @@ import { IconVideo, IconMicrophone, IconWorld, + IconRestore, IconBrightness, IconTicket, IconCloudLock, @@ -43,6 +44,9 @@ export function Settings({ showLogout }: { showLogout: boolean }) { const [imageOpened, { open: imageOpen, close: imageClose }] = useDisclosure(false) const [mfaOpened, { open: mfaOpen, close: mfaClose }] = useDisclosure(false) const [sealOpened, { open: sealOpen, close: sealClose }] = useDisclosure(false) + const [blockedContactOpened, { open: blockedContactOpen, close: blockedContactClose }] = useDisclosure(false) + const [blockedTopicOpened, { open: blockedTopicOpen, close: blockedTopicClose }] = useDisclosure(false) + const [blockedMessageOpened, { open: blockedMessageOpen, close: blockedMessageClose }] = useDisclosure(false) const [savingLogin, setSavingLogin] = useState(false) const [savingDetails, setSavingDetails] = useState(false) const [savingImage, setSavingImage] = useState(false) @@ -57,6 +61,27 @@ export function Settings({ showLogout }: { showLogout: boolean }) { const [sealConfig, setSealConfig] = useState(false) const [authMessage, setAuthMessage] = useState('') + const showBlockedCards = async () => { + await actions.loadBlockedCards(); + blockedContactOpen(); + } + + const unblockCard = async (cardId: string) => { + try { + await actions.unblockCard(cardId); + } catch (err) { + console.log(err); + showError(); + } + } + + const blockedCards = state.blockedCards.map(blocked => ( +
+ CardID: { blocked.cardId } + unblockCard(blocked.cardId)}> +
+ )); + const logout = () => modals.openConfirmModal({ title: state.strings.confirmLogout, @@ -430,19 +455,19 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
-
+
{state.strings.blockedContacts}
-
+
{state.strings.blockedTopics}
-
+
@@ -803,6 +828,29 @@ export function Settings({ showLogout }: { showLogout: boolean }) { )} + + { blockedCards.length > 0 && ( +
+ { blockedCards } +
+ )} + { blockedCards.length === 0 && ( +
+ { state.strings.noContacts } +
+ )} +
+ +
+
+ +
+
+ +
+
) } diff --git a/app/client/web/src/settings/useSettings.hook.ts b/app/client/web/src/settings/useSettings.hook.ts index 949bd699..a0564c91 100644 --- a/app/client/web/src/settings/useSettings.hook.ts +++ b/app/client/web/src/settings/useSettings.hook.ts @@ -50,6 +50,7 @@ export function useSettings() { sealConfirm: '', sealDelete: '', secretCopied: false, + blockedCards: [] as {cardId: string}[], }) // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -112,6 +113,17 @@ export function useSettings() { }, [display.state]) const actions = { + loadBlockedCards: async () => { + const settings = app.state.session.getSettings(); + const blockedCards = await settings.getBlockedCards(); + updateState({ blockedCards }); + }, + unblockCard: async (cardId: string) => { + const contact = app.state.session.getContact(); + await contact.setBlockedCard(cardId, false); + const blockedCards = state.blockedCards.filter(blocked => blocked.cardId != cardId); + updateState({ blockedCards }); + }, getUsernameStatus: async (username: string) => { const { settings } = getSession() return await settings.getUsernameStatus(username) diff --git a/app/sdk/src/api.ts b/app/sdk/src/api.ts index b3864861..844869ff 100644 --- a/app/sdk/src/api.ts +++ b/app/sdk/src/api.ts @@ -44,6 +44,10 @@ export interface Settings { updateSeal(password: string): Promise; forgetSeal(): Promise; + getBlockedCards(): Promise<{cardId: string}[]>; + getBlockedChannels(): Promise<{cardId: string | null, channelId: string}[]>; + getBlockedTopicis(): Promise<{cardId: string | null, channelId: string, topicId: string}[]>; + addConfigListener(ev: (config: Config) => void): void; removeConfigListener(ev: (config: Config) => void): void; } @@ -69,7 +73,6 @@ export interface Contact { resyncCard(cardId: string): Promise; flagCard(cardId: string): Promise; setBlockedCard(cardId: string, blocked: boolean): Promise; - getBlockedCards(): Promise; getRegistry(handle: string | null, server: string | null): Promise; addCardListener(ev: (cards: Card[]) => void): void; @@ -91,7 +94,7 @@ export interface Content { flagChannel(cardId: string | null, channelId: string): Promise; setBlockedChannel(cardId: string | null, channelId: string, blocked: boolean): Promise; - getBlockedChannels(): Promise; + clearBlockedChannelTopic(cardId: string | null, channelId: string, topicId: string): Promise; addChannelListener(ev: (arg: { channels: Channel[]; cardId: string | null }) => void): void; removeChannelListener(ev: (arg: { channels: Channel[]; cardId: string | null }) => void): void; diff --git a/app/sdk/src/contact.ts b/app/sdk/src/contact.ts index 051f96d8..b0fc31f9 100644 --- a/app/sdk/src/contact.ts +++ b/app/sdk/src/contact.ts @@ -199,7 +199,7 @@ export class ContactModule implements Contact { this.blockedCard.add(cardId); entry.card = this.setCard(cardId, entry.item); this.emitCards(); - await this.store.setMarker(this.guid, 'blocked_card', cardId, ''); + await this.store.setMarker(this.guid, 'blocked_card', cardId, JSON.stringify({cardId})); } private async clearCardBlocked(cardId: string) { @@ -231,7 +231,7 @@ export class ContactModule implements Contact { this.blockedCardChannel.add(id); channelEntry.channel = this.setChannel(cardId, channelId, channelEntry.item); this.emitChannels(cardId); - await this.store.setMarker(this.guid, 'blocked_card_channel', id, ''); + await this.store.setMarker(this.guid, 'blocked_card_channel', id, JSON.stringify({ cardId, channelId })); } private async clearChannelBlocked(cardId: string, channelId: string) { @@ -841,30 +841,6 @@ export class ContactModule implements Contact { } } - public async getBlockedCards(): Promise { - return Array.from(this.cardEntries.entries()) - .filter(([key, value]) => this.isCardBlocked(key)) - .map(([key, value]) => value.card); - } - - public async getBlockedChannels(): Promise { - const channels: Channel[] = []; - this.channelEntries.forEach((card, cardId) => { - const cardChannels = Array.from(card.entries()) - .filter(([key, value]) => this.isChannelBlocked(cardId, key)) - .map(([key, value]) => value.channel); - cardChannels.forEach((channel) => { - channels.push(channel); - }); - }); - return channels; - } - - public async getBlockedArticles(): Promise { - const articles: Article[] = []; - return articles; - } - public async setBlockedCard(cardId: string, blocked: boolean): Promise { const entry = this.cardEntries.get(cardId); if (entry) { @@ -894,6 +870,14 @@ export class ContactModule implements Contact { } } + public async clearBlockedChannelTopic(cardId: string, channelId: string, topicId: string) { + const id = `${cardId}:${channelId}:${topicId}` + await this.store.clearMarker(guid, 'blocked_topic', id); + if (this.focus) { + await this.focus.clearBlockedChannelTopic(cardId, channelId, topicId); + } + } + public async setBlockedArticle(cardId: string, articleId: string, blocked: boolean): Promise { const entries = this.articleEntries.get(cardId); if (entries) { diff --git a/app/sdk/src/content.ts b/app/sdk/src/content.ts index af755a60..13992491 100644 --- a/app/sdk/src/content.ts +++ b/app/sdk/src/content.ts @@ -99,6 +99,13 @@ export class ContentModule implements Content { return channels.concat(cardChannels); } + public async clearBlockedChannelTopic(cardId: string | null, channelId: string, topicId: string): Promise { + if (cardId) { + return await this.contact.clearBlockedChannelTopic(cardId, channelId, topicId); + } + return await this.stream.clearBlockedChannelTopic(channelId, topicId); + } + public addChannelListener(ev: (arg: { channels: Channel[]; cardId: string | null }) => void): void { this.stream.addChannelListener(ev); this.contact.addChannelListener(ev); diff --git a/app/sdk/src/focus.ts b/app/sdk/src/focus.ts index 5469fa06..c0390c1d 100644 --- a/app/sdk/src/focus.ts +++ b/app/sdk/src/focus.ts @@ -822,7 +822,7 @@ export class FocusModule implements Focus { this.blocked.add(id); entry.topic = this.setTopic(topicId, entry.item); this.emitTopics(); - await this.store.setMarker(guid, 'blocked_topic', id, ''); + await this.store.setMarker(guid, 'blocked_topic', id, JSON.stringify({ cardId, channelId, topicId })); } } @@ -838,6 +838,12 @@ export class FocusModule implements Focus { } } + public async clearBlockedChannelTopic(cardId: string | null, channelId: string, topicId: string) { + if (cardId === this.cardId && channelId === this.channelId) { + await this.clearBlockTopic(topicId); + } + } + private isTopicBlocked(topicId: string): boolean { const { cardId, channelId, guid } = this; const id = `${cardId ? cardId : ''}:${channelId}:${topicId}` diff --git a/app/sdk/src/settings.ts b/app/sdk/src/settings.ts index f79132c7..f3cc5f72 100644 --- a/app/sdk/src/settings.ts +++ b/app/sdk/src/settings.ts @@ -279,4 +279,26 @@ export class SettingsModule implements Settings { const { node, secure, token } = this; await setAccountLogin(node, secure, token, username, password); } + + public async getBlockedCards(): Promise<{cardId: string}[]> { + const { guid } = this; + const blockedContacts = await this.store.getMarkers(guid, 'blocked_card'); + return blockedContacts.map(marker => { + try { + return JSON.parse(marker.value); + } catch (err) { + return {}; + } + }); + } + + public async getBlockedChannels(): Promise<{cardId: string | null, channelId: string}[]> { + const blockedChannels = await this.store.getMarkers(guid, 'blocked_card_channel'); + return []; + } + + public async getBlockedTopics(): Promise<{cardId: string | null, channelId: string, topicId: string}[]> { + const blockedTopics = await this.store.getMarkers(guid, 'blocked_topic'); + return []; + } } diff --git a/app/sdk/src/stream.ts b/app/sdk/src/stream.ts index 55ed107a..efbe6005 100644 --- a/app/sdk/src/stream.ts +++ b/app/sdk/src/stream.ts @@ -383,10 +383,12 @@ export class StreamModule { } } - public async getBlockedChannels(): Promise { - return Array.from(this.channelEntries.entries()) - .filter(([key, value]) => this.isChannelBlocked(key)) - .map(([key, value]) => value.channel); + public async clearBlockedChannelTopic(channelId: string, topicId: string) { + const id = `'':${channelId}:${topicId}` + await this.store.clearMarker(guid, 'blocked_topic', id); + if (this.focus) { + await this.focus.clearBlockedChannelTopic(null, channelId, topicId); + } } public async flagChannel(channelId: string): Promise { @@ -507,7 +509,7 @@ export class StreamModule { this.blocked.add(channelId); entry.channel = this.setChannel(channelId, entry.item); this.emitChannels(); - await this.store.setMarker(this.guid, 'blocked_channel', channelId, ''); + await this.store.setMarker(this.guid, 'blocked_channel', channelId, JSON.stringify({ cardId: null, channelId })); } private async clearChannelBlocked(channelId: string) {