import { Dialog as BaseDialog } from '@base-ui/react/dialog';
import { useQuery } from '@tanstack/react-query';
import {
FileIcon,
FolderIcon,
FolderPlusIcon,
HardDriveIcon,
LockIcon,
Trash2Icon,
UploadIcon,
} from 'lucide-react';
import { useRef, useState } from 'react';
import { Button } from '#/components/atoms/button.jsx';
import { Notice } from '#/components/atoms/notice.jsx';
import { RowIcon } from '#/components/atoms/row-icon.jsx';
import { Dialog } from '#/components/molecules/dialog.jsx';
import { EmptyState } from '#/components/molecules/empty-state.jsx';
import { ListRow } from '#/components/molecules/list-row.jsx';
import { TextLink } from '#/components/molecules/text-link.jsx';
import { get, post, upload } from '#/lib/api.js';
import { bytes, num, timeAgo } from '#/lib/format.js';
import { queryKeys, useApiMutation } from '#/lib/query.js';
/** The drive page URL for one folder, in the public repo or in a space. */
export function folderHref(folder, space) {
const query = new URLSearchParams();
if (folder) query.set('folder', folder);
if (space) query.set('space', space);
const qs = query.toString();
return qs ? `/account/drive?${qs}` : '/account/drive';
}
/** Where the browser fetches one file's bytes. */
function downloadHref(file, space) {
const query = new URLSearchParams({ rkey: file.rkey });
if (space) query.set('space', space);
return `/account/api/drive/file?${query.toString()}`;
}
/** A read-only value with a button that puts it on the clipboard. */
function CopyField({ label, value, rows }) {
const [copied, setCopied] = useState(false);
return (
{label}
);
}
/**
* The blob behind a single-blob public file, in the two forms another app
* takes: the public URL its bytes serve from, and the blob ref a record
* embeds. Split files and space files have no public single blob, so the
* dialog only offers on files with a blobCid outside a space.
*/
function LinkFileDialog({ file, did }) {
const origin = typeof window === 'undefined' ? '' : window.location.origin;
const url = `${origin}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(file.blobCid)}`;
const ref = JSON.stringify(
{
$type: 'blob',
ref: { $link: file.blobCid },
mimeType: file.mimeType,
size: file.size,
},
null,
2,
);
return (
);
}
/** The path back to the top level, one link per folder along the way. */
function Breadcrumb({ ancestry, space, onNavigate }) {
if (!ancestry?.length) return null;
return (
);
}
/**
* Make a drive that lives in a permissioned space. Its files are private and
* shared with whoever joins the space, and it is a space of the drive's own
* type: another app's space is that app's data model.
*/
function NewDriveDialog({ onCreate, busy }) {
const [name, setName] = useState('');
return (
);
}
/** Make a folder here. It is a record of its own, so an empty one stays. */
function NewFolderDialog({ folder, space, onCreate, busy }) {
const [name, setName] = useState('');
return (
);
}
/** Rename one folder. Every file keeps its own record, so nothing moves. */
function RenameFolderDialog({ entry, space, onRename, busy }) {
const [name, setName] = useState(entry.name);
return (
);
}
/**
* What deleting put aside. The bytes are still stored, so this says what the
* trash costs and offers to give it back or let it go.
*/
function Trash({ space, busy, onRestore, onPurge }) {
const { data } = useQuery({
queryKey: queryKeys.driveTrash(space || ''),
queryFn: () => {
const query = space ? `?space=${encodeURIComponent(space)}` : '';
return get(`/drive/trash${query}`);
},
});
const [open, setOpen] = useState(false);
if (!data?.enabled || !data.items?.length) return null;
return (
Drop to upload into{' '}
{data.path || 'your drive'}
)}
{empty ? (
}
title={data.folder ? 'This folder is empty' : 'No files yet'}
description={
canUpload
? 'Drop files here, or use Upload files. Each one is stored in your own repo, with its bytes in your account’s blob storage.'
: 'Files stored as dev.pdsjs.drive.file records appear here, with their bytes kept in your account’s blob storage.'
}
/>
) : (
<>
{data.folders.map((child) => (
}
title={{child.name}}
description={
<>
{num(child.files)} file{child.files === 1 ? '' : 's'} ·{' '}
{bytes(child.size)}
>
}
href={folderHref(child.rkey, space)}
onNavigate={onNavigate}
actions={