import "./global_menu.css"; import { KeyboardActionNames } from "@triliumnext/commons"; import { ComponentChildren, RefObject } from "preact"; import { useContext, useEffect, useRef, useState } from "preact/hooks"; import { CommandNames } from "../../components/app_context"; import Component from "../../components/component"; import { ExperimentalFeature, ExperimentalFeatureId, experimentalFeatures, isExperimentalFeatureEnabled, toggleExperimentalFeature } from "../../services/experimental_features"; import { t } from "../../services/i18n"; import utils, { dynamicRequire, isElectron, isMobile, reloadFrontendApp } from "../../services/utils"; import Dropdown from "../react/Dropdown"; import { FormDropdownDivider, FormDropdownSubmenu, FormListHeader, FormListItem } from "../react/FormList"; import { useStaticTooltip, useStaticTooltipWithKeyboardShortcut, useTriliumOption, useTriliumOptionBool, useTriliumOptionInt } from "../react/hooks"; import KeyboardShortcut from "../react/KeyboardShortcut"; import { ParentComponent } from "../react/react_utils"; interface MenuItemProps { icon: string, text: ComponentChildren, title?: string, command: T, disabled?: boolean active?: boolean; outsideChildren?: ComponentChildren; } export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout: boolean }) { const isVerticalLayout = !isHorizontalLayout; const parentComponent = useContext(ParentComponent); const { isUpdateAvailable, latestVersion } = useTriliumUpdateStatus(); const logoRef = useRef(null); useStaticTooltip(logoRef); return ( {isVerticalLayout && } {isUpdateAvailable && } } noDropdownListStyle mobileBackdrop > {isUpdateAvailable && <> window.open("https://github.com/TriliumNext/Trilium/releases/latest")} icon="bx bx-download" text={t("global_menu.download-update", {latestVersion})} /> } {!isElectron() && } {glob.isDev && } ); } function AdvancedMenu({ dropStart }: { dropStart: boolean }) { return ( {isElectron() && } ); } function BrowserOnlyOptions() { return <> ; } function DevelopmentOptions({ dropStart }: { dropStart: boolean }) { return <> {experimentalFeatures.map((feature) => ( ))} ; } function ExperimentalFeatureToggle({ experimentalFeature }: { experimentalFeature: ExperimentalFeature }) { const featureEnabled = isExperimentalFeatureEnabled(experimentalFeature.id as ExperimentalFeatureId); return ( { await toggleExperimentalFeature(experimentalFeature.id as ExperimentalFeatureId, !featureEnabled); reloadFrontendApp(); }} >{experimentalFeature.name} ); } function SwitchToOptions() { if (isElectron()) { return; } else if (!isMobile()) { return ; } return ; } function MenuItem({ icon, text, title, command, disabled, active }: MenuItemProps void)>) { return {text}; } function KeyboardActionMenuItem({ text, command, ...props }: MenuItemProps) { return {text} } />; } export function VerticalLayoutIcon({ logoRef }: { logoRef?: RefObject }) { return ( ); } function ZoomControls({ parentComponent }: { parentComponent?: Component | null }) { const [ zoomLevel ] = useTriliumOption("zoomFactor"); function ZoomControlButton({ command, title, icon, children, dismiss }: { command: KeyboardActionNames, title: string, icon?: string, children?: ComponentChildren, dismiss?: boolean }) { const linkRef = useRef(null); useStaticTooltipWithKeyboardShortcut(linkRef, title, command, { placement: "bottom", fallbackPlacements: [ "bottom" ] }); return ( { parentComponent?.triggerCommand(command); if (!dismiss) { e.stopPropagation(); } }} className={`dropdown-item-button ${icon}`} >{children} ); } return isElectron() ? ( e.stopPropagation()} > {t("global_menu.zoom")} <>
  {(parseFloat(zoomLevel) * 100).toFixed(0)}{t("units.percentage")}
) : ( ); } function ToggleWindowOnTop() { const focusedWindow = isElectron() ? dynamicRequire("@electron/remote").BrowserWindow.getFocusedWindow() : null; const [ isAlwaysOnTop, setIsAlwaysOnTop ] = useState(focusedWindow?.isAlwaysOnTop()); return (isElectron() && { const newState = !isAlwaysOnTop; focusedWindow?.setAlwaysOnTop(newState); setIsAlwaysOnTop(newState); }} /> ); } function useTriliumUpdateStatus() { const [ latestVersion, setLatestVersion ] = useState(); const [ checkForUpdates ] = useTriliumOptionBool("checkForUpdates"); const isUpdateAvailable = utils.isUpdateAvailable(latestVersion, glob.triliumVersion); async function updateVersionStatus() { const RELEASES_API_URL = "https://api.github.com/repos/TriliumNext/Trilium/releases/latest"; let latestVersion: string | undefined; try { const resp = await fetch(RELEASES_API_URL); const data = await resp.json(); latestVersion = data?.tag_name?.substring(1); } catch (e) { console.warn("Unable to fetch latest version info from GitHub releases API", e); } setLatestVersion(latestVersion); } useEffect(() => { if (!checkForUpdates) { setLatestVersion(undefined); return; } updateVersionStatus(); const interval = setInterval(() => updateVersionStatus(), 8 * 60 * 60 * 1000); return () => clearInterval(interval); }, [ checkForUpdates ]); return { isUpdateAvailable, latestVersion }; }