import React, { FormEventHandler, ReactNode, useCallback, useEffect, useState } from 'react'; import { Box, Text, IconButton, Icon, Icons, Chip, Input, Button, color, Spinner, toRem, Overlay, OverlayBackdrop, OverlayCenter, } from 'folds'; import { CryptoApi } from 'matrix-js-sdk/lib/crypto-api'; import FocusTrap from 'focus-trap-react'; import { IMyDevice, MatrixError } from 'matrix-js-sdk'; import { SettingTile } from '../../../components/setting-tile'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { timeDayMonYear, timeHourMinute, today, yesterday } from '../../../utils/time'; import { BreakWord } from '../../../styles/Text.css'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../styles.css'; import { LogoutDialog } from '../../../components/LogoutDialog'; import { stopPropagation } from '../../../utils/keyboard'; export function DeviceTilePlaceholder() { return ( ); } function DeviceActiveTime({ ts }: { ts: number }) { return ( {'Last activity: '} <> {today(ts) && 'Today'} {yesterday(ts) && 'Yesterday'} {!today(ts) && !yesterday(ts) && timeDayMonYear(ts)} {timeHourMinute(ts)} ); } function DeviceDetails({ device }: { device: IMyDevice }) { return ( <> {typeof device.device_id === 'string' && ( Device ID: {device.device_id} )} {typeof device.last_seen_ip === 'string' && ( IP Address: {device.last_seen_ip} )} ); } type DeviceKeyDetailsProps = { crypto: CryptoApi; }; export function DeviceKeyDetails({ crypto }: DeviceKeyDetailsProps) { const [keysState, loadKeys] = useAsyncCallback( useCallback(() => { const keys = crypto.getOwnDeviceKeys(); return keys; }, [crypto]) ); useEffect(() => { loadKeys(); }, [loadKeys]); if (keysState.status === AsyncStatus.Error) return null; return ( Device Key:{' '} {keysState.status === AsyncStatus.Success ? keysState.data.ed25519 : 'loading...'} ); } type DeviceRenameProps = { device: IMyDevice; onCancel: () => void; onRename: () => void; refreshDeviceList: () => Promise; }; function DeviceRename({ device, onCancel, onRename, refreshDeviceList }: DeviceRenameProps) { const mx = useMatrixClient(); const [renameState, rename] = useAsyncCallback( useCallback( async (name: string) => { await mx.setDeviceDetails(device.device_id, { display_name: name }); await refreshDeviceList(); }, [mx, device.device_id, refreshDeviceList] ) ); const renaming = renameState.status === AsyncStatus.Loading; useEffect(() => { if (renameState.status === AsyncStatus.Success) { onRename(); } }, [renameState, onRename]); const handleSubmit: FormEventHandler = (evt) => { evt.preventDefault(); if (renaming) return; const target = evt.target as HTMLFormElement | undefined; const nameInput = target?.nameInput as HTMLInputElement | undefined; if (!nameInput) return; const deviceName = nameInput.value.trim(); if (!deviceName || deviceName === device.display_name) return; rename(deviceName); }; return ( Device Name {renameState.status === AsyncStatus.Error ? ( {renameState.error.message} ) : ( Device names are visible to public. )} ); } export function DeviceLogoutBtn() { const [prompt, setPrompt] = useState(false); const handleClose = () => setPrompt(false); return ( <> setPrompt(true)}> Logout {prompt && ( }> )} ); } type DeviceDeleteBtnProps = { deviceId: string; deleted: boolean; onDeleteToggle: (deviceId: string) => void; disabled?: boolean; }; export function DeviceDeleteBtn({ deviceId, deleted, onDeleteToggle, disabled, }: DeviceDeleteBtnProps) { return deleted ? ( onDeleteToggle(deviceId)} disabled={disabled} > Undo ) : ( onDeleteToggle(deviceId)} disabled={disabled} > ); } type DeviceTileProps = { device: IMyDevice; deleted?: boolean; refreshDeviceList: () => Promise; disabled?: boolean; options?: ReactNode; children?: ReactNode; }; export function DeviceTile({ device, deleted, refreshDeviceList, disabled, options, children, }: DeviceTileProps) { const activeTs = device.last_seen_ts; const [details, setDetails] = useState(false); const [edit, setEdit] = useState(false); const handleRename = useCallback(() => { setEdit(false); }, []); return ( <> setDetails(!details)} > } after={ !edit && ( {options} {!deleted && ( setEdit(true)} disabled={disabled} > Edit )} ) } > {device.display_name ?? device.device_id} {typeof activeTs === 'number' && } {details && ( <> {children} )} {edit && ( setEdit(false)} onRename={handleRename} refreshDeviceList={refreshDeviceList} /> )} ); }