rendering contact info

This commit is contained in:
Roland Osborne 2024-10-15 17:04:27 -07:00
parent 00eb0b0d64
commit 20fe796a1c
6 changed files with 505 additions and 55 deletions

View File

@ -124,6 +124,30 @@ const theme = createTheme({
'#aaaaaa',
'#aaaaaa',
],
'dark-status': [
'#555555',
'#cccccc',
'#aaaa44',
'#aa44aa',
'#22aacc',
'#44aa44',
'#dd6633',
'#888888',
'#888888',
'#888888',
],
'light-status': [
'#555555',
'#cccccc',
'#aaaa44',
'#aa44aa',
'#22aacc',
'#44aa44',
'#dd6633',
'#888888',
'#888888',
'#888888',
],
dbgreen: virtualColor({
name: 'dbgreen',
dark: 'dark-databag-green',
@ -139,6 +163,11 @@ const theme = createTheme({
dark: 'dark-surface',
light: 'light-surface',
}),
status: virtualColor({
name: 'status',
dark: 'dark-status',
light: 'light-status',
}),
text: virtualColor({
name: 'text',
dark: 'dark-text',

View File

@ -92,6 +92,14 @@ export const en = {
confirmedTip: 'Disconnected Contact',
unsavedTip: 'Unknown Contact',
unknownStatus: 'Unsaved Contact',
savedStatus: 'Saved Contact',
pendingStatus: 'Unknown Contact Request',
connectingStatus: 'Connection Requested',
requestedStatus: 'Connection Requested by Contact',
connectedStatus: 'Connected Contact',
offsyncStatus: 'Offsync Contact',
actions: 'Actions',
resync: 'Resync',
connect: 'Connect',
@ -107,6 +115,12 @@ export const en = {
cancelRequest: 'Cancel Request',
resyncContact: 'Resync Contact',
block: 'Block',
report: 'Report',
deny: 'Deny',
ignore: 'Ignore',
accept: 'Accept',
login: 'Login',
create: 'Create',
createAccount: 'Create Account',
@ -235,6 +249,19 @@ export const fr = {
forgotPassword: 'Mot de Passe Oublié',
manageTopics: 'Gérer la clé de sécurité',
unknownStatus: 'Contact Inconnu',
savedStatus: 'Contact Enregistré',
pendingStatus: 'Demande de Contact Inconnue',
connectingStatus: 'Demande de Connexion Envoyée',
requestedStatus: 'Demande de Connexion par le Contact',
connectedStatus: 'Contact Connecté',
offsyncStatus: 'Contact Désynchronisé',
block: 'Bloquer',
report: 'Signaler',
deny: 'Refuser',
ignore: 'Ignorer',
accept: 'Accepter',
sealUnlock: 'Déverrouiller la clé de scellement pour accéder aux messages chiffrés de bout en bout',
sealForget: 'Oublier la clé de scellement pour révoquer l\'accès aux messages chiffrés de bout en bout pour cet appareil uniquement',
@ -460,6 +487,20 @@ export const sp = {
forgotPassword: 'Contraseña Olvidada',
manageTopics: 'Administrar clave de seguridad',
unknownStatus: 'Contacto Desconocido',
savedStatus: 'Contacto Guardado',
pendingStatus: 'Solicitud de Contacto Desconocido',
connectingStatus: 'Solicitud de Conexión Enviada',
requestedStatus: 'Solicitud de Conexión por el Contacto',
connectedStatus: 'Contacto Conectado',
offsyncStatus: 'Contacto Fuera de Sincronización',
block: 'Bloquear',
report: 'Reportar',
deny: 'Denegar',
ignore: 'Ignorar',
accept: 'Aceptar',
sealUnlock: 'Desbloquear la clave de sellado para acceder a los mensajes cifrados de extremo a extremo',
sealForget: 'Olvidar la clave de sellado para revocar el acceso a los mensajes cifrados de extremo a extremo solo en este dispositivo',
sealDelete: 'Eliminar la clave de sellado revocará permanentemente el acceso a todos los mensajes cifrados de extremo a extremo desde todos los dispositivos',
@ -682,6 +723,20 @@ export const pt = {
forgotPassword: 'Senha Esquecida',
manageTopics: 'Gerenciar Chave de Selagem',
unknownStatus: 'Contato Desconhecido',
savedStatus: 'Contato Salvo',
pendingStatus: 'Solicitação de Contato Desconhecido',
connectingStatus: 'Solicitação de Conexão Enviada',
requestedStatus: 'Solicitação de Conexão pelo Contato',
connectedStatus: 'Contato Conectado',
offsyncStatus: 'Contato Fora de Sincronização',
block: 'Bloquear',
report: 'Denunciar',
deny: 'Negar',
ignore: 'Ignorar',
accept: 'Aceitar',
sealUnlock: 'Desbloquear a chave de selagem para acessar mensagens criptografadas de ponta a ponta',
sealForget: 'Esquecer a chave de selagem para revogar o acesso às mensagens criptografadas de ponta a ponta apenas para este dispositivo',
sealDelete: 'Excluir a chave de selagem revogará permanentemente o acesso a todas as mensagens criptografadas de ponta a ponta de todos os dispositivos',
@ -904,6 +959,20 @@ export const de = {
forgotPassword: 'Passwort Vergessen',
manageTopics: 'Sicherheitsschlüssel verwalten',
unknownStatus: 'Unbekannter Kontakt',
savedStatus: 'Gespeicherter Kontakt',
pendingStatus: 'Unbekannte Kontaktanfrage',
connectingStatus: 'Verbindungsanfrage Gesendet',
requestedStatus: 'Verbindungsanfrage vom Kontakt',
connectedStatus: 'Verbunden Kontakt',
offsyncStatus: 'Unsynchronisierter Kontakt',
block: 'Blockieren',
report: 'Melden',
deny: 'Ablehnen',
ignore: 'Ignorieren',
accept: 'Akzeptieren',
sealUnlock: 'Entsperren Sie den Versiegelungsschlüssel, um auf Ende-zu-Ende-verschlüsselte Nachrichten zuzugreifen',
sealForget: 'Versiegelungsschlüssel vergessen, um den Zugriff auf Ende-zu-Ende-verschlüsselte Nachrichten nur für dieses Gerät zu widerrufen',
sealDelete: 'Das Löschen des Versiegelungsschlüssels widerruft dauerhaft den Zugriff auf alle Ende-zu-Ende-verschlüsselten Nachrichten von allen Geräten',
@ -1127,6 +1196,20 @@ export const ru = {
forgotPassword: 'Пароль забыт',
manageTopics: 'Управление ключом запечатывания',
unknownStatus: 'Неизвестный Контакт',
savedStatus: 'Сохранённый Контакт',
pendingStatus: 'Запрос Неизвестного Контакта',
connectingStatus: 'Запрос на Подключение Отправлен',
requestedStatus: 'Запрос на Подключение от Контакта',
connectedStatus: 'Подключённый Контакт',
offsyncStatus: 'Несинхронизированный Контакт',
block: 'Заблокировать',
report: 'Пожаловаться',
deny: 'Отклонить',
ignore: 'Игнорировать',
accept: 'Принять',
sealUnlock: 'Разблокировать ключ запечатывания для доступа к сообщениям с сквозным шифрованием',
sealForget: 'Забыть ключ запечатывания, чтобы отозвать доступ к сообщениям с сквозным шифрованием только для этого устройства',
sealDelete: 'Удаление ключа запечатывания навсегда отзовет доступ ко всем сообщениям с сквозным шифрованием со всех устройств',

View File

@ -5,8 +5,19 @@
padding-left: 16px;
padding-right: 16px;
padding-top: 8px;
padding-bottom: 8px;
gap: 2px;
padding-bottom: 4px;
height: 100%;
width: 100%;
.detail {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
flex-grow: 1;
width: 100%;
}
.header {
display: flex;
@ -91,5 +102,66 @@
color: var(--mantine-color-text-9);
}
}
.actions {
display: flex;
flex-direction: row;
flex-grow: 1;
gap: 12px;
padding-top: 16px;
.action {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
.actionLabel {
font-size: 8px;
color: var(--mantine-color-dbgreen-1);
}
}
}
.status {
flex-shrink: 1;
display: flex;
align-items: center;
justify-content: center;
.unknownStatus {
color: var(--mantine-color-status-0);
font-size: 12px;
}
.savedStatus {
color: var(--mantine-color-status-1);
font-size: 12px;
}
.pendingStatus {
color: var(--mantine-color-status-2);
font-size: 12px;
}
.requestedStatus {
color: var(--mantine-color-status-3);
font-size: 12px;
}
.connectingStatus {
color: var(--mantine-color-status-4);
font-size: 12px;
}
.connectedStatus {
color: var(--mantine-color-status-5);
font-size: 12px;
}
.offsyncStatus {
color: var(--mantine-color-status-6);
font-size: 12px;
}
}
}

View File

@ -1,10 +1,12 @@
import React, {useEffect} from 'react';
import { useContact } from './useContact.hook';
import classes from './Contact.module.css';
import { IconX, IconMapPin, IconBook } from '@tabler/icons-react';
import { IconX, IconMapPin, IconBook, IconUserX, IconRouteX2, IconRoute2, IconCircleCheck, IconVolumeOff, IconArrowsCross, IconRefresh, IconAlertHexagon, IconEyeOff, IconCancel, IconDeviceFloppy } from '@tabler/icons-react';
import {
Text,
Image,
ActionIcon,
Button,
} from '@mantine/core'
export type ContactParams = {
@ -25,54 +27,291 @@ export function Contact({ params, close }: { params: ContactParams, close?: ()=>
return (
<div className={classes.contact}>
<div className={classes.header}>
{ close && (
<IconX size={28} className={classes.match} />
)}
<Text className={classes.label}>{`${state.handle}${state.node ? '/' + state.node : ''}`}</Text>
{ close && (
<IconX size={30} className={classes.close} onClick={close} />
)}
</div>
<div className={classes.image}>
<Image radius="md" src={state.imageUrl} />
</div>
<div className={classes.divider} />
{!state.name && (
<Text className={classes.nameUnset}>{state.strings.name}</Text>
)}
{state.name && (
<Text className={classes.nameSet}>{state.name}</Text>
)}
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconMapPin />
<div className={classes.detail}>
<div className={classes.header}>
{ close && (
<IconX size={28} className={classes.match} />
)}
<Text className={classes.label}>{`${state.handle}${state.node ? '/' + state.node : ''}`}</Text>
{ close && (
<IconX size={30} className={classes.close} onClick={close} />
)}
</div>
{!state.location && (
<Text className={classes.entryUnset}>
{state.strings.location}
</Text>
<div className={classes.image}>
<Image radius="md" src={state.imageUrl} />
</div>
<div className={classes.divider} />
{!state.name && (
<Text className={classes.nameUnset}>{state.strings.name}</Text>
)}
{state.location && (
<Text className={classes.entrySet}>{state.location}</Text>
{state.name && (
<Text className={classes.nameSet}>{state.name}</Text>
)}
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconMapPin />
</div>
{!state.location && (
<Text className={classes.entryUnset}>
{state.strings.location}
</Text>
)}
{state.location && (
<Text className={classes.entrySet}>{state.location}</Text>
)}
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconBook />
</div>
{!state.description && (
<Text className={classes.entryUnset}>
{state.strings.description}
</Text>
)}
{state.description && (
<Text className={classes.entrySet}>
{state.description}
</Text>
)}
</div>
<div className={classes.divider} />
{ state.statusLabel === 'unknownStatus' && (
<div className={classes.actions}>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconDeviceFloppy />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.save }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconAlertHexagon />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.report }</Text>
</div>
</div>
)}
{ state.statusLabel === 'savedStatus' && (
<div className={classes.actions}>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconRoute2 />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.connect }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconUserX />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.remove }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconEyeOff />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.block }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconAlertHexagon />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.report }</Text>
</div>
</div>
)}
{ state.statusLabel === 'pendingStatus' && (
<div className={classes.actions}>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconDeviceFloppy />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.save }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconCircleCheck />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.accept }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconVolumeOff />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.ignore }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconArrowsCross />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.deny }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconUserX />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.remove }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconEyeOff />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.block }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconAlertHexagon />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.report }</Text>
</div>
</div>
)}
{ state.statusLabel === 'requestedStatus' && (
<div className={classes.actions}>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconCircleCheck />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.accept }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconVolumeOff />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.ignore }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconArrowsCross />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.deny }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconUserX />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.remove }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconEyeOff />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.block }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconAlertHexagon />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.report }</Text>
</div>
</div>
)}
{ state.statusLabel === 'connectingStatus' && (
<div className={classes.actions}>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconCancel />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.cancel }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconUserX />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.remove }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconEyeOff />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.block }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconAlertHexagon />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.report }</Text>
</div>
</div>
)}
{ state.statusLabel === 'connectedStatus' && (
<div className={classes.actions}>
<ActionIcon variant="subtle">
<IconRouteX2 size={32} />
</ActionIcon>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconUserX />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.remove }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconEyeOff />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.block }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconAlertHexagon />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.report }</Text>
</div>
</div>
)}
{ state.statusLabel === 'offsyncStatus' && (
<div className={classes.actions}>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconRefresh />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.resync }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconRouteX2 />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.disconnect }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconUserX />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.remove }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconEyeOff />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.block }</Text>
</div>
<div className={classes.action}>
<ActionIcon variant="subtle">
<IconAlertHexagon />
</ActionIcon>
<Text className={classes.actionLabel}>{ state.strings.report }</Text>
</div>
</div>
)}
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconBook />
</div>
{!state.description && (
<Text className={classes.entryUnset}>
{state.strings.description}
</Text>
)}
{state.description && (
<Text className={classes.entrySet}>
{state.description}
</Text>
)}
<div className={classes.status}>
<Text className={classes[state.statusLabel]}>{ state.strings[state.statusLabel] }</Text>
</div>
</div>
);
}
//save - DeviceFloppy - save
//cancel - Cancel - cancel
//block - EyeOff - block
//report - AlertHexagon - report
//resync - Refresh - resync
//deny - ArrowsCross - deny
//ignore - VolumeOff - ignore
//accept - CircleCheck - accept
//connect - Route2 - connect
//disconnect - RouteX2 - disconnect
//remove - UserX - remove

