From cabe240e7ee8c6066637ee4fd8511aaf6b63060f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 28 Aug 2025 00:06:40 +0300 Subject: [PATCH] refactor(react/floating_buttons): split into two buttons --- apps/client/src/widgets/FloatingButtons.tsx | 305 +---------------- .../widgets/FloatingButtonsDefinitions.tsx | 306 ++++++++++++++++++ 2 files changed, 308 insertions(+), 303 deletions(-) create mode 100644 apps/client/src/widgets/FloatingButtonsDefinitions.tsx diff --git a/apps/client/src/widgets/FloatingButtons.tsx b/apps/client/src/widgets/FloatingButtons.tsx index c42d8d01d..97db0ff3b 100644 --- a/apps/client/src/widgets/FloatingButtons.tsx +++ b/apps/client/src/widgets/FloatingButtons.tsx @@ -1,107 +1,12 @@ import { t } from "i18next"; import "./FloatingButtons.css"; import Button from "./react/Button"; -import ActionButton, { ActionButtonProps } from "./react/ActionButton"; -import FNote from "../entities/fnote"; -import NoteContext from "../components/note_context"; import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumEvents, useTriliumOption, useTriliumOptionBool } from "./react/hooks"; import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks"; import { ParentComponent } from "./react/react_utils"; -import Component from "../components/component"; -import { VNode } from "preact"; import attributes from "../services/attributes"; -import appContext, { EventData, EventNames } from "../components/app_context"; -import protected_session_holder from "../services/protected_session_holder"; -import options from "../services/options"; -import { createImageSrcUrl, openInAppHelpFromUrl } from "../services/utils"; -import toast from "../services/toast"; -import server from "../services/server"; -import { SaveSqlConsoleResponse } from "@triliumnext/commons"; -import tree from "../services/tree"; -import { copyImageReferenceToClipboard } from "../services/image"; - -interface FloatingButtonContext { - parentComponent: Component; - note: FNote; - noteContext: NoteContext; - /** Shorthand for triggering an event from the parent component. The `ntxId` is automatically handled for convenience. */ - triggerEvent(name: T, data?: Omit, "ntxId">): void; -} - -interface FloatingButtonDefinition { - component: (context: FloatingButtonContext) => VNode; - isEnabled: (context: FloatingButtonContext) => boolean | Promise; -} - -const FLOATING_BUTTON_DEFINITIONS: FloatingButtonDefinition[] = [ - { - component: RefreshBackendLogButton, - isEnabled: ({ note, noteContext }) => note.noteId === "_backendLog" && noteContext.viewScope?.viewMode === "default", - }, - { - component: SwitchSplitOrientationButton, - isEnabled: ({ note, noteContext }) => note.type === "mermaid" && note.isContentAvailable() && !note.hasLabel("readOnly") && noteContext.viewScope?.viewMode === "default" - }, - { - component: ToggleReadOnlyButton, - isEnabled: ({ note, noteContext }) => - (note.type === "mermaid" || note.getLabelValue("viewType") === "geoMap") - && note.isContentAvailable() - && noteContext.viewScope?.viewMode === "default" - }, - { - component: EditButton, - isEnabled: async ({ note, noteContext }) => - noteContext.viewScope?.viewMode === "default" - && (!note.isProtected || protected_session_holder.isProtectedSessionAvailable()) - && !options.is("databaseReadonly") - && await noteContext?.isReadOnly() - }, - { - component: ShowTocWidgetButton, - isEnabled: ({ note, noteContext }) => - note.type === "text" && noteContext?.viewScope?.viewMode === "default" - && !!noteContext.viewScope?.tocTemporarilyHidden - }, - { - component: ShowHighlightsListWidgetButton, - isEnabled: ({ note, noteContext }) => - note.type === "text" && noteContext?.viewScope?.viewMode === "default" - && !!noteContext.viewScope?.highlightsListTemporarilyHidden - }, - { - component: RunActiveNoteButton, - isEnabled: ({ note }) => note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium" - }, - { - component: OpenTriliumApiDocsButton, - isEnabled: ({ note }) => note.mime.startsWith("application/javascript;env=") - }, - { - component: SaveToNoteButton, - isEnabled: ({ note }) => note.mime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely() - }, - { - component: RelationMapButtons, - isEnabled: ({ note }) => note.type === "relationMap" - }, - { - component: GeoMapButtons, - isEnabled: ({ note }) => note?.getLabelValue("viewType") === "geoMap" && !note.hasLabel("readOnly") - }, - { - component: CopyImageReferenceButton, - isEnabled: ({ note, noteContext }) => - ["mermaid", "canvas", "mindMap"].includes(note?.type ?? "") - && note?.isContentAvailable() && noteContext.viewScope?.viewMode === "default" - }, - { - component: ExportImageButtons, - isEnabled: ({ note, noteContext }) => - ["mermaid", "mindMap"].includes(note?.type ?? "") - && note?.isContentAvailable() && noteContext?.viewScope?.viewMode === "default" - } -]; +import { EventData, EventNames } from "../components/app_context"; +import { FLOATING_BUTTON_DEFINITIONS, FloatingButtonContext, FloatingButtonDefinition } from "./FloatingButtonsDefinitions"; async function getFloatingButtonDefinitions(context: FloatingButtonContext) { const defs: FloatingButtonDefinition[] = []; @@ -178,212 +83,6 @@ export default function FloatingButtons() { ) } -function RefreshBackendLogButton({ parentComponent, noteContext }: FloatingButtonContext) { - return parentComponent.triggerEvent("refreshData", { ntxId: noteContext.ntxId })} - /> -} - -function SwitchSplitOrientationButton({ }: FloatingButtonContext) { - const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation"); - const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal"; - - return setSplitEditorOrientation(upcomingOrientation)} - /> -} - -function ToggleReadOnlyButton({ note }: FloatingButtonContext) { - const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly"); - - return setReadOnly(!isReadOnly)} - /> -} - -function EditButton({ noteContext }: FloatingButtonContext) { - const [ animationClass, setAnimationClass ] = useState(""); - - // make the edit button stand out on the first display, otherwise - // it's difficult to notice that the note is readonly - useEffect(() => { - setAnimationClass("bx-tada bx-lg"); - setTimeout(() => { - setAnimationClass(""); - }, 1700); - }, []); - - return { - if (noteContext.viewScope) { - noteContext.viewScope.readOnlyTemporarilyDisabled = true; - appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext }); - } - }} - /> -} - -function ShowTocWidgetButton({ noteContext }: FloatingButtonContext) { - return { - if (noteContext?.viewScope && noteContext.noteId) { - noteContext.viewScope.tocTemporarilyHidden = false; - appContext.triggerEvent("showTocWidget", { noteId: noteContext.noteId }); - } - }} - /> -} - -function ShowHighlightsListWidgetButton({ noteContext }: FloatingButtonContext) { - return { - if (noteContext?.viewScope && noteContext.noteId) { - noteContext.viewScope.highlightsListTemporarilyHidden = false; - appContext.triggerEvent("showHighlightsListWidget", { noteId: noteContext.noteId }); - } - }} - /> -} - -function RunActiveNoteButton() { - return -} - -function OpenTriliumApiDocsButton({ note }: FloatingButtonContext) { - return openInAppHelpFromUrl(note.mime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")} - /> -} - -function SaveToNoteButton({ note }: FloatingButtonContext) { - return { - e.preventDefault(); - const { notePath } = await server.post("special-notes/save-sql-console", { sqlConsoleNoteId: note.noteId }); - if (notePath) { - toast.showMessage(t("code_buttons.sql_console_saved_message", { "note_path": await tree.getNotePathTitle(notePath) })); - // TODO: This hangs the navigation, for some reason. - //await ws.waitForMaxKnownEntityChangeId(); - await appContext.tabManager.getActiveContext()?.setNote(notePath); - } - }} - /> -} - -function RelationMapButtons({ triggerEvent }: FloatingButtonContext) { - return ( - <> - triggerEvent("relationMapCreateChildNote")} - /> - - triggerEvent("relationMapResetPanZoom")} - /> - -
- triggerEvent("relationMapResetZoomIn")} - /> - - triggerEvent("relationMapResetZoomOut")} - /> -
- - ) -} - -function GeoMapButtons({ triggerEvent }) { - return ( - triggerEvent("geoMapCreateChildNote")} - /> - ); -} - -function CopyImageReferenceButton({ note }: FloatingButtonContext) { - const hiddenImageCopyRef = useRef(null); - - return ( - <> - { - if (!hiddenImageCopyRef.current) return; - const imageEl = document.createElement("img"); - imageEl.src = createImageSrcUrl(note); - hiddenImageCopyRef.current.replaceChildren(imageEl); - copyImageReferenceToClipboard($(hiddenImageCopyRef.current)); - hiddenImageCopyRef.current.removeChild(imageEl); - }} - /> - -