mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-03-13 06:30:01 +01:00
move other devices in component
This commit is contained in:
parent
d817e112b8
commit
18357cee95
3 changed files with 177 additions and 160 deletions
|
@ -229,7 +229,7 @@ export function DeviceDeleteBtn({
|
|||
|
||||
type DeviceTileProps = {
|
||||
device: IMyDevice;
|
||||
deleted: boolean;
|
||||
deleted?: boolean;
|
||||
refreshDeviceList: () => Promise<void>;
|
||||
disabled?: boolean;
|
||||
options?: ReactNode;
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { Box, Text, IconButton, Icon, Icons, Scroll, Button, Spinner, Menu, config } from 'folds';
|
||||
import { AuthDict, MatrixError } from 'matrix-js-sdk';
|
||||
import React from 'react';
|
||||
import { Box, Text, IconButton, Icon, Icons, Scroll, Button } from 'folds';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
import { SequenceCardStyle } from '../styles.css';
|
||||
import { SettingTile } from '../../../components/setting-tile';
|
||||
import { useDeviceList } from '../../../hooks/useDeviceList';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { AsyncState, AsyncStatus, useAsync } from '../../../hooks/useAsyncCallback';
|
||||
import { ActionUIA, ActionUIAFlowsLoader } from '../../../components/ActionUIA';
|
||||
import { useUIAMatrixError } from '../../../hooks/useUIAFlows';
|
||||
import { LocalBackup } from './LocalBackup';
|
||||
import { DeviceDeleteBtn, DeviceLogoutBtn, DeviceTile, DeviceTilePlaceholder } from './DeviceTile';
|
||||
import { DeviceLogoutBtn, DeviceTile, DeviceTilePlaceholder } from './DeviceTile';
|
||||
import { OtherDevices } from './OtherDevices';
|
||||
|
||||
function DevicesPlaceholder() {
|
||||
return (
|
||||
|
@ -34,52 +31,6 @@ export function Devices({ requestClose }: DevicesProps) {
|
|||
: undefined;
|
||||
const otherDevices = devices?.filter((device) => device.device_id !== currentDeviceId);
|
||||
|
||||
const [deleted, setDeleted] = useState<Set<string>>(new Set());
|
||||
|
||||
const handleToggleDelete = useCallback((deviceId: string) => {
|
||||
setDeleted((deviceIds) => {
|
||||
const newIds = new Set(deviceIds);
|
||||
if (newIds.has(deviceId)) {
|
||||
newIds.delete(deviceId);
|
||||
} else {
|
||||
newIds.add(deviceId);
|
||||
}
|
||||
return newIds;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const [deleteState, setDeleteState] = useState<AsyncState<void, MatrixError>>({
|
||||
status: AsyncStatus.Idle,
|
||||
});
|
||||
|
||||
const deleteDevices = useAsync(
|
||||
useCallback(
|
||||
async (authDict?: AuthDict) => {
|
||||
await mx.deleteMultipleDevices(Array.from(deleted), authDict);
|
||||
},
|
||||
[mx, deleted]
|
||||
),
|
||||
useCallback(
|
||||
(state: typeof deleteState) => {
|
||||
if (state.status === AsyncStatus.Success) {
|
||||
setDeleted(new Set());
|
||||
refreshDeviceList();
|
||||
}
|
||||
setDeleteState(state);
|
||||
},
|
||||
[refreshDeviceList]
|
||||
)
|
||||
);
|
||||
const [authData, deleteError] = useUIAMatrixError(
|
||||
deleteState.status === AsyncStatus.Error ? deleteState.error : undefined
|
||||
);
|
||||
const deleting = deleteState.status === AsyncStatus.Loading || authData !== undefined;
|
||||
|
||||
const handleCancelDelete = () => setDeleted(new Set());
|
||||
const handleCancelAuth = useCallback(() => {
|
||||
setDeleteState({ status: AsyncStatus.Idle });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader outlined={false}>
|
||||
|
@ -100,76 +51,6 @@ export function Devices({ requestClose }: DevicesProps) {
|
|||
<Scroll hideTrack visibility="Hover">
|
||||
<PageContent>
|
||||
<Box direction="Column" gap="700">
|
||||
{deleted.size > 0 && (
|
||||
<Menu
|
||||
style={{
|
||||
position: 'sticky',
|
||||
padding: config.space.S200,
|
||||
paddingLeft: config.space.S400,
|
||||
top: config.space.S400,
|
||||
left: config.space.S400,
|
||||
right: 0,
|
||||
zIndex: 1,
|
||||
}}
|
||||
variant="Critical"
|
||||
>
|
||||
<Box alignItems="Center" gap="400">
|
||||
<Box grow="Yes" direction="Column">
|
||||
{deleteError ? (
|
||||
<Text size="T200">
|
||||
<b>Failed to logout devices! Please try again. {deleteError.message}</b>
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="T200">
|
||||
<b>Logout from selected devices. ({deleted.size} selected)</b>
|
||||
</Text>
|
||||
)}
|
||||
{authData && (
|
||||
<ActionUIAFlowsLoader
|
||||
authData={authData}
|
||||
unsupported={() => (
|
||||
<Text size="T200">
|
||||
Authentication steps to perform this action are not supported by
|
||||
client.
|
||||
</Text>
|
||||
)}
|
||||
>
|
||||
{(ongoingFlow) => (
|
||||
<ActionUIA
|
||||
authData={authData}
|
||||
ongoingFlow={ongoingFlow}
|
||||
action={deleteDevices}
|
||||
onCancel={handleCancelAuth}
|
||||
/>
|
||||
)}
|
||||
</ActionUIAFlowsLoader>
|
||||
)}
|
||||
</Box>
|
||||
<Box shrink="No" gap="200">
|
||||
<Button
|
||||
size="300"
|
||||
variant="Critical"
|
||||
fill="None"
|
||||
radii="300"
|
||||
disabled={deleting}
|
||||
onClick={handleCancelDelete}
|
||||
>
|
||||
<Text size="B300">Cancel</Text>
|
||||
</Button>
|
||||
<Button
|
||||
size="300"
|
||||
variant="Critical"
|
||||
radii="300"
|
||||
disabled={deleting}
|
||||
before={deleting && <Spinner variant="Critical" fill="Solid" size="100" />}
|
||||
onClick={() => deleteDevices()}
|
||||
>
|
||||
<Text size="B300">Logout</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Menu>
|
||||
)}
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Security</Text>
|
||||
<SequenceCard
|
||||
|
@ -196,15 +77,13 @@ export function Devices({ requestClose }: DevicesProps) {
|
|||
{currentDevice ? (
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant={deleted.has(currentDevice.device_id) ? 'Critical' : 'SurfaceVariant'}
|
||||
variant="SurfaceVariant"
|
||||
direction="Column"
|
||||
gap="400"
|
||||
>
|
||||
<DeviceTile
|
||||
device={currentDevice}
|
||||
deleted={deleted.has(currentDevice.device_id)}
|
||||
refreshDeviceList={refreshDeviceList}
|
||||
disabled={deleting}
|
||||
options={<DeviceLogoutBtn />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
|
@ -213,39 +92,8 @@ export function Devices({ requestClose }: DevicesProps) {
|
|||
)}
|
||||
</Box>
|
||||
{devices === null && <DevicesPlaceholder />}
|
||||
{otherDevices && otherDevices.length > 0 && (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Others</Text>
|
||||
{otherDevices
|
||||
.sort((d1, d2) => {
|
||||
if (!d1.last_seen_ts || !d2.last_seen_ts) return 0;
|
||||
return d1.last_seen_ts < d2.last_seen_ts ? 1 : -1;
|
||||
})
|
||||
.map((device) => (
|
||||
<SequenceCard
|
||||
key={device.device_id}
|
||||
className={SequenceCardStyle}
|
||||
variant={deleted.has(device.device_id) ? 'Critical' : 'SurfaceVariant'}
|
||||
direction="Column"
|
||||
gap="400"
|
||||
>
|
||||
<DeviceTile
|
||||
device={device}
|
||||
deleted={deleted.has(device.device_id)}
|
||||
refreshDeviceList={refreshDeviceList}
|
||||
disabled={deleting}
|
||||
options={
|
||||
<DeviceDeleteBtn
|
||||
deviceId={device.device_id}
|
||||
deleted={deleted.has(device.device_id)}
|
||||
onDeleteToggle={handleToggleDelete}
|
||||
disabled={deleting}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SequenceCard>
|
||||
))}
|
||||
</Box>
|
||||
{otherDevices && (
|
||||
<OtherDevices devices={otherDevices} refreshDeviceList={refreshDeviceList} />
|
||||
)}
|
||||
<LocalBackup />
|
||||
</Box>
|
||||
|
|
169
src/app/features/settings/devices/OtherDevices.tsx
Normal file
169
src/app/features/settings/devices/OtherDevices.tsx
Normal file
|
@ -0,0 +1,169 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { Box, Button, config, Menu, Spinner, Text } from 'folds';
|
||||
import { AuthDict, IMyDevice, MatrixError } from 'matrix-js-sdk';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
import { SequenceCardStyle } from '../styles.css';
|
||||
import { ActionUIA, ActionUIAFlowsLoader } from '../../../components/ActionUIA';
|
||||
import { DeviceDeleteBtn, DeviceTile } from './DeviceTile';
|
||||
import { AsyncState, AsyncStatus, useAsync } from '../../../hooks/useAsyncCallback';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { useUIAMatrixError } from '../../../hooks/useUIAFlows';
|
||||
|
||||
type OtherDevicesProps = {
|
||||
devices: IMyDevice[];
|
||||
refreshDeviceList: () => Promise<void>;
|
||||
};
|
||||
export function OtherDevices({ devices, refreshDeviceList }: OtherDevicesProps) {
|
||||
const mx = useMatrixClient();
|
||||
const [deleted, setDeleted] = useState<Set<string>>(new Set());
|
||||
|
||||
const handleToggleDelete = useCallback((deviceId: string) => {
|
||||
setDeleted((deviceIds) => {
|
||||
const newIds = new Set(deviceIds);
|
||||
if (newIds.has(deviceId)) {
|
||||
newIds.delete(deviceId);
|
||||
} else {
|
||||
newIds.add(deviceId);
|
||||
}
|
||||
return newIds;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const [deleteState, setDeleteState] = useState<AsyncState<void, MatrixError>>({
|
||||
status: AsyncStatus.Idle,
|
||||
});
|
||||
|
||||
const deleteDevices = useAsync(
|
||||
useCallback(
|
||||
async (authDict?: AuthDict) => {
|
||||
await mx.deleteMultipleDevices(Array.from(deleted), authDict);
|
||||
},
|
||||
[mx, deleted]
|
||||
),
|
||||
useCallback(
|
||||
(state: typeof deleteState) => {
|
||||
if (state.status === AsyncStatus.Success) {
|
||||
setDeleted(new Set());
|
||||
refreshDeviceList();
|
||||
}
|
||||
setDeleteState(state);
|
||||
},
|
||||
[refreshDeviceList]
|
||||
)
|
||||
);
|
||||
const [authData, deleteError] = useUIAMatrixError(
|
||||
deleteState.status === AsyncStatus.Error ? deleteState.error : undefined
|
||||
);
|
||||
const deleting = deleteState.status === AsyncStatus.Loading || authData !== undefined;
|
||||
|
||||
const handleCancelDelete = () => setDeleted(new Set());
|
||||
const handleCancelAuth = useCallback(() => {
|
||||
setDeleteState({ status: AsyncStatus.Idle });
|
||||
}, []);
|
||||
|
||||
return devices.length > 0 ? (
|
||||
<>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Others</Text>
|
||||
{devices
|
||||
.sort((d1, d2) => {
|
||||
if (!d1.last_seen_ts || !d2.last_seen_ts) return 0;
|
||||
return d1.last_seen_ts < d2.last_seen_ts ? 1 : -1;
|
||||
})
|
||||
.map((device) => (
|
||||
<SequenceCard
|
||||
key={device.device_id}
|
||||
className={SequenceCardStyle}
|
||||
variant={deleted.has(device.device_id) ? 'Critical' : 'SurfaceVariant'}
|
||||
direction="Column"
|
||||
gap="400"
|
||||
>
|
||||
<DeviceTile
|
||||
device={device}
|
||||
deleted={deleted.has(device.device_id)}
|
||||
refreshDeviceList={refreshDeviceList}
|
||||
disabled={deleting}
|
||||
options={
|
||||
<DeviceDeleteBtn
|
||||
deviceId={device.device_id}
|
||||
deleted={deleted.has(device.device_id)}
|
||||
onDeleteToggle={handleToggleDelete}
|
||||
disabled={deleting}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SequenceCard>
|
||||
))}
|
||||
</Box>
|
||||
{deleted.size > 0 && (
|
||||
<Menu
|
||||
style={{
|
||||
position: 'sticky',
|
||||
padding: config.space.S200,
|
||||
paddingLeft: config.space.S400,
|
||||
bottom: config.space.S400,
|
||||
left: config.space.S400,
|
||||
right: 0,
|
||||
zIndex: 1,
|
||||
}}
|
||||
variant="Critical"
|
||||
>
|
||||
<Box alignItems="Center" gap="400">
|
||||
<Box grow="Yes" direction="Column">
|
||||
{deleteError ? (
|
||||
<Text size="T200">
|
||||
<b>Failed to logout devices! Please try again. {deleteError.message}</b>
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="T200">
|
||||
<b>Logout from selected devices. ({deleted.size} selected)</b>
|
||||
</Text>
|
||||
)}
|
||||
{authData && (
|
||||
<ActionUIAFlowsLoader
|
||||
authData={authData}
|
||||
unsupported={() => (
|
||||
<Text size="T200">
|
||||
Authentication steps to perform this action are not supported by client.
|
||||
</Text>
|
||||
)}
|
||||
>
|
||||
{(ongoingFlow) => (
|
||||
<ActionUIA
|
||||
authData={authData}
|
||||
ongoingFlow={ongoingFlow}
|
||||
action={deleteDevices}
|
||||
onCancel={handleCancelAuth}
|
||||
/>
|
||||
)}
|
||||
</ActionUIAFlowsLoader>
|
||||
)}
|
||||
</Box>
|
||||
<Box shrink="No" gap="200">
|
||||
<Button
|
||||
size="300"
|
||||
variant="Critical"
|
||||
fill="None"
|
||||
radii="300"
|
||||
disabled={deleting}
|
||||
onClick={handleCancelDelete}
|
||||
>
|
||||
<Text size="B300">Cancel</Text>
|
||||
</Button>
|
||||
<Button
|
||||
size="300"
|
||||
variant="Critical"
|
||||
radii="300"
|
||||
disabled={deleting}
|
||||
before={deleting && <Spinner variant="Critical" fill="Solid" size="100" />}
|
||||
onClick={() => deleteDevices()}
|
||||
>
|
||||
<Text size="B300">Logout</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Menu>
|
||||
)}
|
||||
</>
|
||||
) : null;
|
||||
}
|
Loading…
Reference in a new issue