import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks"; import { EventData, EventNames } from "../../components/app_context"; import { ParentComponent } from "./ReactBasicWidget"; import SpacedUpdate from "../../services/spaced_update"; import { OptionNames } from "@triliumnext/commons"; import options from "../../services/options"; import utils, { reloadFrontendApp } from "../../services/utils"; import { __values } from "tslib"; /** * Allows a React component to react to Trilium events (e.g. `entitiesReloaded`). When the desired event is triggered, the handler is invoked with the event parameters. * * Under the hood, it works by altering the parent (Trilium) component of the React element to introduce the corresponding event. * * @param eventName the name of the Trilium event to listen for. * @param handler the handler to be invoked when the event is triggered. * @param enabled determines whether the event should be listened to or not. Useful to conditionally limit the listener based on a state (e.g. a modal being displayed). */ export default function useTriliumEvent(eventName: T, handler: (data: EventData) => void, enabled = true) { const parentWidget = useContext(ParentComponent); useEffect(() => { if (!parentWidget || !enabled) { return; } // Create a unique handler name for this specific event listener const handlerName = `${eventName}Event`; const originalHandler = parentWidget[handlerName]; // Override the event handler to call our handler parentWidget[handlerName] = async function(data: EventData) { // Call original handler if it exists if (originalHandler) { await originalHandler.call(parentWidget, data); } // Call our React component's handler handler(data); }; // Cleanup: restore original handler on unmount or when disabled return () => { parentWidget[handlerName] = originalHandler; }; }, [parentWidget, enabled, eventName, handler]); } export function useSpacedUpdate(callback: () => Promise, interval = 1000) { const callbackRef = useRef(callback); const spacedUpdateRef = useRef(); // Update callback ref when it changes useEffect(() => { callbackRef.current = callback; }); // Create SpacedUpdate instance only once if (!spacedUpdateRef.current) { spacedUpdateRef.current = new SpacedUpdate( () => callbackRef.current(), interval ); } // Update interval if it changes useEffect(() => { spacedUpdateRef.current?.setUpdateInterval(interval); }, [interval]); return spacedUpdateRef.current; } export function useTriliumOption(name: OptionNames, needsRefresh?: boolean): [string, (newValue: string) => Promise] { const initialValue = options.get(name); const [ value, setValue ] = useState(initialValue); const wrappedSetValue = useMemo(() => { return async (newValue: string) => { await options.save(name, newValue); if (needsRefresh) { reloadFrontendApp(`option change: ${name}`); } } }, [ name, needsRefresh ]); useTriliumEvent("entitiesReloaded", useCallback(({ loadResults }) => { if (loadResults.getOptionNames().includes(name)) { const newValue = options.get(name); setValue(newValue); } }, [ name ])); return [ value, wrappedSetValue ] } export function useTriliumOptionBool(name: OptionNames): [boolean, (newValue: boolean) => Promise] { const [ value, setValue ] = useTriliumOption(name); return [ (value === "true"), (newValue) => setValue(newValue ? "true" : "false") ] } /** * Generates a unique name via a random alphanumeric string of a fixed length. * *

* Generally used to assign names to inputs that are unique, especially useful for widgets inside tabs. * * @param prefix a prefix to add to the unique name. * @returns a name with the given prefix and a random alpanumeric string appended to it. */ export function useUniqueName(prefix: string) { return useMemo(() => prefix + utils.randomString(10), [ prefix]); }