The tray's right-click menu is drawn by the app: one tree from tray.rs (stable submenu keys), one frameless always-on-top menu-N window per panel rendered by tray-menu.html with the design system, placed inside the monitor's work area. A pure panel book keeps transient and pinned panels apart; pinned panels (pin button, Ctrl+click, middle click) are draggable, step aside from each other and are saved in state.json (menuPins) and restored at start. tray_update pushes the tree to the open panels, which update in place; closed panels are destroyed. The native menu, rendered from the same tree, stays as the fallback and on Linux. Only menu-* windows get the menu_* commands (menu.json). In-app context menus get the same behaviour through a pure menu stack (ui/menu-stack.ts): pin a menu, tear off a submenu, pinned panels survive the next menu, are draggable and re-read their entries from live sources so data changes update them in place. Also drops a stray diff3 marker line from CHANGELOG.md.
33 lines
1.8 KiB
TypeScript
33 lines
1.8 KiB
TypeScript
// The tray menu panels' host calls (the panel page's counterpart of src/api.ts, kept apart so the panel bundle stays
|
|
// small). Only `menu-*` windows may call these (capabilities/menu.json; every command re-checks the window in Rust).
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
import { listen } from '@tauri-apps/api/event';
|
|
import type { MenuNode, PanelInfo } from './model';
|
|
import type { MenuBridge } from './TrayMenu';
|
|
|
|
export const inTauri = (): boolean => typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window;
|
|
|
|
const call = (cmd: string, args?: Record<string, unknown>) => { invoke(cmd, args).catch(() => {}); };
|
|
|
|
function sub<T>(ev: string, fn: (p: T) => void): () => void {
|
|
const un = listen<T>(ev, e => fn(e.payload));
|
|
return () => { un.then(f => f()).catch(() => {}); };
|
|
}
|
|
|
|
/** This panel: its path into the tree, pinned or not, and the tree now. */
|
|
export const menuState = (): Promise<PanelInfo | null> => invoke<PanelInfo | null>('menu_state').catch(() => null);
|
|
/** Every change of the tree (a store read, mute, language): the panel updates in place. */
|
|
export const onModel = (fn: (nodes: MenuNode[]) => void) => sub<MenuNode[]>('menu-model', fn);
|
|
/** Rust about this panel: `pinned`, `unpinned`, `focus`. */
|
|
export const onPanel = (fn: (word: string) => void) => sub<string>('menu-panel', fn);
|
|
|
|
export const bridge: MenuBridge = {
|
|
ready: (width, height) => call('menu_ready', { width, height }),
|
|
openSub: (key, top, focus) => call('menu_open_sub', { key, top, focus }),
|
|
pinSub: (key, top) => call('menu_pin_sub', { key, top }),
|
|
closeSubs: () => call('menu_close_subs'),
|
|
close: refocusParent => call('menu_close', { refocusParent }),
|
|
dismiss: () => call('menu_dismiss'),
|
|
pin: pinned => call('menu_pin', { pinned }),
|
|
select: id => call('menu_select', { id }),
|
|
};
|