View File

@ -22,6 +22,7 @@ export function useContact(params: ContactParams) {
cardId: null as string | null,
status: '',
offsync: false,
statusLabel: '',
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -43,13 +44,39 @@ export function useContact(params: ContactParams) {
updateState({ guid, handle, node, name, location, description, imageUrl, cardId, status, offsync });
}, [params]);
const getStatusLabel = (card?: Card) => {
if (card) {
const { status, offsync } = card;
if (status === 'confirmed') {
return 'savedStatus'
}
if (status === 'pending') {
return 'pendingStatus'
}
if (status === 'requested') {
return 'requestedStatus'
}
if (status === 'connecting') {
return 'connectingStatus'
}
if (status === 'connected' && !offsync) {
return 'connectedStatus'
}
if (status === 'connected' && offsync) {
return 'offsyncStatus'
}
}
return 'unknownStatus'
}
useEffect(() => {
const card = state.cards.find(card => card.guid === state.guid);
const statusLabel = getStatusLabel(card);
if (card) {
const { handle, node, name, location, description, imageUrl, cardId, status, offsync } = card;
updateState({ handle, node, name, location, description, imageUrl, cardId, status, offsync });
updateState({ handle, node, name, location, description, imageUrl, cardId, status, offsync, statusLabel });
} else {
updateState({ cardId: null, status: '', offsync: false });
updateState({ cardId: null, status: '', offsync: false, statusLabel });
}
}, [state.cards, state.guid]);

View File

@ -44,31 +44,31 @@
overscroll-behavior: none;
.requested {
border-right: 5px solid #aa44aa;
border-right: 5px solid var(--mantine-color-status-3);
}
.connecting {
border-right: 5px solid #22aacc;
border-right: 5px solid var(--mantine-color-status-4);
}
.pending {
border-right: 5px solid #aaaa44;
border-right: 5px solid var(--mantine-color-status-2);
}
.connected {
border-right: 5px solid #44aa44;
border-right: 5px solid var(--mantine-color-status-5);
}
.confirmed {
border-right: 5px solid #cccccc;
border-right: 5px solid var(--mantine-color-status-1);
}
.unknown {
border-right: 5px solid #555555;
border-right: 5px solid var(--mantine-color-status-0);
}
.offsync {
border-right: 5px solid #dd6633;
border-right: 5px solid var(--mantine-color-status-6);
}
.card {