feat(react/ribbon): reintroduce combobox collection properties

This commit is contained in:
Elian Doran 2025-08-23 23:54:14 +03:00
parent 2b8b185b5b
commit d7e36bdf93
No known key found for this signature in database
4 changed files with 41 additions and 119 deletions

View File

@ -39,15 +39,22 @@ export default function FormSelect<T>({ name, id, onChange, style, ...restProps
/**
* Similar to {@link FormSelect}, but the top-level elements are actually groups.
*/
export function FormSelectWithGroups<T>({ name, id, values, keyProperty, titleProperty, currentValue, onChange }: FormSelectProps<T, FormSelectGroup<T>>) {
export function FormSelectWithGroups<T>({ name, id, values, keyProperty, titleProperty, currentValue, onChange }: FormSelectProps<T, FormSelectGroup<T> | T>) {
return (
<FormSelectBody name={name} id={id} onChange={onChange}>
{values.map(({ title, items }) => {
return (
<optgroup label={title}>
<FormSelectGroup values={items} keyProperty={keyProperty} titleProperty={titleProperty} currentValue={currentValue} />
</optgroup>
);
{values.map((item) => {
if (!item) return <></>;
if (typeof item === "object" && "items" in item) {
return (
<optgroup label={item.title}>
<FormSelectGroup values={item.items} keyProperty={keyProperty} titleProperty={titleProperty} currentValue={currentValue} />
</optgroup>
);
} else {
return (
<FormSelectGroup values={[ item ]} keyProperty={keyProperty} titleProperty={titleProperty} currentValue={currentValue} />
)
}
})}
</FormSelectBody>
)

View File

@ -1,11 +1,11 @@
import { useContext, useMemo } from "preact/hooks";
import { t } from "../../services/i18n";
import { ViewTypeOptions } from "../../services/note_list_renderer";
import FormSelect from "../react/FormSelect";
import FormSelect, { FormSelectWithGroups } from "../react/FormSelect";
import { TabContext } from "./ribbon-interface";
import { mapToKeyValueArray } from "../../services/utils";
import { useNoteLabel, useNoteLabelBoolean } from "../react/hooks";
import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, NumberProperty } from "../ribbon_widgets/book_properties_config";
import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxGroup, ComboBoxProperty, NumberProperty } from "../ribbon_widgets/book_properties_config";
import Button from "../react/Button";
import { ParentComponent } from "../react/react_utils";
import FNote from "../../entities/fnote";
@ -69,6 +69,8 @@ function mapPropertyView({ note, property }: { note: FNote, property: BookProper
return <CheckboxPropertyView note={note} property={property} />
case "number":
return <NumberPropertyView note={note} property={property} />
case "combobox":
return <ComboBoxPropertyView note={note} property={property} />
}
}
@ -117,4 +119,22 @@ function NumberPropertyView({ note, property }: { note: FNote, property: NumberP
</label>
</>
)
}
function ComboBoxPropertyView({ note, property }: { note: FNote, property: ComboBoxProperty }) {
const [ value, setValue ] = useNoteLabel(note, property.bindToLabel);
return (
<>
<label>
{property.label}
&nbsp;&nbsp;
<FormSelectWithGroups
values={property.options}
keyProperty="value" titleProperty="label"
currentValue={value ?? ""} onChange={setValue}
/>
</label>
</>
)
}

View File

@ -1,105 +0,0 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
import type FNote from "../../entities/fnote.js";
import type { EventData } from "../../components/app_context.js";
import { bookPropertiesConfig, BookProperty } from "./book_properties_config.js";
import attributes from "../../services/attributes.js";
export default class BookPropertiesWidget extends NoteContextAwareWidget {
private $viewTypeSelect!: JQuery<HTMLElement>;
private $propertiesContainer!: JQuery<HTMLElement>;
private labelsToWatch: string[] = [];
doRender() {
this.$viewTypeSelect = this.$widget.find(".view-type-select");
this.$viewTypeSelect.on("change", () => this.toggleViewType(String(this.$viewTypeSelect.val())));
this.$propertiesContainer = this.$widget.find(".book-properties-container");
}
async refreshWithNote(note: FNote) {
if (!this.note) {
return;
}
const viewType = this.note.getLabelValue("viewType") || "grid";
this.$viewTypeSelect.val(viewType);
this.$propertiesContainer.empty();
const bookPropertiesData = bookPropertiesConfig[viewType];
if (bookPropertiesData) {
for (const property of bookPropertiesData.properties) {
this.$propertiesContainer.append(this.renderBookProperty(property));
this.labelsToWatch.push(property.bindToLabel);
}
}
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
if (loadResults.getAttributeRows().find((attr) =>
attr.noteId === this.noteId
&& (attr.name === "viewType" || this.labelsToWatch.includes(attr.name ?? "")))) {
this.refresh();
}
}
renderBookProperty(property: BookProperty) {
const $container = $("<div>");
$container.addClass(`type-${property.type}`);
const note = this.note;
if (!note) {
return $container;
}
switch (property.type) {
case "combobox":
const $select = $("<select>", {
class: "form-select form-select-sm"
});
const actualValue = note.getLabelValue(property.bindToLabel) ?? property.defaultValue ?? "";
for (const option of property.options) {
if ("items" in option) {
const $optGroup = $("<optgroup>", { label: option.name });
for (const item of option.items) {
buildComboBoxItem(item, actualValue).appendTo($optGroup);
}
$optGroup.appendTo($select);
} else {
buildComboBoxItem(option, actualValue).appendTo($select);
}
}
$select.on("change", () => {
const value = $select.val();
if (value === null || value === "") {
attributes.removeOwnedLabelByName(note, property.bindToLabel);
} else {
attributes.setLabel(note.noteId, property.bindToLabel, String(value));
}
});
$container.append($("<label>")
.text(property.label)
.append("&nbsp;".repeat(2))
.append($select));
break;
}
return $container;
}
}
function buildComboBoxItem({ value, label }: { value: string, label: string }, actualValue: string) {
const $option = $("<option>", {
value,
text: label
});
if (actualValue === value) {
$option.prop("selected", true);
}
return $option;
}

View File

@ -37,11 +37,11 @@ interface ComboBoxItem {
}
interface ComboBoxGroup {
name: string;
title: string;
items: ComboBoxItem[];
}
interface ComboBoxProperty {
export interface ComboBoxProperty {
type: "combobox",
label: string;
bindToLabel: string;
@ -120,19 +120,19 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
defaultValue: DEFAULT_MAP_LAYER_NAME,
options: [
{
name: t("book_properties_config.raster"),
title: t("book_properties_config.raster"),
items: Object.entries(MAP_LAYERS)
.filter(([_, layer]) => layer.type === "raster")
.map(buildMapLayer)
},
{
name: t("book_properties_config.vector_light"),
title: t("book_properties_config.vector_light"),
items: Object.entries(MAP_LAYERS)
.filter(([_, layer]) => layer.type === "vector" && !layer.isDarkTheme)
.map(buildMapLayer)
},
{
name: t("book_properties_config.vector_dark"),
title: t("book_properties_config.vector_dark"),
items: Object.entries(MAP_LAYERS)
.filter(([_, layer]) => layer.type === "vector" && layer.isDarkTheme)
.map(buildMapLayer)