New layout: Integrate small ribbon categories + collection properties (#8018)

This commit is contained in:
Elian Doran 2025-12-11 20:59:31 +02:00 committed by GitHub
commit 792a10ace5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 347 additions and 78 deletions

View File

@ -2031,7 +2031,7 @@
"book_properties_config": { "book_properties_config": {
"hide-weekends": "Hide weekends", "hide-weekends": "Hide weekends",
"display-week-numbers": "Display week numbers", "display-week-numbers": "Display week numbers",
"map-style": "Map style:", "map-style": "Map style",
"max-nesting-depth": "Max nesting depth:", "max-nesting-depth": "Max nesting depth:",
"raster": "Raster", "raster": "Raster",
"vector_light": "Vector (Light)", "vector_light": "Vector (Light)",
@ -2146,6 +2146,12 @@
"backlinks_one": "{{count}} backlink", "backlinks_one": "{{count}} backlink",
"backlinks_other": "{{count}} backlinks", "backlinks_other": "{{count}} backlinks",
"backlinks_description_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.", "backlinks_description_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.",
"backlinks_description_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks." "backlinks_description_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks.",
"clipped_note": "Web clip",
"clipped_note_description": "This note was originally taken from {{url}}.\n\nClick to navigate to the source webpage.",
"execute_script": "Run script",
"execute_script_description": "This note is a script note. Click to execute the script.",
"execute_sql": "Run SQL",
"execute_sql_description": "This note is a SQL note. Click to execute the SQL query."
} }
} }

View File

