Spaces:
Sleeping
Sleeping
File size: 6,471 Bytes
eddc354 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | import { useEffect, useState } from "react";
import { Download, FolderOpen, Pencil, Trash2, Upload } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import {
formatStamp,
listProjects,
removeProject,
renameProject,
type StoredProject,
} from "@/lib/studio-storage";
type Props = {
open: boolean;
onOpenChange: (v: boolean) => void;
onOpenProject: (p: StoredProject) => void;
onImportFile: () => void;
onSaveCurrent: () => void;
ownerId: string;
};
export default function ProjectsDialog({ open, onOpenChange, onOpenProject, onImportFile, onSaveCurrent, ownerId }: Props) {
const [items, setItems] = useState<StoredProject[]>([]);
const [toDelete, setToDelete] = useState<StoredProject | null>(null);
useEffect(() => {
if (open) setItems(listProjects(ownerId));
}, [open, ownerId]);
const download = (p: StoredProject) => {
const blob = new Blob([JSON.stringify(p.data, null, 2)], { type: "application/json" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = `${p.title || "proekt"}.json`;
a.click();
URL.revokeObjectURL(a.href);
};
const rename = (p: StoredProject) => {
const title = window.prompt("Новое название проекта", p.title);
if (title && title.trim()) setItems(renameProject(p.id, title.trim(), ownerId));
};
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-h-[85vh] max-w-4xl overflow-y-auto">
<DialogHeader>
<DialogTitle className="font-display text-2xl">Мои проекты</DialogTitle>
<DialogDescription>Схемы, сохранённые в памяти этого браузера.</DialogDescription>
</DialogHeader>
<div className="flex flex-wrap gap-2">
<Button size="lg" className="h-12 gap-2" onClick={onSaveCurrent} title="Сохранить текущую схему в проекты">
<FolderOpen className="h-5 w-5" /> Сохранить текущую схему
</Button>
<Button
variant="outline"
size="lg"
className="h-12 gap-2"
onClick={onImportFile}
title="Загрузить проект из файла .json"
>
<Upload className="h-5 w-5" /> Загрузить .json
</Button>
</div>
{items.length === 0 ? (
<p className="py-8 text-center text-sm text-muted-foreground">
Пока нет сохранённых проектов. Создайте схему и нажмите «Сохранить текущую схему».
</p>
) : (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{items.map((p) => (
<article key={p.id} className="overflow-hidden rounded-2xl border border-border bg-card">
<img
src={p.thumb}
alt={`Превью схемы «${p.title}»`}
className="aspect-[4/3] w-full bg-secondary object-cover"
/>
<div className="space-y-2 p-3">
<h3 className="truncate font-medium">{p.title}</h3>
<p className="text-xs text-muted-foreground">
{formatStamp(p.updatedAt)} · элементов: {p.elements}
</p>
<div className="grid grid-cols-2 gap-2">
<Button size="sm" className="h-10 gap-1" onClick={() => onOpenProject(p)} title="Открыть проект">
<FolderOpen className="h-4 w-4" /> Открыть
</Button>
<Button
variant="outline"
size="sm"
className="h-10 gap-1"
onClick={() => rename(p)}
title="Переименовать проект"
>
<Pencil className="h-4 w-4" /> Переименовать
</Button>
<Button
variant="outline"
size="sm"
className="h-10 gap-1"
onClick={() => download(p)}
title="Скачать проект файлом .json"
>
<Download className="h-4 w-4" /> Скачать
</Button>
<Button
variant="destructive"
size="sm"
className="h-10 gap-1"
onClick={() => setToDelete(p)}
title="Удалить проект"
>
<Trash2 className="h-4 w-4" /> Удалить
</Button>
</div>
</div>
</article>
))}
</div>
)}
</DialogContent>
</Dialog>
<AlertDialog open={!!toDelete} onOpenChange={(v) => !v && setToDelete(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Удалить проект?</AlertDialogTitle>
<AlertDialogDescription>
Вы уверены, что хотите удалить проект «{toDelete?.title}»? Действие нельзя отменить.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Отмена</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
if (toDelete) setItems(removeProject(toDelete.id, ownerId));
setToDelete(null);
}}
>
Удалить
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
}
|