mirror of
https://github.com/balzack/databag.git
synced 2025-04-23 10:05:19 +00:00
rendering details screen
This commit is contained in:
parent
275629a4a0
commit
257568e729
@ -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: 'Нет подключенных контактов',
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
13424
app/client/web/yarn.lock
13424
app/client/web/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -75,6 +75,7 @@ export type FocusDetail = {
|
||||
enableAudio: boolean;
|
||||
enableVideo: boolean;
|
||||
enableBinary: boolean;
|
||||
created: number;
|
||||
members: Member[];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user