@ -33,7 +33,9 @@
&.temporarily-editable-badge { --color: #4fa52b; } &.temporarily-editable-badge { --color: #4fa52b; }
&.read-only-badge { --color: #e33f3b; } &.read-only-badge { --color: #e33f3b; }
&.share-badge { --color: #3b82f6; } &.share-badge { --color: #3b82f6; }
&.clipped-note-badge { --color: #57a2a5; }
&.backlinks-badge { color: var(--badge-text-color); } &.backlinks-badge { color: var(--badge-text-color); }
&.execute-badge { --color: #f59e0b; }
a { a {
color: inherit !important; color: inherit !important;

View File

@ -8,7 +8,7 @@ import { t } from "../services/i18n";
import { formatDateTime } from "../utils/formatters"; import { formatDateTime } from "../utils/formatters";
import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions"; import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions";
import Dropdown, { DropdownProps } from "./react/Dropdown"; import Dropdown, { DropdownProps } from "./react/Dropdown";
import { useIsNoteReadOnly, useNoteContext, useStaticTooltip } from "./react/hooks"; import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useStaticTooltip } from "./react/hooks";
import Icon from "./react/Icon"; import Icon from "./react/Icon";
import { NoteSizeWidget, useNoteMetadata } from "./ribbon/NoteInfoTab"; import { NoteSizeWidget, useNoteMetadata } from "./ribbon/NoteInfoTab";
import { useShareInfo } from "./shared_info"; import { useShareInfo } from "./shared_info";
@ -20,6 +20,8 @@ export default function BreadcrumbBadges() {
<ReadOnlyBadge /> <ReadOnlyBadge />
<ShareBadge /> <ShareBadge />
<BacklinksBadge /> <BacklinksBadge />
<ClippedNoteBadge />
<ExecuteBadge />
</div> </div>
); );
} }
@ -114,6 +116,40 @@ function BacklinksBadge() {
); );
} }
function ClippedNoteBadge() {
const { note } = useNoteContext();
const [ pageUrl ] = useNoteLabel(note, "pageUrl");
return (pageUrl &&
<Badge
className="clipped-note-badge"
icon="bx bx-globe"
text={t("breadcrumb_badges.clipped_note")}
tooltip={t("breadcrumb_badges.clipped_note_description", { url: pageUrl })}
href={pageUrl}
/>
);
}
function ExecuteBadge() {
const { note, parentComponent } = useNoteContext();
const isScript = note?.isTriliumScript();
const isSql = note?.isTriliumSqlite();
const isExecutable = isScript || isSql;
const [ executeDescription ] = useNoteLabel(note, "executeDescription");
const [ executeButton ] = useNoteLabelBoolean(note, "executeButton");
return (note && isExecutable && (executeDescription || executeButton) &&
<Badge
className="execute-badge"
icon="bx bx-play"
text={isScript ? t("breadcrumb_badges.execute_script") : t("breadcrumb_badges.execute_sql")}
tooltip={executeDescription || (isScript ? t("breadcrumb_badges.execute_script_description") : t("breadcrumb_badges.execute_sql_description"))}
onClick={() => parentComponent.triggerCommand("runActiveNote")}
/>
);
}
interface BadgeProps { interface BadgeProps {
text?: string; text?: string;
icon?: string; icon?: string;

View File

@ -300,8 +300,9 @@ function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingB
function InAppHelpButton({ note }: FloatingButtonContext) { function InAppHelpButton({ note }: FloatingButtonContext) {
const helpUrl = getHelpUrlForNote(note); const helpUrl = getHelpUrlForNote(note);
const isEnabled = !!helpUrl && (!isNewLayout || (note?.type !== "book"));
return !!helpUrl && ( return isEnabled && (
<FloatingButton <FloatingButton
icon="bx bx-help-circle" icon="bx bx-help-circle"
text={t("help-button.title")} text={t("help-button.title")}

View File

@ -1,46 +1,13 @@
import { type ComponentChild } from "preact"; import CollectionProperties from "./note_bars/CollectionProperties";
import { useNoteContext, useNoteProperty } from "./react/hooks";
import { formatDateTime } from "../utils/formatters";
import { useNoteContext, useStaticTooltip } from "./react/hooks";
import { joinElements } from "./react/react_utils";
import { useNoteMetadata } from "./ribbon/NoteInfoTab";
import { Trans } from "react-i18next";
import { useRef } from "preact/hooks";
export default function NoteTitleDetails() { export default function NoteTitleDetails() {
const { note, noteContext } = useNoteContext(); const { note } = useNoteContext();
const isHiddenNote = note?.noteId.startsWith("_"); const noteType = useNoteProperty(note, "type");
const isDefaultView = noteContext?.viewScope?.viewMode === "default";
const items: ComponentChild[] = [].filter(item => !!item); return (
return items.length && (
<div className="title-details"> <div className="title-details">
{joinElements(items, " • ")} {note && noteType === "book" && <CollectionProperties note={note} />}
</div> </div>
); );
} }
function TextWithValue({ i18nKey, value, valueTooltip }: {
i18nKey: string;
value: string;
valueTooltip: string;
}) {
const listItemRef = useRef<HTMLLIElement>(null);
useStaticTooltip(listItemRef, {
selector: "span.value",
title: valueTooltip,
popperConfig: { placement: "bottom" }
});
return (
<li ref={listItemRef}>
<Trans
i18nKey={i18nKey}
components={{
Value: <span className="value">{value}</span> as React.ReactElement
}}
/>
</li>
);
}

View File

@ -0,0 +1,220 @@
import { t } from "i18next";
import { useContext } from "preact/hooks";
import { Fragment } from "preact/jsx-runtime";
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 } 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 { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab";
import ActionButton from "../react/ActionButton";
import { getHelpUrlForNote } from "../../services/in_app_help";
import { openInAppHelpFromUrl } from "../../services/utils";
const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
grid: "bx bxs-grid",
list: "bx bx-list-ul",
calendar: "bx bx-calendar",
table: "bx bx-table",
geoMap: "bx bx-map-alt",
board: "bx bx-columns",
presentation: "bx bx-rectangle"
};
export default function CollectionProperties({ note }: { note: FNote }) {
const [ viewType, setViewType ] = useViewType(note);
return (
<>
<ViewTypeSwitcher viewType={viewType} setViewType={setViewType} />
<ViewOptions note={note} viewType={viewType} />
<div className="spacer" />
<HelpButton note={note} />
</>
);
}
function ViewTypeSwitcher({ viewType, setViewType }: { viewType: ViewTypeOptions, setViewType: (newValue: ViewTypeOptions) => void }) {
return (
<Dropdown
text={<>
<Icon icon={ICON_MAPPINGS[viewType]} />&nbsp;
{VIEW_TYPE_MAPPINGS[viewType]}
</>}
>
{Object.entries(VIEW_TYPE_MAPPINGS).map(([ key, label ]) => (
<FormListItem
key={key}
onClick={() => setViewType(key as ViewTypeOptions)}
selected={viewType === key}
disabled={viewType === key}
icon={ICON_MAPPINGS[key as ViewTypeOptions]}
>{label}</FormListItem>
))}
</Dropdown>
);
}
function ViewOptions({ note, viewType }: { note: FNote, viewType: ViewTypeOptions }) {
const properties = bookPropertiesConfig[viewType].properties;
return (
<Dropdown
buttonClassName="bx bx-cog icon-action"
hideToggleArrow
>
{properties.map(property => (
<ViewProperty key={property.label} note={note} property={property} />
))}
{properties.length > 0 && <FormDropdownDivider />}
<ViewProperty note={note} property={{
type: "checkbox",
icon: "bx bx-archive",
label: t("book_properties.include_archived_notes"),
bindToLabel: "includeArchived"
} as CheckBoxProperty} />
</Dropdown>
);
}
function ViewProperty({ note, property }: { note: FNote, property: BookProperty }) {
switch (property.type) {
case "button":
return <ButtonPropertyView note={note} property={property} />;
case "split-button":
return <SplitButtonPropertyView note={note} property={property} />;
case "checkbox":
return <CheckBoxPropertyView note={note} property={property} />;
case "number":
return <NumberPropertyView note={note} property={property} />;
case "combobox":
return <ComboBoxPropertyView note={note} property={property} />;
}
}
function ButtonPropertyView({ note, property }: { note: FNote, property: ButtonProperty }) {
const parentComponent = useContext(ParentComponent);
return (
<FormListItem
icon={property.icon}
title={property.title}
onClick={() => {
if (!parentComponent) return;
property.onClick({
note,
triggerCommand: parentComponent.triggerCommand.bind(parentComponent)
});
}}
>{property.label}</FormListItem>
);
}
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 &&
<FormDropdownSubmenu
icon={property.icon ?? "bx bx-empty"}
title={property.label}
onDropdownToggleClicked={() => clickContext && property.onClick(clickContext)}
>
<ItemsComponent note={note} parentComponent={parentComponent} />
</FormDropdownSubmenu>
);
}
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 (
<FormListItem
icon={property.icon}
disabled={disabled}
onClick={(e) => e.stopPropagation()}
>
{property.label}
<FormTextBox
type="number"
currentValue={value ?? ""} onChange={setValue}
style={{ width: (property.width ?? 100) }}
min={property.min ?? 0}
disabled={disabled}
/>
</FormListItem>
);
}
function ComboBoxPropertyView({ note, property }: { note: FNote, property: ComboBoxProperty }) {
const [ value, setValue ] = useNoteLabelWithDefault(note, property.bindToLabel, property.defaultValue ?? "");
function renderItem(option: ComboBoxItem) {
return (
<FormListItem
key={option.value}
checked={value === option.value}
onClick={() => setValue(option.value)}
>
{option.label}
</FormListItem>
);
}
return (
<FormDropdownSubmenu
title={property.label}
icon={property.icon ?? "bx bx-empty"}
>
{(property.options).map((option, index) => {
if ("items" in option) {
return (
<Fragment key={option.title}>
<FormListItem key={option.title} disabled>{option.title}</FormListItem>
{option.items.map(renderItem)}
{index < property.options.length - 1 && <FormDropdownDivider />}
</Fragment>
);
} else {
return renderItem(option);
}
})}
</FormDropdownSubmenu>
);
}
function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) {
const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel);
return (
<FormListToggleableItem
icon={property.icon}
title={property.label}
currentValue={value}
onChange={setValue}
/>
);
}
function HelpButton({ note }: { note: FNote }) {
const helpUrl = getHelpUrlForNote(note);
return (helpUrl && (
<ActionButton
icon="bx bx-help-circle"
onClick={(() => openInAppHelpFromUrl(helpUrl))}
text={t("help-button.title")}
/>
));
}

