104 lines
3.6 KiB
TypeScript
104 lines
3.6 KiB
TypeScript
import React, { useCallback, useEffect, useState } from 'react';
|
|||
|
|
import { Badge, Box, Button, Text } from 'folds';
|
||
|
|
import { SequenceCard } from '../../../components/sequence-card';
|
||
|
|
import { SequenceCardStyle } from '../styles.css';
|
||
|
|
import { SettingTile } from '../../../components/setting-tile';
|
||
|
|
import { bytesToSize } from '../../../utils/common';
|
||
|
|
import {
|
||
|
|
percentOf,
|
||
|
|
readStorageUsage,
|
||
|
|
requestPersistentStorage,
|
||
|
|
StorageUsage as Usage,
|
||
|
|
storageUsageSupported,
|
||
|
|
} from '../../../utils/storageUsage';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* [Gitea #120] One tile: how much this device stores for Lotus, whether the
|
||
|
|
* browser has promised to keep it, and a way to ask. Hidden entirely when
|
||
|
|
* `navigator.storage.estimate()` is unavailable rather than showing zeros.
|
||
|
|
*/
|
||
|
|
export function StorageUsage() {
|
||
|
|
const [usage, setUsage] = useState<Usage | undefined>();
|
||
|
|
const [requesting, setRequesting] = useState(false);
|
||
|
|
|
||
|
|
const refresh = useCallback(() => {
|
||
|
|
readStorageUsage()
|
||
|
|
.then(setUsage)
|
||
|
|
.catch(() => setUsage(undefined));
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (storageUsageSupported()) refresh();
|
||
|
|
}, [refresh]);
|
||
|
|
|
||
|
|
if (!usage) return null;
|
||
|
|
|
||
|
|
const handlePersist = async () => {
|
||
|
|
setRequesting(true);
|
||
|
|
await requestPersistentStorage();
|
||
|
|
setRequesting(false);
|
||
|
|
refresh();
|
||
|
|
};
|
||
|
|
|
||
|
|
const breakdown: string[] = [];
|
||
|
|
if (typeof usage.indexedDB === 'number')
|
||
|
|
breakdown.push(
|
||
|
|
`${bytesToSize(usage.indexedDB)} in IndexedDB (sync cache, encryption keys, search index)`,
|
||
|
|
);
|
||
|
|
if (typeof usage.caches === 'number')
|
||
|
|
breakdown.push(`${bytesToSize(usage.caches)} offline app files`);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Box direction="Column" gap="100">
|
||
|
|
<Text size="L400">Storage</Text>
|
||
|
|
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||
|
|
<SettingTile
|
||
|
|
title={`${bytesToSize(usage.usage)} used on this device`}
|
||
|
|
description={
|
||
|
|
<Box direction="Column" gap="100">
|
||
|
|
<Text size="T200">
|
||
|
|
{percentOf(usage.usage, usage.quota)}% of the {bytesToSize(usage.quota)} the browser
|
||
|
|
allows this site.
|
||
|
|
{breakdown.length > 0 && ` ${breakdown.join(' · ')}.`}
|
||
|
|
</Text>
|
||
|
|
<Text size="T200">
|
||
|
|
Images and videos you have viewed are kept in the browser's own cache, which it
|
||
|
|
manages and empties itself; they are not counted here. Encryption keys are never
|
||
|
|
cleared from this page — losing them makes older encrypted messages unreadable on
|
||
|
|
this device. Cached search results can be cleared from Message Search.
|
||
|
|
</Text>
|
||
|
|
</Box>
|
||
|
|
}
|
||
|
|
after={
|
||
|
|
usage.persisted === undefined ? undefined : (
|
||
|
|
<Box direction="Column" alignItems="End" gap="100">
|
||
|
|
<Badge
|
||
|
|
variant={usage.persisted ? 'Success' : 'Warning'}
|
||
|
|
fill="Soft"
|
||
|
|
radii="300"
|
||
|
|
size="400"
|
||
|
|
>
|
||
|
|
<Text size="L400">{usage.persisted ? 'Protected' : 'May be evicted'}</Text>
|
||
|
|
</Badge>
|
||
|
|
{!usage.persisted && (
|
||
|
|
<Button
|
||
|
|
size="300"
|
||
|
|
variant="Secondary"
|
||
|
|
fill="Soft"
|
||
|
|
radii="300"
|
||
|
|
outlined
|
||
|
|
disabled={requesting}
|
||
|
|
onClick={handlePersist}
|
||
|
|
>
|
||
|
|
<Text size="B300">Keep my data</Text>
|
||
|
|
</Button>
|
||
|
|
)}
|
||
|
|
</Box>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
/>
|
||
|
|
</SequenceCard>
|
||
|
|
</Box>
|
||
|
|
);
|
||
|
|
}
|