211 lines
7.8 KiB
TypeScript
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>
|
|
);
|
|
}
|