View File

@ -37,6 +37,21 @@ body.experimental-feature-new-layout {
padding-inline-start: 24px; padding-inline-start: 24px;
} }
.title-details {
padding-inline-end: 16px;
.dropdown-menu {
input.form-control {
padding: 2px 8px;
margin-left: 1em;
}
}
.spacer {
flex-grow: 1;
}
}
.title-row { .title-row {
margin-left: 12px; margin-left: 12px;
@ -75,7 +90,8 @@ body.experimental-feature-new-layout {
} }
} }
.scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)) { .scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)),
.note-split.type-book {
.title-row, .title-row,
.title-details { .title-details {
width: 100%; width: 100%;
@ -84,12 +100,11 @@ body.experimental-feature-new-layout {
} }
.title-row { .title-row {
margin-top: 0; padding: 0;
} }
.title-details { .title-details {
margin-bottom: 0.2em; padding-bottom: 0.2em;
opacity: 0.65;
font-size: 0.8em; font-size: 0.8em;
} }
} }

View File

@ -206,10 +206,11 @@ export function FormDropdownDivider() {
return <div className="dropdown-divider" />; return <div className="dropdown-divider" />;
} }
export function FormDropdownSubmenu({ icon, title, children, dropStart }: { export function FormDropdownSubmenu({ icon, title, children, dropStart, onDropdownToggleClicked }: {
icon: string, icon: string,
title: ComponentChildren, title: ComponentChildren,
children: ComponentChildren, children: ComponentChildren,
onDropdownToggleClicked?: () => void,
dropStart?: boolean dropStart?: boolean
}) { }) {
const [ openOnMobile, setOpenOnMobile ] = useState(false); const [ openOnMobile, setOpenOnMobile ] = useState(false);
@ -224,6 +225,10 @@ export function FormDropdownSubmenu({ icon, title, children, dropStart }: {
if (isMobile()) { if (isMobile()) {
setOpenOnMobile(!openOnMobile); setOpenOnMobile(!openOnMobile);
} }
if (onDropdownToggleClicked) {
onDropdownToggleClicked();
}
}} }}
> >
<Icon icon={icon} />{" "} <Icon icon={icon} />{" "}

