diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index 5dba675e6d..597769a36b 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -2,18 +2,16 @@ import "./CollectionProperties.css"; import { t } from "i18next"; import { ComponentChildren } from "preact"; -import { useContext, useRef } from "preact/hooks"; -import { Fragment } from "preact/jsx-runtime"; +import { useRef } from "preact/hooks"; import FNote from "../../entities/fnote"; import { ViewTypeOptions } from "../collections/interface"; import Dropdown from "../react/Dropdown"; -import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; -import FormTextBox from "../react/FormTextBox"; -import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault, useNoteProperty, useTriliumEvent } from "../react/hooks"; +import { FormDropdownDivider, FormListItem } from "../react/FormList"; +import { useNoteProperty, useTriliumEvent } from "../react/hooks"; import Icon from "../react/Icon"; -import { ParentComponent } from "../react/react_utils"; -import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config"; +import { CheckBoxProperty, ViewProperty } from "../react/NotePropertyMenu"; +import { bookPropertiesConfig } from "../ribbon/collection-properties-config"; import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; export const ICON_MAPPINGS: Record = { @@ -107,127 +105,3 @@ function ViewOptions({ note, viewType }: { note: FNote, viewType: ViewTypeOption ); } - -function ViewProperty({ note, property }: { note: FNote, property: BookProperty }) { - switch (property.type) { - case "button": - return ; - case "split-button": - return ; - case "checkbox": - return ; - case "number": - return ; - case "combobox": - return ; - } -} - -function ButtonPropertyView({ note, property }: { note: FNote, property: ButtonProperty }) { - const parentComponent = useContext(ParentComponent); - - return ( - { - if (!parentComponent) return; - property.onClick({ - note, - triggerCommand: parentComponent.triggerCommand.bind(parentComponent) - }); - }} - >{property.label} - ); -} - -function SplitButtonPropertyView({ note, property }: { note: FNote, property: SplitButtonProperty }) { - const parentComponent = useContext(ParentComponent); - const ItemsComponent = property.items; - const clickContext = parentComponent && { - note, - triggerCommand: parentComponent.triggerCommand.bind(parentComponent) - }; - - return (parentComponent && - clickContext && property.onClick(clickContext)} - > - - - ); -} - -function NumberPropertyView({ note, property }: { note: FNote, property: NumberProperty }) { - //@ts-expect-error Interop with text box which takes in string values even for numbers. - const [ value, setValue ] = useNoteLabel(note, property.bindToLabel); - const disabled = property.disabled?.(note); - - return ( - e.stopPropagation()} - > - {property.label} - - - ); -} - -function ComboBoxPropertyView({ note, property }: { note: FNote, property: ComboBoxProperty }) { - const [ value, setValue ] = useNoteLabelWithDefault(note, property.bindToLabel, property.defaultValue ?? ""); - - function renderItem(option: ComboBoxItem) { - return ( - setValue(option.value)} - > - {option.label} - - ); - } - - return ( - - {(property.options).map((option, index) => { - if ("items" in option) { - return ( - - {option.title} - {option.items.map(renderItem)} - {index < property.options.length - 1 && } - - ); - } - return renderItem(option); - - })} - - ); -} - -function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) { - const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel); - return ( - - ); -} diff --git a/apps/client/src/widgets/react/NotePropertyMenu.tsx b/apps/client/src/widgets/react/NotePropertyMenu.tsx new file mode 100644 index 0000000000..3c01f7e192 --- /dev/null +++ b/apps/client/src/widgets/react/NotePropertyMenu.tsx @@ -0,0 +1,194 @@ +import { FilterLabelsByType } from "@triliumnext/commons"; +import { Fragment, VNode } from "preact"; +import { useContext } from "preact/hooks"; + +import Component from "../../components/component"; +import FNote from "../../entities/fnote"; +import NoteContextAwareWidget from "../note_context_aware_widget"; +import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "./FormList"; +import FormTextBox from "./FormTextBox"; +import { useNoteLabelBoolean, useNoteLabelWithDefault } from "./hooks"; +import { ParentComponent } from "./react_utils"; + +export interface ClickContext { + note: FNote; + triggerCommand: NoteContextAwareWidget["triggerCommand"]; +} + +export interface CheckBoxProperty { + type: "checkbox", + label: string; + bindToLabel: FilterLabelsByType; + icon?: string; +} + +export interface ButtonProperty { + type: "button", + label: string; + title?: string; + icon?: string; + onClick(context: ClickContext): void; +} + +export interface SplitButtonProperty extends Omit { + type: "split-button"; + items({ note, parentComponent }: { note: FNote, parentComponent: Component }): VNode; +} + +export interface NumberProperty { + type: "number", + label: string; + bindToLabel: FilterLabelsByType; + width?: number; + min?: number; + icon?: string; + disabled?: (note: FNote) => boolean; +} + +export interface ComboBoxItem { + value: string; + label: string; +} + +interface ComboBoxGroup { + title: string; + items: ComboBoxItem[]; +} + +export interface ComboBoxProperty { + type: "combobox", + label: string; + icon?: string; + bindToLabel: FilterLabelsByType; + /** + * The default value is used when the label is not set. + */ + defaultValue?: string; + options: (ComboBoxItem | ComboBoxGroup)[]; +} + +export type BookProperty = CheckBoxProperty | ButtonProperty | NumberProperty | ComboBoxProperty | SplitButtonProperty; + +export function ViewProperty({ note, property }: { note: FNote, property: BookProperty }) { + switch (property.type) { + case "button": + return ; + case "split-button": + return ; + case "checkbox": + return ; + case "number": + return ; + case "combobox": + return ; + } +} + +function ButtonPropertyView({ note, property }: { note: FNote, property: ButtonProperty }) { + const parentComponent = useContext(ParentComponent); + + return ( + { + if (!parentComponent) return; + property.onClick({ + note, + triggerCommand: parentComponent.triggerCommand.bind(parentComponent) + }); + }} + >{property.label} + ); +} + +function SplitButtonPropertyView({ note, property }: { note: FNote, property: SplitButtonProperty }) { + const parentComponent = useContext(ParentComponent); + const ItemsComponent = property.items; + const clickContext = parentComponent && { + note, + triggerCommand: parentComponent.triggerCommand.bind(parentComponent) + }; + + return (parentComponent && + clickContext && property.onClick(clickContext)} + > + + + ); +} + +function NumberPropertyView({ note, property }: { note: FNote, property: NumberProperty }) { + //@ts-expect-error Interop with text box which takes in string values even for numbers. + const [ value, setValue ] = useNoteLabel(note, property.bindToLabel); + const disabled = property.disabled?.(note); + + return ( + e.stopPropagation()} + > + {property.label} + + + ); +} + +function ComboBoxPropertyView({ note, property }: { note: FNote, property: ComboBoxProperty }) { + const [ value, setValue ] = useNoteLabelWithDefault(note, property.bindToLabel, property.defaultValue ?? ""); + + function renderItem(option: ComboBoxItem) { + return ( + setValue(option.value)} + > + {option.label} + + ); + } + + return ( + + {(property.options).map((option, index) => { + if ("items" in option) { + return ( + + {option.title} + {option.items.map(renderItem)} + {index < property.options.length - 1 && } + + ); + } + return renderItem(option); + + })} + + ); +} + +function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) { + const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel); + return ( + + ); +} diff --git a/apps/client/src/widgets/ribbon/collection-properties-config.tsx b/apps/client/src/widgets/ribbon/collection-properties-config.tsx index 1f79217e9c..2d6c051e91 100644 --- a/apps/client/src/widgets/ribbon/collection-properties-config.tsx +++ b/apps/client/src/widgets/ribbon/collection-properties-config.tsx @@ -1,79 +1,19 @@ import { t } from "i18next"; + +import Component from "../../components/component"; import FNote from "../../entities/fnote"; import attributes from "../../services/attributes"; -import NoteContextAwareWidget from "../note_context_aware_widget"; import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, type MapLayer } from "../collections/geomap/map_layer"; import { ViewTypeOptions } from "../collections/interface"; -import { FilterLabelsByType } from "@triliumnext/commons"; import { DEFAULT_THEME, getPresentationThemes } from "../collections/presentation/themes"; -import { VNode } from "preact"; -import { useNoteLabel } from "../react/hooks"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; -import Component from "../../components/component"; +import { useNoteLabel } from "../react/hooks"; +import { BookProperty, ClickContext, ComboBoxItem } from "../react/NotePropertyMenu"; interface BookConfig { properties: BookProperty[]; } -export interface CheckBoxProperty { - type: "checkbox", - label: string; - bindToLabel: FilterLabelsByType; - icon?: string; -} - -export interface ButtonProperty { - type: "button", - label: string; - title?: string; - icon?: string; - onClick(context: BookContext): void; -} - -export interface SplitButtonProperty extends Omit { - type: "split-button"; - items({ note, parentComponent }: { note: FNote, parentComponent: Component }): VNode; -} - -export interface NumberProperty { - type: "number", - label: string; - bindToLabel: FilterLabelsByType; - width?: number; - min?: number; - icon?: string; - disabled?: (note: FNote) => boolean; -} - -export interface ComboBoxItem { - value: string; - label: string; -} - -interface ComboBoxGroup { - title: string; - items: ComboBoxItem[]; -} - -export interface ComboBoxProperty { - type: "combobox", - label: string; - icon?: string; - bindToLabel: FilterLabelsByType; - /** - * The default value is used when the label is not set. - */ - defaultValue?: string; - options: (ComboBoxItem | ComboBoxGroup)[]; -} - -export type BookProperty = CheckBoxProperty | ButtonProperty | NumberProperty | ComboBoxProperty | SplitButtonProperty; - -interface BookContext { - note: FNote; - triggerCommand: NoteContextAwareWidget["triggerCommand"]; -} - export const bookPropertiesConfig: Record = { grid: { properties: [] @@ -211,7 +151,7 @@ function ListExpandDepth(context: { note: FNote, parentComponent: Component }) { - ) + ); } function ListExpandDepthButton({ label, depth, note, parentComponent, checked }: { label: string, depth: number | "all", note: FNote, parentComponent: Component, checked?: boolean }) { @@ -226,7 +166,7 @@ function ListExpandDepthButton({ label, depth, note, parentComponent, checked }: } function buildExpandListHandler(depth: number | "all") { - return async ({ note, triggerCommand }: BookContext) => { + return async ({ note, triggerCommand }: ClickContext) => { const { noteId } = note; const existingValue = note.getLabelValue("expanded"); @@ -236,5 +176,5 @@ function buildExpandListHandler(depth: number | "all") { await attributes.setLabel(noteId, "expanded", newValue); triggerCommand("refreshNoteList", { noteId }); - } + }; }