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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
eed6bdd8f4
commit
e5d43afffc
27 changed files with 1587 additions and 18 deletions
|
|
@ -89,6 +89,62 @@ pub fn tab_action<R: Runtime>(app: &AppHandle<R>, id: &str, action: &str) -> Res
|
|||
result.map_err(|error| format!("TAB: {error}"))
|
||||
}
|
||||
|
||||
/// The remembered page zoom per tab ([`crate::zoom`], app data `zoom.json`), read once.
|
||||
fn zooms<R: Runtime>(app: &AppHandle<R>) -> std::sync::MutexGuard<'static, crate::zoom::ZoomMap> {
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
static ZOOMS: OnceLock<Mutex<crate::zoom::ZoomMap>> = OnceLock::new();
|
||||
ZOOMS
|
||||
.get_or_init(|| Mutex::new(read_json::<crate::zoom::ZoomMap>(app, crate::zoom::ZOOM_FILE).unwrap_or_default().validated()))
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
}
|
||||
|
||||
/// `tab_zoom`: sets tab `id`'s page zoom to `factor` (clamped to 25%..500%, the engine's own page
|
||||
/// zoom) and remembers it for that tab; without a factor only reads it. Returns the factor in effect.
|
||||
pub fn tab_zoom<R: Runtime>(app: &AppHandle<R>, id: &str, factor: Option<f64>) -> Result<f64, String> {
|
||||
if !policy::valid_tab_id(id) {
|
||||
return Err("VALIDATION: invalid tab id".into());
|
||||
}
|
||||
let Some(factor) = factor else { return Ok(zooms(app).get(id)) };
|
||||
let tab = tab_webview(app, id).ok_or("TAB: not found")?;
|
||||
let (before, factor, saved) = {
|
||||
let mut map = zooms(app);
|
||||
let before = map.get(id);
|
||||
let factor = map.set(id, factor)?;
|
||||
(before, factor, map.clone())
|
||||
};
|
||||
if let Err(error) = tab.set_zoom(factor) {
|
||||
// Not applied: what is remembered stays what the tab shows.
|
||||
let _ = zooms(app).set(id, before);
|
||||
return Err(format!("TAB: {error}"));
|
||||
}
|
||||
if factor != before {
|
||||
write_json(app, crate::zoom::ZOOM_FILE, &saved)?;
|
||||
}
|
||||
Ok(factor)
|
||||
}
|
||||
|
||||
/// A tab webview that was just created, or loads a page: it shows its remembered zoom (a new
|
||||
/// webview starts at 100%). Tabs at 100% are left alone.
|
||||
pub fn restore_tab_zoom<R: Runtime>(app: &AppHandle<R>, tab: &Webview<R>) {
|
||||
let Some(id) = policy::tab_id(tab.label()) else { return };
|
||||
let factor = zooms(app).get(id);
|
||||
if factor != crate::zoom::DEFAULT {
|
||||
let _ = tab.set_zoom(factor);
|
||||
}
|
||||
}
|
||||
|
||||
/// A closed tab's zoom is forgotten (a tab that comes back under the same id starts at 100%).
|
||||
pub fn forget_tab_zoom<R: Runtime>(app: &AppHandle<R>, id: &str) {
|
||||
let saved = {
|
||||
let mut map = zooms(app);
|
||||
map.forget(id).then(|| map.clone())
|
||||
};
|
||||
if let Some(map) = saved {
|
||||
let _ = write_json(app, crate::zoom::ZOOM_FILE, &map);
|
||||
}
|
||||
}
|
||||
|
||||
/// A browser shortcut pressed inside tab `id`, forwarded to the shell (`tab-shortcut`).
|
||||
pub fn forward_shortcut<R: Runtime>(app: &AppHandle<R>, id: String, shortcut: String) -> Result<(), String> {
|
||||
if !tabs::valid_shortcut(&shortcut) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue