import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks"; import { useNoteContext, useNoteProperty, useStaticTooltipWithKeyboardShortcut, useTriliumEvents } from "../react/hooks"; import "./style.css"; import { Indexed, numberObjectsInPlace } from "../../services/utils"; import { EventNames } from "../../components/app_context"; import NoteActions from "./NoteActions"; import { KeyboardActionNames } from "@triliumnext/commons"; import { RIBBON_TAB_DEFINITIONS } from "./RibbonDefinition"; import { TabConfiguration, TitleContext } from "./ribbon-interface"; const TAB_CONFIGURATION = numberObjectsInPlace(RIBBON_TAB_DEFINITIONS); interface ComputedTab extends Indexed { shouldShow: boolean; } export default function Ribbon() { const { note, ntxId, hoistedNoteId, notePath, noteContext, componentId, isReadOnlyTemporarilyDisabled } = useNoteContext(); const noteType = useNoteProperty(note, "type"); const [ activeTabIndex, setActiveTabIndex ] = useState(); const [ computedTabs, setComputedTabs ] = useState(); const titleContext: TitleContext = useMemo(() => ({ note, noteContext }), [ note, noteContext ]); async function refresh() { const computedTabs: ComputedTab[] = []; for (const tab of TAB_CONFIGURATION) { const shouldShow = await shouldShowTab(tab.show, titleContext); computedTabs.push({ ...tab, shouldShow: !!shouldShow }); } setComputedTabs(computedTabs); } useEffect(() => { refresh(); }, [ note, noteType, isReadOnlyTemporarilyDisabled ]); // Automatically activate the first ribbon tab that needs to be activated whenever a note changes. useEffect(() => { if (!computedTabs) return; const tabToActivate = computedTabs.find(tab => tab.shouldShow && (typeof tab.activate === "boolean" ? tab.activate : tab.activate?.(titleContext))); setActiveTabIndex(tabToActivate?.index); }, [ computedTabs, note?.noteId ]); // Register keyboard shortcuts. const eventsToListenTo = useMemo(() => TAB_CONFIGURATION.filter(config => config.toggleCommand).map(config => config.toggleCommand) as EventNames[], []); useTriliumEvents(eventsToListenTo, useCallback((e, toggleCommand) => { if (!computedTabs) return; const correspondingTab = computedTabs.find(tab => tab.toggleCommand === toggleCommand); if (correspondingTab) { if (activeTabIndex !== correspondingTab.index) { setActiveTabIndex(correspondingTab.index); } else { setActiveTabIndex(undefined); } } }, [ computedTabs, activeTabIndex ])); return (
{noteContext?.viewScope?.viewMode === "default" && ( <>
{computedTabs && computedTabs.map(({ title, icon, index, toggleCommand, shouldShow }) => ( shouldShow && { if (activeTabIndex !== index) { setActiveTabIndex(index); } else { // Collapse setActiveTabIndex(undefined); } }} /> ))}
{ note && }
{computedTabs && computedTabs.map(tab => { const isActive = tab.index === activeTabIndex; if (!isActive && !tab.stayInDom) { return; } const TabContent = tab.content; return (
); })}
)}
) } function RibbonTab({ icon, title, active, onClick, toggleCommand }: { icon: string; title: string; active: boolean, onClick: () => void, toggleCommand?: KeyboardActionNames }) { const iconRef = useRef(null); useStaticTooltipWithKeyboardShortcut(iconRef, title, toggleCommand); return ( <>
  { active && {title} }
) } export async function shouldShowTab(showConfig: boolean | ((context: TitleContext) => Promise | boolean | null | undefined), context: TitleContext) { if (showConfig === null || showConfig === undefined) return true; if (typeof showConfig === "boolean") return showConfig; if ("then" in showConfig) return await showConfig(context); return showConfig(context); }