View File

@ -12,34 +12,41 @@ import FormCheckbox from "../react/FormCheckbox";
import FormTextBox from "../react/FormTextBox"; import FormTextBox from "../react/FormTextBox";
import { ComponentChildren } from "preact"; import { ComponentChildren } from "preact";
import { ViewTypeOptions } from "../collections/interface"; import { ViewTypeOptions } from "../collections/interface";
import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
const VIEW_TYPE_MAPPINGS: Record<ViewTypeOptions, string> = { export const VIEW_TYPE_MAPPINGS: Record<ViewTypeOptions, string> = {
grid: t("book_properties.grid"), grid: t("book_properties.grid"),
list: t("book_properties.list"), list: t("book_properties.list"),
calendar: t("book_properties.calendar"), calendar: t("book_properties.calendar"),
table: t("book_properties.table"), table: t("book_properties.table"),
geoMap: t("book_properties.geo-map"), geoMap: t("book_properties.geo-map"),
board: t("book_properties.board"), board: t("book_properties.board"),
presentation: t("book_properties.presentation") presentation: t("book_properties.presentation")
}; };
export default function CollectionPropertiesTab({ note }: TabContext) { const isNewLayout = isExperimentalFeatureEnabled("new-layout");
const [ viewType, setViewType ] = useNoteLabel(note, "viewType");
const defaultViewType = (note?.type === "search" ? "list" : "grid");
const viewTypeWithDefault = (viewType ?? defaultViewType) as ViewTypeOptions;
const properties = bookPropertiesConfig[viewTypeWithDefault].properties;
return ( export default function CollectionPropertiesTab({ note }: TabContext) {
<div className="book-properties-widget"> const [viewType, setViewType] = useViewType(note);
{note && ( const properties = bookPropertiesConfig[viewType].properties;
<>
<CollectionTypeSwitcher viewType={viewTypeWithDefault} setViewType={setViewType} /> return (
<BookProperties viewType={viewTypeWithDefault} note={note} properties={properties} /> <div className="book-properties-widget">
</> {note && (
)} <>
</div> {!isNewLayout && <CollectionTypeSwitcher viewType={viewType} setViewType={setViewType} />}
); <BookProperties viewType={viewType} note={note} properties={properties} />
</>
)}
</div>
);
}
export function useViewType(note: FNote | null | undefined) {
const [ viewType, setViewType ] = useNoteLabel(note, "viewType");
const defaultViewType = (note?.type === "search" ? "list" : "grid");
const viewTypeWithDefault = (viewType ?? defaultViewType) as ViewTypeOptions;
return [ viewTypeWithDefault, setViewType ] as const;
} }
function CollectionTypeSwitcher({ viewType, setViewType }: { viewType: string, setViewType: (newValue: string) => void }) { function CollectionTypeSwitcher({ viewType, setViewType }: { viewType: string, setViewType: (newValue: string) => void }) {
@ -148,7 +155,7 @@ function NumberPropertyView({ note, property }: { note: FNote, property: NumberP
<FormTextBox <FormTextBox
type="number" type="number"
currentValue={value ?? ""} onChange={setValue} currentValue={value ?? ""} onChange={setValue}
style={{ width: (property.width ?? 100) + "px" }} style={{ width: (property.width ?? 100) }}
min={property.min ?? 0} min={property.min ?? 0}
disabled={disabled} disabled={disabled}
/> />

View File

@ -38,7 +38,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
icon: "bx bx-play", icon: "bx bx-play",
content: ScriptTab, content: ScriptTab,
activate: true, activate: true,
show: ({ note }) => note && show: ({ note }) => note && !isNewLayout &&
(note.isTriliumScript() || note.isTriliumSqlite()) && (note.isTriliumScript() || note.isTriliumSqlite()) &&
(note.hasLabel("executeDescription") || note.hasLabel("executeButton")) (note.hasLabel("executeDescription") || note.hasLabel("executeButton"))
}, },
@ -60,14 +60,14 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
title: t("book_properties.book_properties"), title: t("book_properties.book_properties"),
icon: "bx bx-book", icon: "bx bx-book",
content: CollectionPropertiesTab, content: CollectionPropertiesTab,
show: ({ note }) => note?.type === "book" || note?.type === "search", show: ({ note }) => !isNewLayout && note?.type === "book" || note?.type === "search",
toggleCommand: "toggleRibbonTabBookProperties" toggleCommand: "toggleRibbonTabBookProperties"
}, },
{ {
title: t("note_properties.info"), title: t("note_properties.info"),
icon: "bx bx-info-square", icon: "bx bx-info-square",
content: NotePropertiesTab, content: NotePropertiesTab,
show: ({ note }) => !!note?.getLabelValue("pageUrl"), show: ({ note }) => !isNewLayout && !!note?.getLabelValue("pageUrl"),
activate: true activate: true
}, },
{ {

View File

@ -18,7 +18,8 @@ interface BookConfig {
export interface CheckBoxProperty { export interface CheckBoxProperty {
type: "checkbox", type: "checkbox",
label: string; label: string;
bindToLabel: FilterLabelsByType<boolean> bindToLabel: FilterLabelsByType<boolean>;
icon?: string;
} }
export interface ButtonProperty { export interface ButtonProperty {
@ -40,10 +41,11 @@ export interface NumberProperty {
bindToLabel: FilterLabelsByType<number>; bindToLabel: FilterLabelsByType<number>;
width?: number; width?: number;
min?: number; min?: number;
icon?: string;
disabled?: (note: FNote) => boolean; disabled?: (note: FNote) => boolean;
} }
interface ComboBoxItem { export interface ComboBoxItem {
value: string; value: string;
label: string; label: string;
} }
@ -56,6 +58,7 @@ interface ComboBoxGroup {
export interface ComboBoxProperty { export interface ComboBoxProperty {
type: "combobox", type: "combobox",
label: string; label: string;
icon?: string;
bindToLabel: FilterLabelsByType<string>; bindToLabel: FilterLabelsByType<string>;
/** /**
* The default value is used when the label is not set. * The default value is used when the label is not set.
@ -107,11 +110,13 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
properties: [ properties: [
{ {
label: t("book_properties_config.hide-weekends"), label: t("book_properties_config.hide-weekends"),
icon: "bx bx-calendar-week",
type: "checkbox", type: "checkbox",
bindToLabel: "calendar:hideWeekends" bindToLabel: "calendar:hideWeekends"
}, },
{ {
label: t("book_properties_config.display-week-numbers"), label: t("book_properties_config.display-week-numbers"),
icon: "bx bx-hash",
type: "checkbox", type: "checkbox",
bindToLabel: "calendar:weekNumbers" bindToLabel: "calendar:weekNumbers"
} }
@ -121,6 +126,7 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
properties: [ properties: [
{ {
label: t("book_properties_config.map-style"), label: t("book_properties_config.map-style"),
icon: "bx bx-palette",
type: "combobox", type: "combobox",
bindToLabel: "map:style", bindToLabel: "map:style",
defaultValue: DEFAULT_MAP_LAYER_NAME, defaultValue: DEFAULT_MAP_LAYER_NAME,
@ -147,6 +153,7 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
}, },
{ {
label: t("book_properties_config.show-scale"), label: t("book_properties_config.show-scale"),
icon: "bx bx-ruler",
type: "checkbox", type: "checkbox",
bindToLabel: "map:scale" bindToLabel: "map:scale"
} }
@ -156,6 +163,7 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
properties: [ properties: [
{ {
label: t("book_properties_config.max-nesting-depth"), label: t("book_properties_config.max-nesting-depth"),
icon: "bx bx-subdirectory-right",
type: "number", type: "number",
bindToLabel: "maxNestingDepth", bindToLabel: "maxNestingDepth",
width: 65, width: 65,
@ -171,6 +179,7 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
{ {
label: "Theme", label: "Theme",
type: "combobox", type: "combobox",
icon: "bx bx-palette",
bindToLabel: "presentation:theme", bindToLabel: "presentation:theme",
defaultValue: DEFAULT_THEME, defaultValue: DEFAULT_THEME,
options: getPresentationThemes().map(theme => ({ options: getPresentationThemes().map(theme => ({

View File

@ -5,6 +5,7 @@ type Labels = {
color: string; color: string;
iconClass: string; iconClass: string;
workspaceIconClass: string; workspaceIconClass: string;
executeButton: boolean;
executeDescription: string; executeDescription: string;
executeTitle: string; executeTitle: string;
limit: string; // should be probably be number limit: string; // should be probably be number