chore(react/ribbon): bring back tab filtering

This commit is contained in:
Elian Doran 2025-08-22 15:53:52 +03:00
parent bf0213907e
commit b99d01ad7b
No known key found for this signature in database
5 changed files with 45 additions and 68 deletions

View File

@ -788,6 +788,22 @@ export function arrayEqual<T>(a: T[], b: T[]) {
return true; return true;
} }
type Indexed<T extends object> = T & { index: number };
/**
* Given an object array, alters every object in the array to have an index field assigned to it.
*
* @param items the objects to be numbered.
* @returns the same object for convenience, with the type changed to indicate the new index field.
*/
export function numberObjectsInPlace<T extends object>(items: T[]): Indexed<T>[] {
let index = 0;
for (const item of items) {
(item as Indexed<T>).index = index++;
}
return items as Indexed<T>[];
}
export default { export default {
reloadFrontendApp, reloadFrontendApp,
restartDesktopApp, restartDesktopApp,

View File

@ -3,7 +3,7 @@ import Dropdown from "../react/Dropdown";
import { NOTE_TYPES } from "../../services/note_types"; import { NOTE_TYPES } from "../../services/note_types";
import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList"; import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList";
import { getAvailableLocales, t } from "../../services/i18n"; import { getAvailableLocales, t } from "../../services/i18n";
import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEventBeta, useTriliumOption, useTriliumOptionBeta, useTriliumOptionJson } from "../react/hooks"; import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEventBeta, useTriliumOption, useTriliumOptionBeta, useTriliumOptionJson } from "../react/hooks";
import mime_types from "../../services/mime_types"; import mime_types from "../../services/mime_types";
import { Locale, NoteType, ToggleInParentResponse } from "@triliumnext/commons"; import { Locale, NoteType, ToggleInParentResponse } from "@triliumnext/commons";
import server from "../../services/server"; import server from "../../services/server";
@ -17,10 +17,9 @@ import branches from "../../services/branches";
import sync from "../../services/sync"; import sync from "../../services/sync";
import appContext from "../../components/app_context"; import appContext from "../../components/app_context";
import HelpButton from "../react/HelpButton"; import HelpButton from "../react/HelpButton";
import { TabContext } from "./ribbon-interface";
export default function BasicPropertiesTab() { export default function BasicPropertiesTab({ note }: TabContext) {
const { note } = useNoteContext();
return ( return (
<div className="basic-properties-widget"> <div className="basic-properties-widget">
<NoteTypeWidget note={note} /> <NoteTypeWidget note={note} />

View File

@ -1,24 +1,21 @@
import { useState } from "preact/hooks"; import { useMemo, useState } from "preact/hooks";
import FNote from "../../entities/fnote"; import FNote from "../../entities/fnote";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import { useNoteContext } from "../react/hooks"; import { useNoteContext } from "../react/hooks";
import "./style.css"; import "./style.css";
import { VNode } from "preact"; import { VNode } from "preact";
import BasicPropertiesTab from "./BasicPropertiesTab"; import BasicPropertiesTab from "./BasicPropertiesTab";
import { numberObjectsInPlace } from "../../services/utils";
type TitleFn = string | ((context: TabContext) => string); import { TabContext } from "./ribbon-interface";
interface TabContext {
note: FNote | null | undefined;
}
interface TabConfiguration { interface TabConfiguration {
title: TitleFn; title: string | ((context: TabContext) => string);
icon: string; icon: string;
content?: () => VNode; // TODO: Mark as required after porting them all.
content?: (context: TabContext) => VNode;
show?: (context: TabContext) => boolean;
} }
const TAB_CONFIGURATION: TabConfiguration[] = [ const TAB_CONFIGURATION = numberObjectsInPlace<TabConfiguration>([
{ {
title: t("classic_editor_toolbar.title"), title: t("classic_editor_toolbar.title"),
icon: "bx bx-text" icon: "bx bx-text"
@ -63,7 +60,8 @@ const TAB_CONFIGURATION: TabConfiguration[] = [
// BasicProperties // BasicProperties
title: t("basic_properties.basic_properties"), title: t("basic_properties.basic_properties"),
icon: "bx bx-slider", icon: "bx bx-slider",
content: BasicPropertiesTab content: BasicPropertiesTab,
show: ({note}) => !note?.isLaunchBarConfig()
}, },
{ {
// OwnedAttributeListWidget // OwnedAttributeListWidget
@ -94,31 +92,31 @@ const TAB_CONFIGURATION: TabConfiguration[] = [
// NoteInfoWidget // NoteInfoWidget
title: t("note_info_widget.title"), title: t("note_info_widget.title"),
icon: "bx bx-info-circle" icon: "bx bx-info-circle"
}, }
]);
];
export default function Ribbon() { export default function Ribbon() {
const { note } = useNoteContext(); const { note } = useNoteContext();
const context: TabContext = { note }; const context: TabContext = { note };
const [ activeTab, setActiveTab ] = useState<number | undefined>(8); const [ activeTabIndex, setActiveTabIndex ] = useState<number | undefined>();
const activeTabConfiguration = activeTab ? TAB_CONFIGURATION[activeTab] : undefined; const filteredTabs = useMemo(() => TAB_CONFIGURATION.filter(tab => tab.show?.(context)), [ context, note ])
const activeTabConfiguration = activeTabIndex ? filteredTabs.find(tab => tab.index === activeTabIndex) : undefined;
return ( return (
<div class="ribbon-container" style={{ contain: "none" }}> <div class="ribbon-container" style={{ contain: "none" }}>
<div className="ribbon-top-row"> <div className="ribbon-top-row">
<div className="ribbon-tab-container"> <div className="ribbon-tab-container">
{TAB_CONFIGURATION.map(({ title, icon }, i) => ( {filteredTabs.map(({ title, icon, index }) => (
<RibbonTab <RibbonTab
icon={icon} icon={icon}
title={typeof title === "string" ? title : title(context)} title={typeof title === "string" ? title : title(context)}
active={i === activeTab} active={index === activeTabIndex}
onClick={() => { onClick={() => {
if (activeTab !== i) { if (activeTabIndex !== index) {
setActiveTab(i); setActiveTabIndex(index);
} else { } else {
// Collapse // Collapse
setActiveTab(undefined); setActiveTabIndex(undefined);
} }
}} }}
/> />
@ -129,7 +127,7 @@ export default function Ribbon() {
<div className="ribbon-body-container"> <div className="ribbon-body-container">
<div className="ribbon-body"> <div className="ribbon-body">
{activeTabConfiguration?.content && activeTabConfiguration.content()} {activeTabConfiguration?.content && activeTabConfiguration.content({ note })}
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,5 @@
import FNote from "../../entities/fnote";
export interface TabContext {
note: FNote | null | undefined;
}

View File

@ -1,41 +0,0 @@
import { t } from "../services/i18n.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
export default class SwitchWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.$switchButton = this.$widget.find(".switch-button");
this.$switchToggle = this.$widget.find(".switch-toggle");
this.$switchName = this.$widget.find(".switch-name");
this.$helpButton = this.$widget.find(".switch-help-button");
}
switchOff() {}
switchOn() {}
/** Gets or sets whether the switch is toggled. */
get isToggled() {
return this.currentState;
}
set isToggled(state) {
this.currentState = !!state;
this.$switchButton.toggleClass("on", this.currentState);
}
/** Gets or sets whether the switch is enabled. */
get canToggle() {
return !this.$switchButton.hasClass("disabled");
}
set canToggle(isEnabled) {
if (isEnabled) {
this.isToggled = this.currentState; // Reapply the correct tooltip
} else {
this.$switchButton.attr("title", this.disabledTooltip);
}
}
}