diff --git a/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesLayersPanel.tsx b/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesLayersPanel.tsx new file mode 100644 index 0000000..16cb533 --- /dev/null +++ b/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesLayersPanel.tsx @@ -0,0 +1,22 @@ +import { type Image } from "@tissuumaps/core"; + +import { cn } from "@/lib/utils"; + +import { Fieldset, FieldsetLegend } from "../../common/fieldset"; + +export type ImagesLayersPanelProps = { + image: Image; + className?: string; +}; + +export function ImagesLayersPanel({ className }: ImagesLayersPanelProps) { + return ( +
+ + Layers + +
+ ); +} diff --git a/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesPanelItem.tsx b/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesPanelItem.tsx deleted file mode 100644 index 289c139..0000000 --- a/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesPanelItem.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { type Image } from "@tissuumaps/core"; - -import { ImagesPanelItemSettings } from "./ImagesPanelItemSettings"; - -export type ImagesPanelItemProps = { - image: Image; -}; - -export function ImagesPanelItem({ image }: ImagesPanelItemProps) { - return ( - <> - - - ); -} diff --git a/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesSettingsPanel.tsx b/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesSettingsPanel.tsx new file mode 100644 index 0000000..3c74a4b --- /dev/null +++ b/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesSettingsPanel.tsx @@ -0,0 +1,70 @@ +import { type Image } from "@tissuumaps/core"; + +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { cn } from "@/lib/utils"; + +import { useTissUUmaps } from "../../../store"; +import { Field, FieldLabel } from "../../common/field"; +import { Fieldset, FieldsetLegend } from "../../common/fieldset"; + +export type ImagesSettingsPanelProps = { + image: Image; + className?: string; +}; + +export function ImagesSettingsPanel({ + image, + className, +}: ImagesSettingsPanelProps) { + const updateImage = useTissUUmaps((state) => state.updateImage); + + return ( +
+ + Settings + + + Name + + updateImage(image.id, { name: event.target.value }) + } + /> + + + Visibility +
+ + updateImage(image.id, { visibility: checked }) + } + /> + {image.visibility ? "Visible" : "Hidden"} +
+
+ + Opacity + { + const opacity = event.target.valueAsNumber; + if (Number.isFinite(opacity)) { + updateImage(image.id, { + opacity: Math.min(Math.max(0, opacity), 1), + }); + } + }} + /> + +
+ ); +} diff --git a/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesPanelItemSettings.tsx b/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesSourcePanel.tsx similarity index 50% rename from apps/tissuumaps/src/components/panels/ImagesPanel/ImagesPanelItemSettings.tsx rename to apps/tissuumaps/src/components/panels/ImagesPanel/ImagesSourcePanel.tsx index 4f78b69..4b460b1 100644 --- a/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesPanelItemSettings.tsx +++ b/apps/tissuumaps/src/components/panels/ImagesPanel/ImagesSourcePanel.tsx @@ -1,22 +1,29 @@ import { JsonForms } from "@jsonforms/react"; +import { EditIcon } from "lucide-react"; import { useMemo } from "react"; -import { type Image, type ImageDataSource } from "@tissuumaps/core"; +import { type Image } from "@tissuumaps/core"; + +import { Input } from "@/components/ui/input"; +import { cn } from "@/lib/utils"; import { useTissUUmaps } from "../../../store"; +import { Field, FieldLabel } from "../../common/field"; +import { Fieldset, FieldsetLegend } from "../../common/fieldset"; import { cells, renderers } from "../../jsonforms"; -export type ImagesPanelItemSettingsProps = { +export type ImagesSourcePanelProps = { image: Image; + className?: string; }; -export function ImagesPanelItemSettings({ +export function ImagesSourcePanel({ image, -}: ImagesPanelItemSettingsProps) { + className, +}: ImagesSourcePanelProps) { const imageDataStorageRegistry = useTissUUmaps( (state) => state.imageDataStorageRegistry, ); - const updateImage = useTissUUmaps((state) => state.updateImage); const { dataSourceSchema, dataSourceUISchema } = useMemo(() => { const value = imageDataStorageRegistry.get(image.dataSource.type); @@ -29,25 +36,25 @@ export function ImagesPanelItemSettings({ }, [imageDataStorageRegistry, image.dataSource.type]); return ( -
- {/* Data source */} +
+ + Source + + + + Type + + { - if (errors === undefined || errors.length === 0) { - updateImage(image.id, { - dataSource: { - ...image.dataSource, - ...(data as ImageDataSource), - }, - }); - } - }} renderers={renderers} cells={cells} + readonly={true} /> -
+ ); } diff --git a/apps/tissuumaps/src/components/panels/ImagesPanel/index.tsx b/apps/tissuumaps/src/components/panels/ImagesPanel/index.tsx index eb1d060..42ebd0d 100644 --- a/apps/tissuumaps/src/components/panels/ImagesPanel/index.tsx +++ b/apps/tissuumaps/src/components/panels/ImagesPanel/index.tsx @@ -1,9 +1,16 @@ import { DragDropProvider } from "@dnd-kit/react"; import { isSortable, useSortable } from "@dnd-kit/react/sortable"; -import { GripVertical } from "lucide-react"; +import { EyeIcon, EyeOffIcon, GripVertical, Trash2Icon } from "lucide-react"; import { type Image } from "@tissuumaps/core"; +import { Button } from "@/components/ui/button"; +import { + InputGroup, + InputGroupAddon, + InputGroupInput, +} from "@/components/ui/input-group"; + import { useTissUUmaps } from "../../../store"; import { Accordion, @@ -13,7 +20,9 @@ import { AccordionTrigger, AccordionTriggerUpDownIcon, } from "../../common/accordion"; -import { ImagesPanelItem } from "./ImagesPanelItem"; +import { ImagesLayersPanel } from "./ImagesLayersPanel"; +import { ImagesSettingsPanel } from "./ImagesSettingsPanel"; +import { ImagesSourcePanel } from "./ImagesSourcePanel"; export type ImagesPanelProps = { className?: string; @@ -49,18 +58,71 @@ type ImageAccordionItemProps = { }; function ImageAccordionItem({ image, index }: ImageAccordionItemProps) { + const updateImage = useTissUUmaps((state) => state.updateImage); + const deleteImage = useTissUUmaps((state) => state.deleteImage); + const { ref, handleRef } = useSortable({ id: image.id, index }); return ( - }> - - - {image.name} - - - - - - +
+ + + +
+ + {image.name} + +
+
+ + OPA + { + const opacity = event.target.valueAsNumber; + if (Number.isFinite(opacity)) { + updateImage(image.id, { + opacity: Math.min(Math.max(0, opacity), 1), + }); + } + }} + /> + + + +
+ +
+ + + + + +
+
); } diff --git a/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsLayersPanel.tsx b/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsLayersPanel.tsx new file mode 100644 index 0000000..40cacdd --- /dev/null +++ b/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsLayersPanel.tsx @@ -0,0 +1,22 @@ +import { type Labels } from "@tissuumaps/core"; + +import { cn } from "@/lib/utils"; + +import { Fieldset, FieldsetLegend } from "../../common/fieldset"; + +export type LabelsLayersPanelProps = { + labels: Labels; + className?: string; +}; + +export function LabelsLayersPanel({ className }: LabelsLayersPanelProps) { + return ( +
+ + Layers + +
+ ); +} diff --git a/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsPanelItem.tsx b/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsPanelItem.tsx deleted file mode 100644 index d207233..0000000 --- a/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsPanelItem.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { type Labels } from "@tissuumaps/core"; - -import { LabelsPanelItemSettings } from "./LabelsPanelItemSettings"; - -export type LabelsPanelItemProps = { - labels: Labels; -}; - -export function LabelsPanelItem({ labels }: LabelsPanelItemProps) { - return ( - <> - - - ); -} diff --git a/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsPanelItemSettings.tsx b/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsSettingsPanel.tsx similarity index 54% rename from apps/tissuumaps/src/components/panels/LabelsPanel/LabelsPanelItemSettings.tsx rename to apps/tissuumaps/src/components/panels/LabelsPanel/LabelsSettingsPanel.tsx index 7837e4c..6aaa121 100644 --- a/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsPanelItemSettings.tsx +++ b/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsSettingsPanel.tsx @@ -1,14 +1,14 @@ -import { JsonForms } from "@jsonforms/react"; -import { useMemo, useState } from "react"; - import { type Labels, - type LabelsDataSource, defaultLabelColor, defaultLabelOpacity, defaultLabelVisibility, } from "@tissuumaps/core"; +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { cn } from "@/lib/utils"; + import { useTissUUmaps } from "../../../store"; import { Accordion, @@ -18,6 +18,8 @@ import { AccordionTrigger, AccordionTriggerRightDownIcon, } from "../../common/accordion"; +import { Field, FieldLabel } from "../../common/field"; +import { Fieldset, FieldsetLegend } from "../../common/fieldset"; import { ColorConfigControl, ColorConfigSourceToggleGroup, @@ -33,41 +35,18 @@ import { VisibilityConfigSourceToggleGroup, } from "../../controls/VisibilityConfigControl"; import { useVisibilityConfigControl } from "../../controls/VisibilityConfigControl/useVisibilityConfigControl"; -import { cells, renderers } from "../../jsonforms"; - -const ConfigControl = { - labelColor: "labelColor", - labelVisibility: "labelVisibility", - labelOpacity: "labelOpacity", -} as const; -type ConfigControl = (typeof ConfigControl)[keyof typeof ConfigControl]; - -export type LabelsPanelItemSettingsProps = { +export type LabelsSettingsPanelProps = { labels: Labels; + className?: string; }; -export function LabelsPanelItemSettings({ +export function LabelsSettingsPanel({ labels, -}: LabelsPanelItemSettingsProps) { - const [expandedConfigControl, setExpandedConfigControl] = - useState(null); - - const labelsDataStorageRegistry = useTissUUmaps( - (state) => state.labelsDataStorageRegistry, - ); + className, +}: LabelsSettingsPanelProps) { const updateLabels = useTissUUmaps((state) => state.updateLabels); - const { dataSourceSchema, dataSourceUISchema } = useMemo(() => { - const value = labelsDataStorageRegistry.get(labels.dataSource.type); - if (value === undefined) { - throw new Error( - `No labels data storage adapter registered for data source type "${labels.dataSource.type}"`, - ); - } - return value; - }, [labelsDataStorageRegistry, labels.dataSource.type]); - const labelColorConfigControlState = useColorConfigControl( labels.labelColor, (newColorConfig) => updateLabels(labels.id, { labelColor: newColorConfig }), @@ -87,43 +66,68 @@ export function LabelsPanelItemSettings({ ); return ( -
- {/* Data source */} - { - if (errors === undefined || errors.length === 0) { - updateLabels(labels.id, { - dataSource: { - ...labels.dataSource, - ...(data as LabelsDataSource), - }, - }); - } - }} - renderers={renderers} - cells={cells} - /> - {/* Label settings */} - - setExpandedConfigControl( - (value[0] as ConfigControl | undefined) ?? null, - ) - } - > +
+ + Settings + + + + + + General + + + + Name + + updateLabels(labels.id, { name: event.target.value }) + } + /> + + + Visibility +
+ + updateLabels(labels.id, { visibility: checked }) + } + /> + {labels.visibility ? "Visible" : "Hidden"} +
+
+ + Opacity + { + const opacity = event.target.valueAsNumber; + if (Number.isFinite(opacity)) { + updateLabels(labels.id, { + opacity: Math.min(Math.max(0, opacity), 1), + }); + } + }} + /> + +
+
{/* Label color */} - + - Color + Label color setExpandedConfigControl(ConfigControl.labelColor)} /> @@ -131,16 +135,13 @@ export function LabelsPanelItemSettings({ {/* Label visibility */} - + - Visibility + Label visibility - setExpandedConfigControl(ConfigControl.labelVisibility) - } /> @@ -150,16 +151,13 @@ export function LabelsPanelItemSettings({ {/* Label opacity */} - + - Opacity + Label opacity - setExpandedConfigControl(ConfigControl.labelOpacity) - } /> @@ -167,6 +165,6 @@ export function LabelsPanelItemSettings({
-
+ ); } diff --git a/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsSourcePanel.tsx b/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsSourcePanel.tsx new file mode 100644 index 0000000..a3884e3 --- /dev/null +++ b/apps/tissuumaps/src/components/panels/LabelsPanel/LabelsSourcePanel.tsx @@ -0,0 +1,60 @@ +import { JsonForms } from "@jsonforms/react"; +import { EditIcon } from "lucide-react"; +import { useMemo } from "react"; + +import { type Labels } from "@tissuumaps/core"; + +import { Input } from "@/components/ui/input"; +import { cn } from "@/lib/utils"; + +import { useTissUUmaps } from "../../../store"; +import { Field, FieldLabel } from "../../common/field"; +import { Fieldset, FieldsetLegend } from "../../common/fieldset"; +import { cells, renderers } from "../../jsonforms"; + +export type LabelsSourcePanelProps = { + labels: Labels; + className?: string; +}; + +export function LabelsSourcePanel({ + labels, + className, +}: LabelsSourcePanelProps) { + const labelsDataStorageRegistry = useTissUUmaps( + (state) => state.labelsDataStorageRegistry, + ); + + const { dataSourceSchema, dataSourceUISchema } = useMemo(() => { + const value = labelsDataStorageRegistry.get(labels.dataSource.type); + if (value === undefined) { + throw new Error( + `No labels data Storage registered for data source type "${labels.dataSource.type}"`, + ); + } + return value; + }, [labelsDataStorageRegistry, labels.dataSource.type]); + + return ( +
+ + Source + + + + Type + + + +
+ ); +} diff --git a/apps/tissuumaps/src/components/panels/LabelsPanel/index.tsx b/apps/tissuumaps/src/components/panels/LabelsPanel/index.tsx index 1443360..be0173e 100644 --- a/apps/tissuumaps/src/components/panels/LabelsPanel/index.tsx +++ b/apps/tissuumaps/src/components/panels/LabelsPanel/index.tsx @@ -1,9 +1,16 @@ import { DragDropProvider } from "@dnd-kit/react"; import { isSortable, useSortable } from "@dnd-kit/react/sortable"; -import { GripVertical } from "lucide-react"; +import { EyeIcon, EyeOffIcon, GripVertical, Trash2Icon } from "lucide-react"; import { type Labels } from "@tissuumaps/core"; +import { Button } from "@/components/ui/button"; +import { + InputGroup, + InputGroupAddon, + InputGroupInput, +} from "@/components/ui/input-group"; + import { useTissUUmaps } from "../../../store"; import { Accordion, @@ -13,7 +20,9 @@ import { AccordionTrigger, AccordionTriggerUpDownIcon, } from "../../common/accordion"; -import { LabelsPanelItem } from "./LabelsPanelItem"; +import { LabelsLayersPanel } from "./LabelsLayersPanel"; +import { LabelsSettingsPanel } from "./LabelsSettingsPanel"; +import { LabelsSourcePanel } from "./LabelsSourcePanel"; export type LabelsPanelProps = { className?: string; @@ -28,8 +37,6 @@ export function LabelsPanel({ className }: LabelsPanelProps) { onDragEnd={(event) => { const { source, canceled } = event.operation; if (isSortable(source) && !canceled) { - // dnd-kit optimistically updates the DOM - // https://github.com/clauderic/dnd-kit/issues/1564 moveLabels(source.id as string, source.index); } }} @@ -53,18 +60,70 @@ type LabelsAccordionItemProps = { }; function LabelsAccordionItem({ labels, index }: LabelsAccordionItemProps) { + const updateLabels = useTissUUmaps((state) => state.updateLabels); + const deleteLabels = useTissUUmaps((state) => state.deleteLabels); + const { ref, handleRef } = useSortable({ id: labels.id, index }); return (
- + - {labels.name} - +
+ + {labels.name} + +
+
+ + OPA + { + const opacity = event.target.valueAsNumber; + if (Number.isFinite(opacity)) { + updateLabels(labels.id, { + opacity: Math.min(Math.max(0, opacity), 1), + }); + } + }} + /> + + + +
+
- - + + + +
diff --git a/apps/tissuumaps/src/components/panels/TablesPanel/TablesPanelItem.tsx b/apps/tissuumaps/src/components/panels/TablesPanel/TablesPanelItem.tsx deleted file mode 100644 index d6144bf..0000000 --- a/apps/tissuumaps/src/components/panels/TablesPanel/TablesPanelItem.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { type Table } from "@tissuumaps/core"; - -import { TablesPanelItemSettings } from "./TablesPanelItemSettings"; - -export type TablesPanelItemProps = { - table: Table; -}; - -export function TablesPanelItem({ table }: TablesPanelItemProps) { - return ( - <> - - - ); -} diff --git a/apps/tissuumaps/src/components/panels/TablesPanel/TablesSettingsPanel.tsx b/apps/tissuumaps/src/components/panels/TablesPanel/TablesSettingsPanel.tsx new file mode 100644 index 0000000..3bd73cd --- /dev/null +++ b/apps/tissuumaps/src/components/panels/TablesPanel/TablesSettingsPanel.tsx @@ -0,0 +1,39 @@ +import { type Table } from "@tissuumaps/core"; + +import { Input } from "@/components/ui/input"; +import { cn } from "@/lib/utils"; + +import { useTissUUmaps } from "../../../store"; +import { Field, FieldLabel } from "../../common/field"; +import { Fieldset, FieldsetLegend } from "../../common/fieldset"; + +export type TablesSettingsPanelProps = { + table: Table; + className?: string; +}; + +export function TablesSettingsPanel({ + table, + className, +}: TablesSettingsPanelProps) { + const updateTable = useTissUUmaps((state) => state.updateTable); + + return ( +
+ + Settings + + + Name + + updateTable(table.id, { name: event.target.value }) + } + /> + +
+ ); +} diff --git a/apps/tissuumaps/src/components/panels/TablesPanel/TablesPanelItemSettings.tsx b/apps/tissuumaps/src/components/panels/TablesPanel/TablesSourcePanel.tsx similarity index 54% rename from apps/tissuumaps/src/components/panels/TablesPanel/TablesPanelItemSettings.tsx rename to apps/tissuumaps/src/components/panels/TablesPanel/TablesSourcePanel.tsx index 51d8b01..21e3332 100644 --- a/apps/tissuumaps/src/components/panels/TablesPanel/TablesPanelItemSettings.tsx +++ b/apps/tissuumaps/src/components/panels/TablesPanel/TablesSourcePanel.tsx @@ -1,18 +1,26 @@ import { JsonForms } from "@jsonforms/react"; +import { EditIcon } from "lucide-react"; import { useMemo } from "react"; import { type Table } from "@tissuumaps/core"; +import { Input } from "@/components/ui/input"; +import { cn } from "@/lib/utils"; + import { useTissUUmaps } from "../../../store"; +import { Field, FieldLabel } from "../../common/field"; +import { Fieldset, FieldsetLegend } from "../../common/fieldset"; import { cells, renderers } from "../../jsonforms"; -export type TablesPanelItemSettingsProps = { +export type TablesSourcePanelProps = { table: Table; + className?: string; }; -export function TablesPanelItemSettings({ +export function TablesSourcePanel({ table, -}: TablesPanelItemSettingsProps) { + className, +}: TablesSourcePanelProps) { const tableDataStorageRegistry = useTissUUmaps( (state) => state.tableDataStorageRegistry, ); @@ -28,8 +36,17 @@ export function TablesPanelItemSettings({ }, [tableDataStorageRegistry, table.dataSource.type]); return ( -
- {/* Data source */} +
+ + Source + + + + Type + + -
+ ); } diff --git a/apps/tissuumaps/src/components/panels/TablesPanel/index.tsx b/apps/tissuumaps/src/components/panels/TablesPanel/index.tsx index 8700121..18a3aad 100644 --- a/apps/tissuumaps/src/components/panels/TablesPanel/index.tsx +++ b/apps/tissuumaps/src/components/panels/TablesPanel/index.tsx @@ -1,3 +1,11 @@ +import { DragDropProvider } from "@dnd-kit/react"; +import { isSortable, useSortable } from "@dnd-kit/react/sortable"; +import { GripVertical, Trash2Icon } from "lucide-react"; + +import { type Table } from "@tissuumaps/core"; + +import { Button } from "@/components/ui/button"; + import { useTissUUmaps } from "../../../store"; import { Accordion, @@ -7,7 +15,8 @@ import { AccordionTrigger, AccordionTriggerUpDownIcon, } from "../../common/accordion"; -import { TablesPanelItem } from "./TablesPanelItem"; +import { TablesSettingsPanel } from "./TablesSettingsPanel"; +import { TablesSourcePanel } from "./TablesSourcePanel"; export type TablesPanelProps = { className?: string; @@ -15,20 +24,68 @@ export type TablesPanelProps = { export function TablesPanel({ className }: TablesPanelProps) { const tables = useTissUUmaps((state) => state.tables); + const moveTable = useTissUUmaps((state) => state.moveTable); + + return ( + { + const { source, canceled } = event.operation; + if (isSortable(source) && !canceled) { + moveTable(source.id as string, source.index); + } + }} + > + + {tables.map((table, index) => ( + + ))} + + + ); +} + +type TableAccordionItemProps = { + table: Table; + index: number; +}; + +function TableAccordionItem({ table, index }: TableAccordionItemProps) { + const deleteTable = useTissUUmaps((state) => state.deleteTable); + + const { ref, handleRef } = useSortable({ id: table.id, index }); return ( - - {tables.map((table) => ( - - - {table.name} - - - - - - - ))} - +
+ + + +
+ + {table.name} + +
+
+ +
+ +
+ + + + +
+
); }