App menu: the titlebar's "Settings and more" (Alt+F, F10, the palette, the account menu's last row) with rows from the site config; page zoom per tab in the host
Frame: AppMenu.tsx is a menu at the end of the titlebar cluster (before the caption buttons; macOS has none there). Its rows are `ShellSite.menu`: ordered groups of built-in rows by name and the app's own ({id, label, icon, shortcut, hint, when, checked, run | submenu}); `DEFAULT_MENU`, `menuWith` and `menuWithout` order, add and remove. A built-in row shows only when the app serves it (decks, `menu.languages`, themes, `menu.modes`, and `features`: zoom, store, workspaces, developer, updates), so no app gets a dead row. Language, Theme, Mode, Workspaces and Developer are submenus; Language and Theme apply live and keep the menu open, Mode asks the same keep-my-changes question as Settings (panel `menuModes()`). Keyboard: arrows, Home/End, type-ahead, the arrow along the reading direction opens a submenu, Esc closes one level; ARIA menu roles; submenus stay inside the window; one highlight for pointer and keys. It is an overlay like the other shell menus (the shell is raised, panes stay visible, nothing is dimmed).
Words: menuMessages.en.json (`wb.menu.*`), read through the framework i18n with that English as the fallback until the catalogs have them; words the panel already has are reused.
Host: `tab_zoom {id, factor}` (shell-only) sets a tab's page zoom with the engine's own zoom, clamped to 25%..500% and remembered per tab id in app data `zoom.json` (a number per tab id, nothing else; a closed tab's entry goes); without a factor it reads. A tab may forward four more shortcut names: `app-menu`, `zoom-in`, `zoom-out`, `zoom-reset`. The titlebar shows a zoom chip only while the pane in front is not at 100%.
create-waybetter: `tab_zoom` in the shell capability list; the template says which panel sections it lacks (store, updates) and how to add menu rows.
This commit is contained in:
parent
6e80b3e2c9
commit
613723ee93
27 changed files with 1587 additions and 18 deletions
|
|
@ -5,10 +5,11 @@
|
|||
// the owner's own changes stay. A mode is a starting point: choosing one writes flag values
|
||||
// (`features_set`) and remembers the choice (`mode_get/set`); it never takes a switch away.
|
||||
import { useEffect, useState, type ComponentType } from 'react';
|
||||
import { modeById, sparseFlags as sparse, type FeatureTable, type Flags } from '../../../runtime/src/featureTable.ts';
|
||||
import { modeById, normalizeFlags, sparseFlags as sparse, type FeatureTable, type Flags } from '../../../runtime/src/featureTable.ts';
|
||||
import { invoke } from '../frame/bridge.ts';
|
||||
import { useT } from '../frame/hooks.ts';
|
||||
import { tx } from '../frame/i18n.ts';
|
||||
import type { MenuModes } from '../frame/menuModel.ts';
|
||||
import { shell } from '../frame/state.ts';
|
||||
import { Badge, Button, Row, Section } from './controls.tsx';
|
||||
import { modePlan, type PluginFileState } from './featureModel.ts';
|
||||
|
|
@ -169,3 +170,28 @@ export function openOnboardingWhenDue(): void {
|
|||
}, 250);
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* The app menu's Mode submenu (the frame's `ShellSite.menu.modes`): the same modes, the same
|
||||
* question (keep my changes or take the mode as it is) and the same steps as Settings.
|
||||
*/
|
||||
export function menuModes(): MenuModes {
|
||||
const state = async () => (cached ??= await invoke<ModeState>('mode_get'));
|
||||
const flagsNow = async (table: FeatureTable) => normalizeFlags(table, await invoke<unknown>('features_get'));
|
||||
const plugins = () => invoke<PluginFileState[]>('plugins_list').catch(() => [] as PluginFileState[]);
|
||||
return {
|
||||
list: () => (featureTable()?.modes ?? []).map(mode => ({ id: mode.id, label: tx(mode.name, undefined, mode.id) })),
|
||||
current: async () => (featureTable()?.modes.length ? (await state()).mode : null),
|
||||
async changes(to) {
|
||||
const table = featureTable();
|
||||
return table ? modePlan(table, await flagsNow(table), await plugins(), (await state()).mode, to, false).changes : 0;
|
||||
},
|
||||
async apply(to, keep) {
|
||||
const table = featureTable();
|
||||
if (!table) return;
|
||||
const save = async (next: Flags) => { await invoke('features_set', { features: sparse(table, next) }); return true; };
|
||||
const next = await applyMode(table, await flagsNow(table), (await state()).mode, to, keep, save);
|
||||
if (next) { cached = next; for (const listener of listeners) listener(next); }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue