feat(files): add per-channel storage quota [CI]
This commit is contained in:
1 parent
441ed0e5f0
commit
a6f078d009
19 files changed
+172
-15
No files matched your search
@@ -10,11 +10,14 @@ import { ExplorerMoveDialog } from './ExplorerMoveDialog';
|
||||
import { useExplorerInteractions } from './useExplorerInteractions';
|
||||
import { ContextMenu } from '@/ui/ContextMenu';
|
||||
import styles from './FileExplorer.module.css';
|
||||
import { formatSize } from '@/lib/format';
|
||||
|
||||
interface Props {
|
||||
scope: string;
|
||||
threadId?: string;
|
||||
threadName: string;
|
||||
fileBytes: number;
|
||||
maxFileBytes?: number;
|
||||
folders: FolderInfo[];
|
||||
folderId?: string;
|
||||
ids: string[];
|
||||
@@ -56,6 +59,8 @@ export function FileExplorer(props: Props) {
|
||||
seen.add(current.id); path.unshift(current); current = props.folders.find((folder) => folder.id === current?.parentId);
|
||||
}
|
||||
const children = props.folders.filter((folder) => folder.parentId === props.folderId).sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'));
|
||||
const maxFileBytes = props.maxFileBytes;
|
||||
const usedRatio = maxFileBytes && maxFileBytes > 0 ? Math.min(1, props.fileBytes / maxFileBytes) : 0;
|
||||
function beginFolder(id?: string) { const folder = props.folders.find((row) => row.id === id); setEdit(id ?? 'create'); setName(folder?.name ?? ''); setError(''); }
|
||||
const interaction = useExplorerInteractions({ ...props, ids, onDownload: props.onDownloadItems, onDelete: props.onDeleteItems, onMove: props.onMoveItems,
|
||||
onRenameFolder: (id) => beginFolder(id), onCreateFolder: () => beginFolder(), onUpload: () => input.current?.click(), onFolder: navigateFolder });
|
||||
@@ -99,7 +104,16 @@ export function FileExplorer(props: Props) {
|
||||
}}><option value="__choose">移动到…</option><option value="__root">根目录</option>{props.folders.map((folder) => <option value={folder.id} key={folder.id}>{folder.name}</option>)}</select> : null}
|
||||
</div> : null}
|
||||
{error || interaction.error ? <p className={styles.error} role="alert">{error || interaction.error}</p> : null}
|
||||
<footer className={styles.status}>{children.length} 个文件夹 · {ids.length} 个文件<span>双击打开 · 右键操作 · 拖动移动</span></footer>
|
||||
<footer className={styles.status}>{children.length} 个文件夹 · {ids.length} 个文件<span>双击打开 · 右键操作 · 拖动移动</span></footer>
|
||||
<div className={styles.quota} role="group" aria-label="当前频道文件空间">
|
||||
<div className={styles.quotaLabel}><span>频道文件空间</span><span>{formatSize(props.fileBytes)} / {maxFileBytes ? formatSize(maxFileBytes) : '读取中'}</span></div>
|
||||
<div className={styles.quotaTrack} role="progressbar" aria-label="文件空间使用量" aria-valuemin={0}
|
||||
aria-valuemax={maxFileBytes ?? 0} aria-valuenow={Math.min(props.fileBytes, maxFileBytes ?? 0)}>
|
||||
<span className={props.fileBytes >= (maxFileBytes ?? Number.POSITIVE_INFINITY) ? styles.quotaFull : undefined}
|
||||
style={{ width: `${usedRatio * 100}%` }} />
|
||||
</div>
|
||||
{maxFileBytes ? <span className={styles.quotaRemaining}>剩余 {formatSize(Math.max(0, maxFileBytes - props.fileBytes))}</span> : null}
|
||||
</div>
|
||||
{interaction.menu ? <ContextMenu point={interaction.menu} actions={interaction.actions} onClose={interaction.dismiss} /> : null}
|
||||
{interaction.hover && ids.includes(interaction.hover.id) && !interaction.menu ? <ExplorerHover key={interaction.hover.id} {...interaction.hover} /> : null}
|
||||
{interaction.moveIds ? <ExplorerMoveDialog folders={props.folders} count={interaction.moveIds.length} currentFolderId={props.folderId} onClose={() => interaction.setMoveIds(undefined)}
|
||||
|
||||
Reference in new issue
Block a user