v3/frontend/src/components/collection/collection_settings_dialog.tsx

211 lines
7.8 KiB
TypeScript

import * as Y from "yjs";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "~/components/ui/dialog";
import { Input } from "~/components/ui/input";
import { Button } from "~/components/ui/button";
import { ColorPicker } from "../form/color_picker";
import { Label } from "~/components/ui/label";
import { IconPicker } from "../form/icon_picker";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "~/components/ui/alert-dialog";
import { useCollection, useCollections } from "~/hooks/use-metadata";
import { type CollectionId, type CollectionProperty, deleteCollection } from "~/lib/metadata";
import Icon from "@mdi/react";
import { mdiPin, mdiPinOff, mdiPlus, mdiTrashCan } from "@mdi/js";
import { createCollectionProperty, deleteCollectionProperty } from "~/lib/property";
import { useEffect } from "react";
import { PropertyTypeCombobox } from "../form/property_type_combobox";
type CollectionSettingsDialogProps = {
name?: string;
collectionId: CollectionId;
children: React.ReactNode;
};
export function CollectionSettingsDialog(props: CollectionSettingsDialogProps) {
return (
<Dialog>
<DialogTrigger asChild>
{props.children}
</DialogTrigger>
<DialogContent className="max-w-[800px]">
<DialogHeader>
<DialogTitle>{props.name} settings</DialogTitle>
<DialogDescription>
Settings for the {props.name} collection.
</DialogDescription>
</DialogHeader>
<div className="h-[600px] overflow-scroll flex flex-col gap-4">
<SettingsSection collectionId={props.collectionId} />
<PropertiesSection collectionId={props.collectionId} />
<DangerSection collectionId={props.collectionId} />
</div>
</DialogContent>
</Dialog>
);
}
function SettingsSection(props: { collectionId: CollectionId }) {
const collection = useCollection(props.collectionId)
if (!collection) {
return null;
}
return (
<div className="flex flex-col gap-2 p-2">
<div className="flex flex-row gap-2">
<IconPicker
initialValue={collection.get("icon")}
onChange={(icon) => collection.set("icon", icon ?? "")}
/>
<div className="grid flex-grow items-center gap-1.5">
<Label htmlFor="name">Name</Label>
<Input
id="name"
className="flex-grow"
defaultValue={collection.get("name")}
placeholder="Name..."
onChange={(name) => collection.set("name", name.currentTarget.value)}
/>
</div>
</div>
<ColorPicker
initialValue={collection.get("color")}
onChange={(color) => collection.set("color", color)}
/>
</div>
);
}
function PropertiesSection(props: { collectionId: CollectionId }) {
const collection = useCollection(props.collectionId)
if (!collection) {
return null;
}
// Ensure that the properties array exists on the collection
useEffect(() => {
if (!collection.has("properties")) {
collection.set("properties", new Y.Array() as any)
}
}, [collection])
const properties = collection.get("properties");
return (
<div>
<Header label="Properties">
<Button
variant="ghost"
onClick={() => properties && createCollectionProperty(properties)}
>
<Icon path={mdiPlus} size={.75} />
</Button>
</Header>
<div className="flex flex-col gap-2 p-2">
{properties?.map((property) =>
<PropertyItem key={property.get("id")}
property={property}
onDelete={() => deleteCollectionProperty(properties, property.get("id"))}
/>
)}
</div>
</div>
);
}
type PropertyItemProps = {
property: CollectionProperty,
onDelete: () => void,
}
function PropertyItem(props: PropertyItemProps) {
const { property: prop } = props;
return (
<div className="flex flex-row gap-2">
<Input
className="flex-grow"
defaultValue={prop.get("name")}
placeholder="Name..."
onChange={(event) => prop.set("name", event.target.value)}
/>
<PropertyTypeCombobox
initialValue={prop.get("type")}
onSelect={(type) => prop.set("type", type)}
/>
<Button
variant="outline"
onClick={() => prop.set("pinned", !prop.get("pinned"))}
>
<Icon path={prop.get("pinned") ? mdiPinOff : mdiPin} size={.75} />
</Button>
<Button
variant="destructive"
onClick={props.onDelete}
>
<Icon path={mdiTrashCan} size={.75} />
</Button>
</div>
);
}
function DangerSection(props: { collectionId: CollectionId }) {
const collections = useCollections();
return (
<div>
<Header label="Danger zone">
</Header>
<div className="flex flex-row gap-2 p-2 items-center">
<div className="flex-grow flex flex-col gap-1">
<span className="text-sm font-bold">Delete collection</span>
<span className="text-xs text-zinc-400">Delete the collection and all items contained within. WARNING: This action is irreversible!</span>
</div>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="destructive"
>
Delete
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the collection and ALL items contained in it.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction asChild>
<Button
variant="destructive"
// TODO: Navigate away from the page when clicked
onClick={() => deleteCollection(collections, props.collectionId)}
>
Delete
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
);
}
type HeaderProps = {
label: string,
children?: React.ReactNode,
}
function Header(props: HeaderProps) {
return (
<div className="flex flex-row gap-2 pr-2 items-center">
<h1 className="font-bold flex-grow">{props.label}</h1>
{props.children}
</div>
);
}