rendering details screen

This commit is contained in:
balzack 2024-12-21 22:22:19 -08:00
parent 275629a4a0
commit 257568e729
10 changed files with 8582 additions and 5460 deletions

View File

@ -7,11 +7,16 @@ export const en = {
encrypted: 'Encrypted',
unknown: 'Unknown',
sealed: 'Sealed',
notSealed: 'Not Sealed',
notes: 'Notes',
server: 'Server',
token: 'Token',
delayMessage: 'Key generation can take several minutes.',
membership: 'Membership',
channelGuest: 'Topic Guest',
channelHost: 'Topic Host',
created: 'Created',
code: 'en',
settings: 'Settings',
contacts: 'Contacts',
@ -44,6 +49,7 @@ export const en = {
unsetSealing: 'Unset Sealing Key',
newTopic: 'New Topic',
subject: 'Subject',
noContacts: 'No Contacts',
noTopics: 'No Topics',
noConnected: 'No Connected Contacts',
@ -272,11 +278,16 @@ export const fr = {
encrypted: 'Crypté',
unknown: 'Inconnu',
sealed: 'Scellé',
notSealed: 'Non Scellé',
notes: 'Notes',
server: 'Serveur',
token: 'Code',
delayMessage: 'La génération de clé peut prendre plusieurs minutes.',
membership: 'Adhésion',
channelHost: 'Hôte du Sujet',
channelGuest: 'Invité du Sujet',
created: 'Créé',
flagMessage: 'Signaler le message',
flagMessagePrompt: 'Êtes-vous sûr de vouloir signaler le message à l\'administrateur?',
flag: 'Signaler',
@ -340,6 +351,7 @@ export const fr = {
unsetSealing: 'Clé de sécurité non définie',
newTopic: 'Nouveau Sujet',
subject: 'Sujet',
noContacts: 'Pas de Contacts',
noTopics: 'Pas de Sujets',
noConnected: 'Pas de Contacts Connecter',
@ -538,11 +550,16 @@ export const sp = {
encrypted: 'Cifrado',
unknown: 'Desconocido',
sealed: 'Sellado',
notSealed: 'No Sellado',
notes: 'Notas',
server: 'Server',
token: 'Código',
delayMessage: 'La generación de claves puede tardar varios minutos.',
membership: 'Afiliación',
channelHost: 'Anfitrión del Tema',
channelGuest: 'Invitado de Tema',
created: 'Creado',
flagMessage: 'Marcar mensaje',
flagMessagePrompt: '¿Está seguro de que desea marcar el mensaje para el administrador?',
flag: 'Marcar',
@ -606,6 +623,7 @@ export const sp = {
unsetSealing: 'Clave de seguridad no definida',
newTopic: 'Nuevo tema',
subject: 'Tema',
noContacts: 'Sin contactos',
noTopics: 'Sin temas',
noConnected: 'Ningún contacto conectado',
@ -803,11 +821,16 @@ export const pt = {
encrypted: 'Criptografado',
unknown: 'Desconhecido',
sealed: 'Selado',
notSealed: 'Não Selado',
notes: 'Notas',
server: 'Servidor',
token: 'Code',
delayMessage: 'A geração da chave pode levar vários minutos.',
membership: 'Associação',
channelHost: 'Anfitrião do Tópico',
channelGuest: 'Convidado do Tópico',
created: 'Criado',
flagMessage: 'Sinalizar mensagem',
flagMessagePrompt: 'Tem certeza de que deseja sinalizar a mensagem para o administrador?',
flag: 'Sinalizar',
@ -871,6 +894,7 @@ export const pt = {
unsetSealing: 'Chave de segurança não definida',
newTopic: 'Novo tópico',
subject: 'Assunto',
noContacts: 'Sem contatos',
noTopics: 'Sem tópicos',
noConnected: 'Nenhum contato conectado',
@ -1068,11 +1092,16 @@ export const de = {
encrypted: 'Verschlüsselt',
unknown: 'Unbekannt',
sealed: 'Versiegelt',
notSealed: 'Nicht Versiegelt',
notes: 'Notizen',
server: 'Servierer',
token: 'Token',
delayMessage: 'Die Schlüsselgenerierung kann mehrere Minuten dauern.',
membership: 'Mitgliedschaft',
channelHost: 'Themenhost',
channelGuest: 'Thema Gast',
created: 'Erstellt',
flagMessage: 'Nachricht melden',
flagMessagePrompt: 'Sind Sie sicher, dass Sie die Nachricht an den Administrator melden möchten?',
flag: 'Melden',
@ -1136,6 +1165,7 @@ export const de = {
unsetSealing: 'Sicherheitsschlüssel nicht festgelegt',
newTopic: 'Neues Thema',
subject: 'Betreff',
noContacts: 'Keine Kontakte',
noTopics: 'Keine Themen',
noConnected: 'Keine verbundenen Kontakte',
@ -1333,11 +1363,16 @@ export const ru = {
encrypted: 'Зашифрованный',
unknown: 'Неизвестно',
sealed: 'Запечатано',
notSealed: 'Не Запечатано',
notes: 'Заметки',
server: 'Сервер',
token: 'Токен',
delayMessage: 'Генерация ключа может занять несколько минут.',
created: 'Созданный',
membership: 'Членство',
channelHost: 'Ведущий темы',
channelGuest: 'Тема Гость',
flagMessage: 'Пожаловаться на сообщение',
flagMessagePrompt: 'Вы уверены, что хотите пожаловаться на сообщение администратору?',
flag: 'Пожаловаться',
@ -1401,6 +1436,7 @@ export const ru = {
unsetSealing: 'Ключ безопасности не установлен',
newTopic: 'Новая тема',
subject: 'Тема',
noContacts: 'Нет контактов',
noTopics: 'Нет тем',
noConnected: 'Нет подключенных контактов',

View File

@ -5,4 +5,166 @@
align-items: center;
width: 100%;
height: 100%;
min-width: 0;
.membership {
padding-top: 32px;
font-size: 12px;
.members {
font-size: 12px;
}
}
.body {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
min-width: 0;
overflow: auto;
}
.disconnected {
display: flex;
align-items: center;
gap: 16px;
padding-top: 32px;
color: var(--mantine-color-red-2);
}
.actions {
display: flex;
flex-direction: row;
gap: 12px;
padding-top: 16px;
flex-wrap: wrap;
padding-left: 16px;
padding-right: 16px;
padding-top: 32px;
padding-bottom: 16px;
justify-content: center;
.action {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
height: fit-content;
.actionLabel {
font-size: 12px;
color: var(--mantine-color-dbgreen-1);
}
}
}
.attributes {
display: flex;
flex-direction: column;
padding-left: 16px;
padding-right: 16px;
gap: 6px;
padding-top: 16px;
padding-bottom: 24px;
.attribute {
display: flex;
gap: 8px;
align-items: center;
padding-left: 4px;
.subjectLabel {
}
.subjectValue {
font-size: 28px;
}
.subjectPlaceholder {
font-size: 28px;
color: var(--mantine-color-text-7);
}
.attributeLabel {
font-size: 16px;
}
.attributeValue {
font-size: 16px;
}
}
}
.label {
font-size: 24px;
padding: 8px;
}
.divider {
width: 100%;
}
.header {
display: flex;
justify-content: center;
width: 100%;
border-bottom: 1px solid var(--mantine-color-text-7);
padding-left: 16px;
padding-right: 16px;
height: 48px;
flex-shrink: 0;
}
.subject {
padding-bottom: 8px;
.subjectControls {
display: flex;
gap: 8;
}
.subjectLabel {
display: flex;
gap: 16px;
align-items: center;
.guestSubject {
display: flex;
gap: 8px;
}
.guestPlaceholder {
display: flex;
font-style: italic;
gap: 8px;
}
.subjectValue {
font-size: 20px;
}
.subjectPlaceholder {
font-size: 20px;
font-style: italic;
}
}
}
.card {
width: 100%;
height: 48px;
padding-top: 8px;
padding-bottom: 8px;
padding-right: 16px;
padding-left: 16px;
border-bottom: 1px solid var(--mantine-color-text-8);
}
.cards {
min-width: 0;
width: 100%;
flex-shink: 1;
overflow: auto;
}
}

View File

@ -1,14 +1,159 @@
import React, { useState } from 'react'
import { useDetails } from './useDetails.hook'
import classes from './Details.module.css'
import { IconX } from '@tabler/icons-react'
import { IconUserCog, IconEyeOff, IconAlertHexagon, IconMessageX, IconLogout2, IconHome, IconServer, IconShield, IconShieldOff, IconCalendarClock, IconExclamationCircle, IconX, IconEdit, IconDeviceFloppy, IconArrowBack, IconLabel } from '@tabler/icons-react'
import { Divider, Text, Textarea, Image, TextInput, ActionIcon } from '@mantine/core'
import { Card } from '../card/Card';
export function Details({ close }: { close?: () => void }) {
const { state, actions } = useDetails()
const undo = () => {
actions.undoSubject();
}
const save = () => {
console.log('save subject');
}
const cards = state.channelCards.map((card, index) => (
<Card className={classes.card} key={index} imageUrl={card.imageUrl} name={card.name} placeHolder={state.strings.name}
handle={card.handle} node={card.node}/>
))
console.log(state.hostCard, state.channelCards, state.unknownContacts);
return (
<div className={classes.details}>
{close && <IconX size={30} className={classes.close} onClick={close} />}
<div className={classes.header}>
{close && <IconX size={30} className={classes.match} />}
<Text className={classes.label}>{ state.strings.details }</Text>
{close && <IconX size={30} className={classes.close} onClick={close} />}
</div>
{ state.access && (
<div className={classes.body}>
<div className={classes.attributes}>
{ state.host && (
<div className={classes.subject}>
<div className={classes.subjectLabel}>
<TextInput size="lg" placeholder={state.strings.subject} value={state.editSubject} onChange={(event) => actions.setEditSubject(event.currentTarget.value)}
leftSectionPointerEvents="none" leftSection={<IconLabel />}
rightSectionPointerEvents="all" rightSectionWidth={64} rightSection={
<div className={classes.subjectControls}>
{ state.editSubject != state.subject && (
<ActionIcon key="undo" variant="subtle" onClick={undo}><IconArrowBack /></ActionIcon>
)}
{ state.editSubject != state.subject && (
<ActionIcon key="save" variant="subtle" onClick={save}><IconDeviceFloppy /></ActionIcon>
)}
</div>
} />
</div>
</div>
)}
{ !state.host && state.subject && (
<div className={classes.attribute}>
<IconLabel size={28} className={classes.subjectValue} />
<Text className={classes.subjectValue}>{ state.subject }</Text>
</div>
)}
{ !state.host && !state.subject && (
<div className={classes.attribute}>
<IconLabel size={28} className={classes.subjectPlaceholder} />
<Text className={classes.subjectPlaceholder}>{ state.strings.subject }</Text>
</div>
)}
<div className={classes.attribute}>
<IconCalendarClock size={20}/>
<Text className={classes.attributeValue}>{ state.created }</Text>
</div>
{ state.sealed && (
<div className={classes.attribute}>
<IconShield size={20} />
<Text className={classes.attributeValue}>{ state.strings.sealed }</Text>
</div>
)}
{ !state.sealed && (
<div className={classes.attribute}>
<IconShieldOff size={20} />
<Text className={classes.attributeValue}>{ state.strings.notSealed }</Text>
</div>
)}
{ state.host && (
<div className={classes.attribute}>
<IconHome size={20} />
<Text className={classes.attributeValue}>{ state.strings.channelHost }</Text>
</div>
)}
{ !state.host && (
<div className={classes.attribute}>
<IconServer size={20} />
<Text className={classes.attributeValue}>{ state.strings.channelGuest }</Text>
</div>
)}
</div>
<Divider className={classes.divider} />
{ !state.host && (
<div className={classes.actions}>
<div className={classes.action}>
<ActionIcon variant="subtle" size="lg">
<IconLogout2 size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.leave}</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle" size="lg">
<IconEyeOff size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.block}</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle" size="lg">
<IconAlertHexagon size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.report}</Text>
</div>
</div>
)}
{ state.host && (
<div className={classes.actions}>
<div className={classes.action}>
<ActionIcon variant="subtle" size="lg" >
<IconMessageX size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.remove}</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle" size="lg" >
<IconUserCog size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.members}</Text>
</div>
</div>
)}
<div className={classes.membership}>
<Text className={classes.members}>{ state.strings.membership }</Text>
</div>
<Divider className={classes.divider} size="md" />
<div className={classes.cards}>
{ state.hostCard && (
<Card className={classes.card} imageUrl={state.hostCard.imageUrl} name={state.hostCard.name} placeHolder={state.strings.name}
handle={state.hostCard.handle} node={state.hostCard.node} actions={[<IconHome key="host" size={20} />]} />
)}
{ cards }
{ state.unknownContacts > 0 && (
<Text className={classes.unknown}>{ state.strings.unknown }: {state.unknownContacts}</Text>
)}
</div>
</div>
)}
{ !state.access && (
<div className={classes.disconnected}>
<IconExclamationCircle />
<Text>{ state.strings.syncError }</Text>
</div>
)}
</div>
)
}

View File

@ -1,7 +1,27 @@
import { useState, useContext, useEffect } from 'react'
import { AppContext } from '../context/AppContext'
import { DisplayContext } from '../context/DisplayContext'
import { ContextType } from '../context/ContextType'
import { FocusDetail, Card, Profile } from 'databag-client-sdk';
export function useDetails() {
const display = useContext(DisplayContext) as ContextType
const app = useContext(AppContext) as ContextType
const [state, setState] = useState({
access: false,
host: false,
sealed: false,
locked: false,
strings: display.state.strings,
timeFormat: display.state.timeFormat,
dateFormat: display.state.dateFormat,
subject: '',
editSubject: '',
created: '',
profile: null as null | Porfile,
cards: [] as Card[],
hostCard: null as null | Card,
channelCards: [] as Card[],
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -9,7 +29,98 @@ export function useDetails() {
setState((s) => ({ ...s, ...value }))
}
const getTimestamp = (created: number) => {
const now = Math.floor((new Date()).getTime() / 1000)
const date = new Date(created * 1000);
const offset = now - created;
if(offset < 43200) {
if (state.timeFormat === '12h') {
return date.toLocaleTimeString("en-US", {hour: 'numeric', minute:'2-digit'});
}
else {
return date.toLocaleTimeString("en-GB", {hour: 'numeric', minute:'2-digit'});
}
}
else if (offset < 31449600) {
if (state.dateFormat === 'mm/dd') {
return date.toLocaleDateString("en-US", {day: 'numeric', month:'numeric'});
}
else {
return date.toLocaleDateString("en-GB", {day: 'numeric', month:'numeric'});
}
}
else {
if (state.dateFormat === 'mm/dd') {
return date.toLocaleDateString("en-US");
}
else {
return date.toLocaleDateString("en-GB");
}
}
}
useEffect(() => {
const { strings, timeFormat, dateFormat } = display.state;
updateState({ strings, timeFormat, dateFormat });
}, [display.state]);
useEffect(() => {
const cards = Array.from(state.cards.values());
const hostCard = cards.find(entry => entry.cardId == state.cardId);
const profileRemoved = state.detail?.members ? state.detail.members.filter(member => state.profile?.guid != member.guid) : [];
const contactCards = profileRemoved.map(member => state.cards.get(member.guid));
const channelCards = contactCards.filter(member => Boolean(member));
const unknownContacts = contactCards.length - channelCards.length;
updateState({ hostCard, channelCards, unknownContacts });
}, [state.detail, state.cards, state.profile, state.cardId]);
useEffect(() => {
const focus = app.state.focus;
const { contact, identity } = app.state.session || { };
if (focus && contact && identity) {
const setCards = (cards: Card[]) => {
const contacts = new Map<string, Card>();
cards.forEach(card => {
contacts.set(card.guid, card);
});
updateState({ cards: contacts });
}
const setProfile = (profile: Profile) => {
updateState({ profile });
}
const setDetail = (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => {
const detail = focused ? focused.detail : null;
const cardId = focused.cardId;
const access = Boolean(detail);
const sealed = detail?.sealed;
const locked = detail?.locked;
const host = cardId == null;
const subject = detail?.data?.subject ? detail.data.subject : '';
const created = detail?.created ? getTimestamp(detail.created) : '';
updateState({ detail, editSubject: subject, subject, cardId, access, sealed, locked, host, created });
}
focus.addDetailListener(setDetail);
contact.addCardListener(setCards);
identity.addProfileListener(setProfile);
return () => {
focus.removeDetailListener(setDetail);
contact.removeCardListener(setCards);
identity.removeProfileListener(setProfile);
}
}
}, [app.state.focus, state.timeFormat, state.dateFormat]);
const actions = {
setEditSubject: (editSubject: string) => {
updateState({ editSubject });
},
undoSubject: () => {
updateState({ editSubject: state.subject });
},
saveSubject: () => {
console.log('saving subject');
},
}
return { state, actions }

View File

@ -270,14 +270,14 @@ export function Profile({ params, close }: { params: ProfileParams; close?: () =
{state.statusLabel === 'unknownStatus' && (
<div className={classes.actions}>
<div className={classes.action} onClick={applySave}>
<ActionIcon variant="subtle" loading={saving}>
<IconDeviceFloppy />
<ActionIcon variant="subtle" loading={saving} size="lg">
<IconDeviceFloppy size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.save}</Text>
</div>
<div className={classes.action} onClick={confirmReport}>
<ActionIcon variant="subtle" loading={reporting}>
<IconAlertHexagon />
<ActionIcon variant="subtle" loading={reporting} size="lg">
<IconAlertHexagon size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.report}</Text>
</div>
@ -286,26 +286,26 @@ export function Profile({ params, close }: { params: ProfileParams; close?: () =
{state.statusLabel === 'savedStatus' && (
<div className={classes.actions}>
<div className={classes.action} onClick={applyConnect}>
<ActionIcon variant="subtle" loading={connecting}>
<IconRoute2 />
<ActionIcon variant="subtle" loading={connecting} size="lg">
<IconRoute2 size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.connect}</Text>
</div>
<div className={classes.action} onClick={confirmRemove}>
<ActionIcon variant="subtle" loading={removing}>
<IconUserX />
<ActionIcon variant="subtle" loading={removing} size="lg">
<IconUserX size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.remove}</Text>
</div>
<div className={classes.action} onClick={confirmBlock}>
<ActionIcon variant="subtle" loading={blocking}>
<IconEyeOff />
<ActionIcon variant="subtle" loading={blocking} size="lg">
<IconEyeOff size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.block}</Text>
</div>
<div className={classes.action} onClick={confirmReport}>
<ActionIcon variant="subtle" loading={reporting}>
<IconAlertHexagon />
<ActionIcon variant="subtle" loading={reporting} size="lg">
<IconAlertHexagon size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.report}</Text>
</div>
@ -314,44 +314,44 @@ export function Profile({ params, close }: { params: ProfileParams; close?: () =
{state.statusLabel === 'pendingStatus' && (
<div className={classes.actions}>
<div className={classes.action} onClick={applyConfirm}>
<ActionIcon variant="subtle" loading={confirming}>
<IconDeviceFloppy />
<ActionIcon variant="subtle" loading={confirming} size="lg">
<IconDeviceFloppy size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.save}</Text>
</div>
<div className={classes.action} onClick={applyAccept}>
<ActionIcon variant="subtle" loading={accepting}>
<IconUserCheck />
<ActionIcon variant="subtle" loading={accepting} size="lg">
<IconUserCheck size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.accept}</Text>
</div>
<div className={classes.action} onClick={confirmIgnore}>
<ActionIcon variant="subtle" loading={ignoring}>
<IconVolumeOff />
<ActionIcon variant="subtle" loading={ignoring} size="lg">
<IconVolumeOff size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.ignore}</Text>
</div>
<div className={classes.action} onClick={confirmDeny}>
<ActionIcon variant="subtle" loading={denying}>
<IconArrowsCross />
<ActionIcon variant="subtle" loading={denying} size="lg">
<IconArrowsCross size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.deny}</Text>
</div>
<div className={classes.action} onClick={confirmRemove}>
<ActionIcon variant="subtle" loading={removing}>
<IconUserX />
<ActionIcon variant="subtle" loading={removing} size="lg">
<IconUserX size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.remove}</Text>
</div>
<div className={classes.action} onClick={confirmBlock}>
<ActionIcon variant="subtle" loading={blocking}>
<IconEyeOff />
<ActionIcon variant="subtle" loading={blocking} size="lg">
<IconEyeOff size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.block}</Text>
</div>
<div className={classes.action} onClick={confirmReport}>
<ActionIcon variant="subtle" loading={reporting}>
<IconAlertHexagon />
<ActionIcon variant="subtle" loading={reporting} size="lg">
<IconAlertHexagon size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.report}</Text>
</div>
@ -360,38 +360,38 @@ export function Profile({ params, close }: { params: ProfileParams; close?: () =
{state.statusLabel === 'requestedStatus' && (
<div className={classes.actions} onClick={applyAccept}>
<div className={classes.action}>
<ActionIcon variant="subtle" loading={accepting}>
<IconUserCheck />
<ActionIcon variant="subtle" loading={accepting} size="lg">
<IconUserCheck size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.accept}</Text>
</div>
<div className={classes.action} onClick={confirmIgnore}>
<ActionIcon variant="subtle" loading={ignoring}>
<IconVolumeOff />
<ActionIcon variant="subtle" loading={ignoring} size="lg">
<IconVolumeOff size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.ignore}</Text>
</div>
<div className={classes.action} onClick={confirmDeny}>
<ActionIcon variant="subtle" loading={denying}>
<IconArrowsCross />
<ActionIcon variant="subtle" loading={denying} size="lg">
<IconArrowsCross size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.deny}</Text>
</div>
<div className={classes.action} onClick={confirmRemove}>
<ActionIcon variant="subtle" loading={removing}>
<IconUserX />
<ActionIcon variant="subtle" loading={removing} size="lg">
<IconUserX size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.remove}</Text>
</div>
<div className={classes.action} onClick={confirmBlock}>
<ActionIcon variant="subtle" loading={blocking}>
<IconEyeOff />
<ActionIcon variant="subtle" loading={blocking} size="lg">
<IconEyeOff size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.block}</Text>
</div>
<div className={classes.action} onClick={confirmReport}>
<ActionIcon variant="subtle" loading={reporting}>
<IconAlertHexagon />
<ActionIcon variant="subtle" loading={reporting} size="lg">
<IconAlertHexagon size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.report}</Text>
</div>
@ -400,26 +400,26 @@ export function Profile({ params, close }: { params: ProfileParams; close?: () =
{state.statusLabel === 'connectingStatus' && (
<div className={classes.actions}>
<div className={classes.action} onClick={applyCancel}>
<ActionIcon variant="subtle" loading={canceling}>
<IconCancel />
<ActionIcon variant="subtle" loading={canceling} size="lg">
<IconCancel size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.cancel}</Text>
</div>
<div className={classes.action} onClick={confirmRemove}>
<ActionIcon variant="subtle" loading={removing}>
<IconUserX />
<ActionIcon variant="subtle" loading={removing} size="lg">
<IconUserX size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.remove}</Text>
</div>
<div className={classes.action} onClick={confirmBlock}>
<ActionIcon variant="subtle" loading={blocking}>
<IconEyeOff />
<ActionIcon variant="subtle" loading={blocking} size="lg">
<IconEyeOff size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.block}</Text>
</div>
<div className={classes.action} onClick={confirmReport}>
<ActionIcon variant="subtle" loading={reporting}>
<IconAlertHexagon />
<ActionIcon variant="subtle" loading={reporting} size="lg">
<IconAlertHexagon size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.report}</Text>
</div>
@ -428,26 +428,26 @@ export function Profile({ params, close }: { params: ProfileParams; close?: () =
{state.statusLabel === 'connectedStatus' && (
<div className={classes.actions}>
<div className={classes.action} onClick={confirmDisconnect}>
<ActionIcon variant="subtle" loading={disconnecting}>
<IconRouteX2 />
<ActionIcon variant="subtle" loading={disconnecting} size="lg">
<IconRouteX2 size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.disconnect}</Text>
</div>
<div className={classes.action} onClick={confirmRemove}>
<ActionIcon variant="subtle" loading={removing}>
<IconUserX />
<ActionIcon variant="subtle" loading={removing} size="lg">
<IconUserX size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.remove}</Text>
</div>
<div className={classes.action} onClick={confirmBlock}>
<ActionIcon variant="subtle" loading={blocking}>
<IconEyeOff />
<ActionIcon variant="subtle" loading={blocking} size="lg">
<IconEyeOff size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.block}</Text>
</div>
<div className={classes.action} onClick={confirmReport}>
<ActionIcon variant="subtle" loading={reporting}>
<IconAlertHexagon />
<ActionIcon variant="subtle" loading={reporting} size="lg">
<IconAlertHexagon size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.report}</Text>
</div>
@ -456,32 +456,32 @@ export function Profile({ params, close }: { params: ProfileParams; close?: () =
{state.statusLabel === 'offsyncStatus' && (
<div className={classes.actions}>
<div className={classes.action} onClick={applyResync}>
<ActionIcon variant="subtle" loading={resyncing}>
<IconRefresh />
<ActionIcon variant="subtle" loading={resyncing} size="lg">
<IconRefresh size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.resync}</Text>
</div>
<div className={classes.action} onClick={confirmDisconnect}>
<ActionIcon variant="subtle" loading={disconnecting}>
<IconRouteX2 />
<ActionIcon variant="subtle" loading={disconnecting} size="lg">
<IconRouteX2 size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.disconnect}</Text>
</div>
<div className={classes.action} onClick={confirmRemove}>
<ActionIcon variant="subtle" loading={removing}>
<IconUserX />
<ActionIcon variant="subtle" loading={removing} size="lg">
<IconUserX size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.remove}</Text>
</div>
<div className={classes.action} onClick={confirmBlock}>
<ActionIcon variant="subtle" loading={blocking}>
<IconEyeOff />
<ActionIcon variant="subtle" loading={blocking} size="lg">
<IconEyeOff size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.block}</Text>
</div>
<div className={classes.action} onClick={confirmReport}>
<ActionIcon variant="subtle" loading={reporting}>
<IconAlertHexagon />
<ActionIcon variant="subtle" loading={reporting} size="lg">
<IconAlertHexagon size="lg" />
</ActionIcon>
<Text className={classes.actionLabel}>{state.strings.report}</Text>
</div>
@ -492,14 +492,3 @@ export function Profile({ params, close }: { params: ProfileParams; close?: () =
)
}
//save - DeviceFloppy - save
//cancel - Cancel - cancel
//block - EyeOff - block
//report - AlertHexagon - report
//resync - Refresh - resync
//deny - ArrowsCross - deny
//ignore - VolumeOff - ignore
//accept - UserCheck - accept
//connect - Route2 - connect
//disconnect - RouteX2 - disconnect
//remove - UserX - remove

View File

@ -164,7 +164,7 @@ export function Session() {
<Profile params={profileParams} />
</div>
</Drawer>
<Drawer opened={details} onClose={closeDetails} withCloseButton={false} size="xs" padding="0" position="right">
<Drawer opened={details} onClose={closeDetails} withCloseButton={false} size="xs" padding="0" position="right" trapFocus={false}>
<div style={{ height: '100vh' }}>
<Details />
</div>

File diff suppressed because it is too large Load Diff

View File

@ -556,7 +556,7 @@ export class ContactModule implements Contact {
entry.item.unsealedDetail = null;
await this.unsealChannelDetail(cardId, id, entry.item);
if (this.focus) {
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary, members } = detail;
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary, members, created } = detail;
const sealed = dataType === 'sealed';
const channelData = sealed ? entry.item.unsealedDetail : data;
const focusDetail = {
@ -568,6 +568,7 @@ export class ContactModule implements Contact {
enableAudio,
enableVideo,
enableBinary,
created,
members: members.map(guid => ({ guid })),
}
this.focus.setDetail(cardId, id, focusDetail);
@ -715,7 +716,7 @@ export class ContactModule implements Contact {
this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, cardId, channelId, this.guid, { node, secure: !insecure, token: `${guid}.${token}` }, channelKey, sealEnabled, revision, markRead, flagTopic);
// set current detail
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary, members } = channelEntry.item.detail;
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary, members, created } = channelEntry.item.detail;
const sealed = dataType === 'sealed';
const channelData = sealed ? channelEntry.item.unsealedDetail : data;
const focusDetail = {
@ -727,6 +728,7 @@ export class ContactModule implements Contact {
enableAudio,
enableVideo,
enableBinary,
created,
members: members.map(guid => ({ guid })),
}
this.focus.setDetail(cardId, channelId, focusDetail);
@ -1122,7 +1124,7 @@ export class ContactModule implements Contact {
const { data } = await this.crypto.aesDecrypt(subjectEncrypted, subjectIv, item.channelKey);
item.unsealedDetail = data;
if (this.focus) {
const { dataType, enableImage, enableAudio, enableVideo, enableBinary, members } = item.detail;
const { dataType, enableImage, enableAudio, enableVideo, enableBinary, members, created } = item.detail;
const focusDetail = {
sealed: true,
locked: false,
@ -1132,6 +1134,7 @@ export class ContactModule implements Contact {
enableAudio,
enableVideo,
enableBinary,
created,
members: members.map(guid => ({ guid })),
}
this.focus.setDetail(cardId, channelId, focusDetail);

View File

@ -152,7 +152,7 @@ export class StreamModule {
await this.unsealChannelDetail(id, entry.item);
entry.channel = this.setChannel(id, entry.item);
if (this.focus) {
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary, members } = detail;
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary, members, created } = detail;
const sealed = dataType === 'sealed';
const channelData = sealed ? entry.item.unsealedDetail : data;
const focusDetail = {
@ -164,6 +164,7 @@ export class StreamModule {
enableAudio,
enableVideo,
enableBinary,
created,
members: members.map(guid => ({ guid })),
}
this.focus.setDetail(null, id, focusDetail);
@ -440,7 +441,7 @@ export class StreamModule {
this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, null, channelId, this.guid, { node, secure, token }, channelKey, sealEnabled, revision, markRead, flagTopic);
if (entry) {
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary, members } = entry.item.detail;
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary, members, created } = entry.item.detail;
const sealed = dataType === 'sealed';
const channelData = sealed ? entry.item.unsealedDetail : data;
const focusDetail = {
@ -452,6 +453,7 @@ export class StreamModule {
enableAudio,
enableVideo,
enableBinary,
created,
members: members.map(guid => ({ guid })),
}
this.focus.setDetail(null, channelId, focusDetail);
@ -605,7 +607,7 @@ export class StreamModule {
const { data } = await this.crypto.aesDecrypt(subjectEncrypted, subjectIv, item.channelKey);
item.unsealedDetail = data;
if (this.focus) {
const { dataType, enableImage, enableAudio, enableVideo, enableBinary, members } = item.detail;
const { dataType, enableImage, enableAudio, enableVideo, enableBinary, members, created } = item.detail;
const focusDetail = {
sealed: true,
locked: false,
@ -615,6 +617,7 @@ export class StreamModule {
enableAudio,
enableVideo,
enableBinary,
created,
members: members.map(guid => ({ guid })),
}
this.focus.setDetail(null, channelId, focusDetail);

View File

@ -75,6 +75,7 @@ export type FocusDetail = {
enableAudio: boolean;
enableVideo: boolean;
enableBinary: boolean;
created: number;
members: Member[];
}