97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
import { FileIcon, X, Plus, ExternalLink } from "lucide-react";
|
|
import { openPath } from "@tauri-apps/plugin-opener";
|
|
import { open } from "@tauri-apps/plugin-dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
import { useBoardStore } from "@/stores/board-store";
|
|
import { copyAttachment } from "@/lib/storage";
|
|
import type { Attachment } from "@/types/board";
|
|
|
|
interface AttachmentSectionProps {
|
|
cardId: string;
|
|
attachments: Attachment[];
|
|
}
|
|
|
|
export function AttachmentSection({
|
|
cardId,
|
|
attachments,
|
|
}: AttachmentSectionProps) {
|
|
const addAttachment = useBoardStore((s) => s.addAttachment);
|
|
const removeAttachment = useBoardStore((s) => s.removeAttachment);
|
|
|
|
async function handleAdd() {
|
|
const selected = await open({
|
|
multiple: false,
|
|
title: "Select attachment",
|
|
});
|
|
if (!selected) return;
|
|
|
|
const fileName = selected.split(/[\\/]/).pop() ?? "attachment";
|
|
|
|
const board = useBoardStore.getState().board;
|
|
if (!board) return;
|
|
|
|
const mode = board.settings.attachmentMode;
|
|
|
|
if (mode === "copy") {
|
|
const destPath = await copyAttachment(board.id, selected, fileName);
|
|
addAttachment(cardId, { name: fileName, path: destPath, mode: "copy" });
|
|
} else {
|
|
addAttachment(cardId, { name: fileName, path: selected, mode: "link" });
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col gap-2">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<h4 className="font-mono text-xs uppercase tracking-widest text-pylon-text-secondary">
|
|
Attachments
|
|
</h4>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-xs"
|
|
onClick={handleAdd}
|
|
className="text-pylon-text-secondary hover:text-pylon-text"
|
|
>
|
|
<Plus className="size-3.5" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Attachment list */}
|
|
{attachments.length > 0 ? (
|
|
<div className="flex flex-col gap-1">
|
|
{attachments.map((att) => (
|
|
<div
|
|
key={att.id}
|
|
className="group/att flex items-center gap-2 rounded px-1 py-1 hover:bg-pylon-column/60"
|
|
>
|
|
<FileIcon className="size-3.5 shrink-0 text-pylon-text-secondary" />
|
|
<span className="flex-1 truncate text-sm text-pylon-text">
|
|
{att.name}
|
|
</span>
|
|
<button
|
|
onClick={() => openPath(att.path)}
|
|
className="shrink-0 rounded p-0.5 text-pylon-text-secondary opacity-0 transition-opacity hover:bg-pylon-accent/10 hover:text-pylon-accent group-hover/att:opacity-100"
|
|
aria-label="Open attachment"
|
|
>
|
|
<ExternalLink className="size-3" />
|
|
</button>
|
|
<button
|
|
onClick={() => removeAttachment(cardId, att.id)}
|
|
className="shrink-0 rounded p-0.5 text-pylon-text-secondary opacity-0 transition-opacity hover:bg-pylon-danger/10 hover:text-pylon-danger group-hover/att:opacity-100"
|
|
aria-label="Remove attachment"
|
|
>
|
|
<X className="size-3" />
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="text-xs italic text-pylon-text-secondary/60">
|
|
No attachments
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|