waybetter 0.1.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -6
- package/bin/waybetter.mjs +43 -0
- package/build/runtime.mjs +105 -0
- package/cli/buildenv.mjs +130 -0
- package/cli/dev.mjs +97 -0
- package/cli/i18n.mjs +382 -0
- package/cli/manifests.mjs +383 -0
- package/cli/release.mjs +437 -0
- package/cli/releasecheck.mjs +15 -0
- package/cli/remote.mjs +260 -0
- package/cli/store.mjs +378 -0
- package/cli/strings.mjs +123 -0
- package/dist/runtime/column.d.ts +93 -0
- package/dist/runtime/column.js +451 -0
- package/dist/runtime/column.js.map +1 -0
- package/dist/runtime/commandReport.d.ts +19 -0
- package/dist/runtime/commandReport.js +47 -0
- package/dist/runtime/commandReport.js.map +1 -0
- package/dist/runtime/commands.d.ts +72 -0
- package/dist/runtime/commands.js +214 -0
- package/dist/runtime/commands.js.map +1 -0
- package/dist/runtime/devErrors.d.ts +16 -0
- package/dist/runtime/devErrors.js +30 -0
- package/dist/runtime/devErrors.js.map +1 -0
- package/dist/runtime/domHub.d.ts +21 -0
- package/dist/runtime/domHub.js +139 -4
- package/dist/runtime/domHub.js.map +1 -1
- package/dist/runtime/featureModules.d.ts +157 -0
- package/dist/runtime/featureModules.js +484 -0
- package/dist/runtime/featureModules.js.map +1 -0
- package/dist/runtime/featureTable.d.ts +144 -0
- package/dist/runtime/featureTable.js +223 -0
- package/dist/runtime/featureTable.js.map +1 -0
- package/dist/runtime/fonts.d.ts +22 -0
- package/dist/runtime/fonts.js +72 -0
- package/dist/runtime/fonts.js.map +1 -0
- package/dist/runtime/i18n.d.ts +138 -0
- package/dist/runtime/i18n.js +614 -0
- package/dist/runtime/i18n.js.map +1 -0
- package/dist/runtime/i18nCheck.d.ts +30 -0
- package/dist/runtime/i18nCheck.js +224 -0
- package/dist/runtime/i18nCheck.js.map +1 -0
- package/dist/runtime/pageLang.d.ts +8 -0
- package/dist/runtime/pageLang.js +64 -0
- package/dist/runtime/pageLang.js.map +1 -0
- package/dist/runtime/pageStrings.d.ts +35 -0
- package/dist/runtime/pageStrings.js +76 -0
- package/dist/runtime/pageStrings.js.map +1 -0
- package/dist/runtime/panelButton.d.ts +33 -0
- package/dist/runtime/panelButton.js +83 -0
- package/dist/runtime/panelButton.js.map +1 -0
- package/dist/runtime/permissions.d.ts +47 -0
- package/dist/runtime/permissions.js +141 -0
- package/dist/runtime/permissions.js.map +1 -0
- package/dist/runtime/pluginApi.d.ts +51 -2
- package/dist/runtime/pluginApi.js +137 -22
- package/dist/runtime/pluginApi.js.map +1 -1
- package/dist/runtime/pluginI18n.d.ts +29 -0
- package/dist/runtime/pluginI18n.js +119 -0
- package/dist/runtime/pluginI18n.js.map +1 -0
- package/dist/runtime/settingsForm.d.ts +66 -0
- package/dist/runtime/settingsForm.js +114 -0
- package/dist/runtime/settingsForm.js.map +1 -0
- package/dist/runtime/site.d.ts +5 -0
- package/dist/runtime/site.js +1 -1
- package/dist/runtime/site.js.map +1 -1
- package/dist/runtime/siteRuntime.d.ts +144 -0
- package/dist/runtime/siteRuntime.js +175 -0
- package/dist/runtime/siteRuntime.js.map +1 -0
- package/dist/runtime/stringPackHook.d.ts +84 -0
- package/dist/runtime/stringPackHook.js +220 -0
- package/dist/runtime/stringPackHook.js.map +1 -0
- package/dist/runtime/stringPackTranslate.d.ts +61 -0
- package/dist/runtime/stringPackTranslate.js +267 -0
- package/dist/runtime/stringPackTranslate.js.map +1 -0
- package/dist/runtime/stringPacks.d.ts +248 -0
- package/dist/runtime/stringPacks.js +610 -0
- package/dist/runtime/stringPacks.js.map +1 -0
- package/dist/runtime/styles.d.ts +15 -0
- package/dist/runtime/styles.js +81 -0
- package/dist/runtime/styles.js.map +1 -0
- package/dist/runtime/textNet.d.ts +15 -0
- package/dist/runtime/textNet.js +131 -0
- package/dist/runtime/textNet.js.map +1 -0
- package/dist/runtime/themeApi.d.ts +121 -0
- package/dist/runtime/themeApi.js +220 -0
- package/dist/runtime/themeApi.js.map +1 -0
- package/dist/runtime/themeBar.d.ts +28 -0
- package/dist/runtime/themeBar.js +82 -0
- package/dist/runtime/themeBar.js.map +1 -0
- package/dist/runtime/themeChoice.d.ts +52 -0
- package/dist/runtime/themeChoice.js +69 -0
- package/dist/runtime/themeChoice.js.map +1 -0
- package/dist/runtime/themeComponents.d.ts +435 -0
- package/dist/runtime/themeComponents.js +476 -0
- package/dist/runtime/themeComponents.js.map +1 -0
- package/dist/runtime/themeDecor.d.ts +73 -0
- package/dist/runtime/themeDecor.js +133 -0
- package/dist/runtime/themeDecor.js.map +1 -0
- package/dist/runtime/themeDerive.d.ts +69 -0
- package/dist/runtime/themeDerive.js +117 -0
- package/dist/runtime/themeDerive.js.map +1 -0
- package/dist/runtime/themeDoctor.d.ts +52 -0
- package/dist/runtime/themeDoctor.js +112 -0
- package/dist/runtime/themeDoctor.js.map +1 -0
- package/dist/runtime/themeGuards.d.ts +60 -0
- package/dist/runtime/themeGuards.js +235 -0
- package/dist/runtime/themeGuards.js.map +1 -0
- package/dist/runtime/themeIcons.d.ts +130 -0
- package/dist/runtime/themeIcons.js +271 -0
- package/dist/runtime/themeIcons.js.map +1 -0
- package/dist/runtime/themeInk.d.ts +37 -0
- package/dist/runtime/themeInk.js +111 -0
- package/dist/runtime/themeInk.js.map +1 -0
- package/dist/runtime/themeInvariants.d.ts +39 -0
- package/dist/runtime/themeInvariants.js +273 -0
- package/dist/runtime/themeInvariants.js.map +1 -0
- package/dist/runtime/themeRuntime.d.ts +114 -0
- package/dist/runtime/themeRuntime.js +570 -0
- package/dist/runtime/themeRuntime.js.map +1 -0
- package/dist/runtime/themeSchema.d.ts +10 -0
- package/dist/runtime/themeSchema.js +150 -0
- package/dist/runtime/themeSchema.js.map +1 -0
- package/dist/runtime/themeSpec.d.ts +263 -0
- package/dist/runtime/themeSpec.js +435 -0
- package/dist/runtime/themeSpec.js.map +1 -0
- package/dist/runtime/themeTokens.d.ts +12 -0
- package/dist/runtime/themeTokens.js +14 -6
- package/dist/runtime/themeTokens.js.map +1 -1
- package/dist/runtime/themeVariants.d.ts +30 -0
- package/dist/runtime/themeVariants.js +53 -0
- package/dist/runtime/themeVariants.js.map +1 -0
- package/dist/runtime/ui.d.ts +192 -0
- package/dist/runtime/ui.js +926 -0
- package/dist/runtime/ui.js.map +1 -0
- package/dist/shell/InstallDialog.d.ts +40 -0
- package/dist/shell/InstallDialog.js +176 -0
- package/dist/shell/InstallDialog.js.map +1 -0
- package/dist/shell/StoreView.d.ts +78 -0
- package/dist/shell/StoreView.js +108 -0
- package/dist/shell/StoreView.js.map +1 -0
- package/dist/shell/addressModel.js.map +1 -1
- package/dist/shell/chrome.js.map +1 -1
- package/dist/shell/deckModel.d.ts +117 -0
- package/dist/shell/deckModel.js +267 -0
- package/dist/shell/deckModel.js.map +1 -0
- package/dist/shell/frame/AccountSwitcher.d.ts +42 -0
- package/dist/shell/frame/AccountSwitcher.js +243 -0
- package/dist/shell/frame/AccountSwitcher.js.map +1 -0
- package/dist/shell/frame/AddColumnDialog.d.ts +8 -0
- package/dist/shell/frame/AddColumnDialog.js +81 -0
- package/dist/shell/frame/AddColumnDialog.js.map +1 -0
- package/dist/shell/frame/AppMenu.d.ts +44 -0
- package/dist/shell/frame/AppMenu.js +721 -0
- package/dist/shell/frame/AppMenu.js.map +1 -0
- package/dist/shell/frame/CommandPalette.d.ts +7 -0
- package/dist/shell/frame/CommandPalette.js +118 -0
- package/dist/shell/frame/CommandPalette.js.map +1 -0
- package/dist/shell/frame/DeckView.d.ts +13 -0
- package/dist/shell/frame/DeckView.js +287 -0
- package/dist/shell/frame/DeckView.js.map +1 -0
- package/dist/shell/frame/FindBar.d.ts +10 -0
- package/dist/shell/frame/FindBar.js +108 -0
- package/dist/shell/frame/FindBar.js.map +1 -0
- package/dist/shell/frame/MediaPanel.d.ts +8 -0
- package/dist/shell/frame/MediaPanel.js +76 -0
- package/dist/shell/frame/MediaPanel.js.map +1 -0
- package/dist/shell/frame/NewTabPage.d.ts +9 -0
- package/dist/shell/frame/NewTabPage.js +71 -0
- package/dist/shell/frame/NewTabPage.js.map +1 -0
- package/dist/shell/frame/PaneTheme.d.ts +16 -0
- package/dist/shell/frame/PaneTheme.js +67 -0
- package/dist/shell/frame/PaneTheme.js.map +1 -0
- package/dist/shell/frame/ReportDialog.d.ts +3 -0
- package/dist/shell/frame/ReportDialog.js +95 -0
- package/dist/shell/frame/ReportDialog.js.map +1 -0
- package/dist/shell/frame/Shell.d.ts +9 -0
- package/dist/shell/frame/Shell.js +1139 -0
- package/dist/shell/frame/Shell.js.map +1 -0
- package/dist/shell/frame/Titlebar.d.ts +17 -0
- package/dist/shell/frame/Titlebar.js +79 -0
- package/dist/shell/frame/Titlebar.js.map +1 -0
- package/dist/shell/frame/TitlebarExtras.d.ts +4 -0
- package/dist/shell/frame/TitlebarExtras.js +202 -0
- package/dist/shell/frame/TitlebarExtras.js.map +1 -0
- package/dist/shell/frame/WebPanel.d.ts +4 -0
- package/dist/shell/frame/WebPanel.js +185 -0
- package/dist/shell/frame/WebPanel.js.map +1 -0
- package/dist/shell/frame/accountModel.d.ts +49 -0
- package/dist/shell/frame/accountModel.js +67 -0
- package/dist/shell/frame/accountModel.js.map +1 -0
- package/dist/shell/frame/accountSwitcher.css +91 -0
- package/dist/shell/frame/appI18n.d.ts +44 -0
- package/dist/shell/frame/appI18n.js +64 -0
- package/dist/shell/frame/appI18n.js.map +1 -0
- package/dist/shell/frame/appMenu.css +127 -0
- package/dist/shell/frame/backupModel.d.ts +38 -0
- package/dist/shell/frame/backupModel.js +192 -0
- package/dist/shell/frame/backupModel.js.map +1 -0
- package/dist/shell/frame/boot.d.ts +8 -0
- package/dist/shell/frame/boot.js +37 -0
- package/dist/shell/frame/boot.js.map +1 -0
- package/dist/shell/frame/brand.d.ts +1 -0
- package/dist/shell/frame/brand.js +4 -0
- package/dist/shell/frame/brand.js.map +1 -0
- package/dist/shell/frame/bridge.d.ts +35 -0
- package/dist/shell/frame/bridge.js +70 -0
- package/dist/shell/frame/bridge.js.map +1 -0
- package/dist/shell/frame/chromeTheme.d.ts +47 -0
- package/dist/shell/frame/chromeTheme.js +118 -0
- package/dist/shell/frame/chromeTheme.js.map +1 -0
- package/dist/shell/frame/deck.css +209 -0
- package/dist/shell/frame/deckStore.d.ts +89 -0
- package/dist/shell/frame/deckStore.js +239 -0
- package/dist/shell/frame/deckStore.js.map +1 -0
- package/dist/shell/frame/devHost.d.ts +35 -0
- package/dist/shell/frame/devHost.js +80 -0
- package/dist/shell/frame/devHost.js.map +1 -0
- package/dist/shell/frame/devModel.d.ts +7 -0
- package/dist/shell/frame/devModel.js +17 -0
- package/dist/shell/frame/devModel.js.map +1 -0
- package/dist/shell/frame/diag.d.ts +7 -0
- package/dist/shell/frame/diag.js +39 -0
- package/dist/shell/frame/diag.js.map +1 -0
- package/dist/shell/frame/edges.css +159 -0
- package/dist/shell/frame/findBar.css +14 -0
- package/dist/shell/frame/frameMessages.d.ts +248 -0
- package/dist/shell/frame/frameMessages.js +252 -0
- package/dist/shell/frame/frameMessages.js.map +1 -0
- package/dist/shell/frame/hooks.d.ts +39 -0
- package/dist/shell/frame/hooks.js +97 -0
- package/dist/shell/frame/hooks.js.map +1 -0
- package/dist/shell/frame/i18n.d.ts +27 -0
- package/dist/shell/frame/i18n.js +66 -0
- package/dist/shell/frame/i18n.js.map +1 -0
- package/dist/shell/frame/iconPack.d.ts +11 -0
- package/dist/shell/frame/iconPack.js +45 -0
- package/dist/shell/frame/iconPack.js.map +1 -0
- package/dist/shell/frame/icons.d.ts +71 -0
- package/dist/shell/frame/icons.js +75 -0
- package/dist/shell/frame/icons.js.map +1 -0
- package/dist/shell/frame/index.d.ts +34 -0
- package/dist/shell/frame/index.js +43 -0
- package/dist/shell/frame/index.js.map +1 -0
- package/dist/shell/frame/language.d.ts +58 -0
- package/dist/shell/frame/language.js +165 -0
- package/dist/shell/frame/language.js.map +1 -0
- package/dist/shell/frame/mediaModel.d.ts +47 -0
- package/dist/shell/frame/mediaModel.js +67 -0
- package/dist/shell/frame/mediaModel.js.map +1 -0
- package/dist/shell/frame/menuMessages.en.json +96 -0
- package/dist/shell/frame/menuModel.d.ts +319 -0
- package/dist/shell/frame/menuModel.js +301 -0
- package/dist/shell/frame/menuModel.js.map +1 -0
- package/dist/shell/frame/nativeMenu.d.ts +5 -0
- package/dist/shell/frame/nativeMenu.js +178 -0
- package/dist/shell/frame/nativeMenu.js.map +1 -0
- package/dist/shell/frame/nativeMenuModel.d.ts +102 -0
- package/dist/shell/frame/nativeMenuModel.js +186 -0
- package/dist/shell/frame/nativeMenuModel.js.map +1 -0
- package/dist/shell/frame/paletteModel.d.ts +139 -0
- package/dist/shell/frame/paletteModel.js +114 -0
- package/dist/shell/frame/paletteModel.js.map +1 -0
- package/dist/shell/frame/paneTheme.css +9 -0
- package/dist/shell/frame/pluginCommandStore.d.ts +23 -0
- package/dist/shell/frame/pluginCommandStore.js +70 -0
- package/dist/shell/frame/pluginCommandStore.js.map +1 -0
- package/dist/shell/frame/reportModel.d.ts +52 -0
- package/dist/shell/frame/reportModel.js +105 -0
- package/dist/shell/frame/reportModel.js.map +1 -0
- package/dist/shell/frame/safeStore.d.ts +7 -0
- package/dist/shell/frame/safeStore.js +40 -0
- package/dist/shell/frame/safeStore.js.map +1 -0
- package/dist/shell/frame/searchInput.d.ts +2 -0
- package/dist/shell/frame/searchInput.js +11 -0
- package/dist/shell/frame/searchInput.js.map +1 -0
- package/dist/shell/frame/shell.css +634 -0
- package/dist/shell/frame/site.d.ts +206 -0
- package/dist/shell/frame/site.js +22 -0
- package/dist/shell/frame/site.js.map +1 -0
- package/dist/shell/frame/state.d.ts +101 -0
- package/dist/shell/frame/state.js +286 -0
- package/dist/shell/frame/state.js.map +1 -0
- package/dist/shell/frame/syncGate.d.ts +10 -0
- package/dist/shell/frame/syncGate.js +36 -0
- package/dist/shell/frame/syncGate.js.map +1 -0
- package/dist/shell/frame/syncHost.d.ts +35 -0
- package/dist/shell/frame/syncHost.js +85 -0
- package/dist/shell/frame/syncHost.js.map +1 -0
- package/dist/shell/frame/tabStill.d.ts +2 -0
- package/dist/shell/frame/tabStill.js +62 -0
- package/dist/shell/frame/tabStill.js.map +1 -0
- package/dist/shell/frame/themeScopes.d.ts +29 -0
- package/dist/shell/frame/themeScopes.js +61 -0
- package/dist/shell/frame/themeScopes.js.map +1 -0
- package/dist/shell/frame/themeStore.d.ts +99 -0
- package/dist/shell/frame/themeStore.js +267 -0
- package/dist/shell/frame/themeStore.js.map +1 -0
- package/dist/shell/frame/windowBinding.d.ts +2 -0
- package/dist/shell/frame/windowBinding.js +7 -0
- package/dist/shell/frame/windowBinding.js.map +1 -0
- package/dist/shell/frame/workspaceModel.d.ts +82 -0
- package/dist/shell/frame/workspaceModel.js +250 -0
- package/dist/shell/frame/workspaceModel.js.map +1 -0
- package/dist/shell/frame/workspaces.css +84 -0
- package/dist/shell/frame/workspaces.d.ts +22 -0
- package/dist/shell/frame/workspaces.js +125 -0
- package/dist/shell/frame/workspaces.js.map +1 -0
- package/dist/shell/frame/zoomStore.d.ts +10 -0
- package/dist/shell/frame/zoomStore.js +65 -0
- package/dist/shell/frame/zoomStore.js.map +1 -0
- package/dist/shell/installController.d.ts +137 -0
- package/dist/shell/installController.js +172 -0
- package/dist/shell/installController.js.map +1 -0
- package/dist/shell/installerMessages.d.ts +30 -0
- package/dist/shell/installerMessages.js +36 -0
- package/dist/shell/installerMessages.js.map +1 -0
- package/dist/shell/linkDrop.js.map +1 -1
- package/dist/shell/messages.d.ts +16 -0
- package/dist/shell/messages.js +27 -0
- package/dist/shell/messages.js.map +1 -0
- package/dist/shell/palette.js.map +1 -1
- package/dist/shell/paneModel.d.ts +22 -0
- package/dist/shell/paneModel.js +24 -1
- package/dist/shell/paneModel.js.map +1 -1
- package/dist/shell/panel/Appearance.d.ts +8 -0
- package/dist/shell/panel/Appearance.js +265 -0
- package/dist/shell/panel/Appearance.js.map +1 -0
- package/dist/shell/panel/BackupPanel.d.ts +5 -0
- package/dist/shell/panel/BackupPanel.js +198 -0
- package/dist/shell/panel/BackupPanel.js.map +1 -0
- package/dist/shell/panel/DeveloperPanel.d.ts +4 -0
- package/dist/shell/panel/DeveloperPanel.js +51 -0
- package/dist/shell/panel/DeveloperPanel.js.map +1 -0
- package/dist/shell/panel/FeatureToggles.d.ts +13 -0
- package/dist/shell/panel/FeatureToggles.js +42 -0
- package/dist/shell/panel/FeatureToggles.js.map +1 -0
- package/dist/shell/panel/Home.d.ts +3 -0
- package/dist/shell/panel/Home.js +23 -0
- package/dist/shell/panel/Home.js.map +1 -0
- package/dist/shell/panel/IconPackInstall.d.ts +15 -0
- package/dist/shell/panel/IconPackInstall.js +41 -0
- package/dist/shell/panel/IconPackInstall.js.map +1 -0
- package/dist/shell/panel/LanguageSettings.d.ts +1 -0
- package/dist/shell/panel/LanguageSettings.js +54 -0
- package/dist/shell/panel/LanguageSettings.js.map +1 -0
- package/dist/shell/panel/MakePanel.d.ts +3 -0
- package/dist/shell/panel/MakePanel.js +237 -0
- package/dist/shell/panel/MakePanel.js.map +1 -0
- package/dist/shell/panel/MarkdownDoc.d.ts +6 -0
- package/dist/shell/panel/MarkdownDoc.js +35 -0
- package/dist/shell/panel/MarkdownDoc.js.map +1 -0
- package/dist/shell/panel/Modes.d.ts +40 -0
- package/dist/shell/panel/Modes.js +270 -0
- package/dist/shell/panel/Modes.js.map +1 -0
- package/dist/shell/panel/NavIcon.d.ts +4 -0
- package/dist/shell/panel/NavIcon.js +6 -0
- package/dist/shell/panel/NavIcon.js.map +1 -0
- package/dist/shell/panel/Panel.d.ts +7 -0
- package/dist/shell/panel/Panel.js +139 -0
- package/dist/shell/panel/Panel.js.map +1 -0
- package/dist/shell/panel/PluginFeatures.d.ts +24 -0
- package/dist/shell/panel/PluginFeatures.js +165 -0
- package/dist/shell/panel/PluginFeatures.js.map +1 -0
- package/dist/shell/panel/Plugins.d.ts +33 -0
- package/dist/shell/panel/Plugins.js +112 -0
- package/dist/shell/panel/Plugins.js.map +1 -0
- package/dist/shell/panel/PowerSettings.d.ts +4 -0
- package/dist/shell/panel/PowerSettings.js +52 -0
- package/dist/shell/panel/PowerSettings.js.map +1 -0
- package/dist/shell/panel/Settings.d.ts +23 -0
- package/dist/shell/panel/Settings.js +117 -0
- package/dist/shell/panel/Settings.js.map +1 -0
- package/dist/shell/panel/ShareSetup.d.ts +8 -0
- package/dist/shell/panel/ShareSetup.js +130 -0
- package/dist/shell/panel/ShareSetup.js.map +1 -0
- package/dist/shell/panel/StoreSection.d.ts +7 -0
- package/dist/shell/panel/StoreSection.js +78 -0
- package/dist/shell/panel/StoreSection.js.map +1 -0
- package/dist/shell/panel/StringPacks.d.ts +20 -0
- package/dist/shell/panel/StringPacks.js +131 -0
- package/dist/shell/panel/StringPacks.js.map +1 -0
- package/dist/shell/panel/ThemeBuilder.d.ts +5 -0
- package/dist/shell/panel/ThemeBuilder.js +205 -0
- package/dist/shell/panel/ThemeBuilder.js.map +1 -0
- package/dist/shell/panel/backup.css +34 -0
- package/dist/shell/panel/builder.css +240 -0
- package/dist/shell/panel/builderModel.d.ts +55 -0
- package/dist/shell/panel/builderModel.js +95 -0
- package/dist/shell/panel/builderModel.js.map +1 -0
- package/dist/shell/panel/compare.d.ts +10 -0
- package/dist/shell/panel/compare.js +51 -0
- package/dist/shell/panel/compare.js.map +1 -0
- package/dist/shell/panel/controls.d.ts +135 -0
- package/dist/shell/panel/controls.js +213 -0
- package/dist/shell/panel/controls.js.map +1 -0
- package/dist/shell/panel/featureModel.d.ts +69 -0
- package/dist/shell/panel/featureModel.js +79 -0
- package/dist/shell/panel/featureModel.js.map +1 -0
- package/dist/shell/panel/fonts/FontBrowser.d.ts +10 -0
- package/dist/shell/panel/fonts/FontBrowser.js +189 -0
- package/dist/shell/panel/fonts/FontBrowser.js.map +1 -0
- package/dist/shell/panel/fonts/FontRoles.d.ts +20 -0
- package/dist/shell/panel/fonts/FontRoles.js +77 -0
- package/dist/shell/panel/fonts/FontRoles.js.map +1 -0
- package/dist/shell/panel/fonts/catalog.d.ts +37 -0
- package/dist/shell/panel/fonts/catalog.js +68 -0
- package/dist/shell/panel/fonts/catalog.js.map +1 -0
- package/dist/shell/panel/fonts/googleFontsCatalog.json +1 -0
- package/dist/shell/panel/index.d.ts +20 -0
- package/dist/shell/panel/index.js +34 -0
- package/dist/shell/panel/index.js.map +1 -0
- package/dist/shell/panel/languageModel.d.ts +5 -0
- package/dist/shell/panel/languageModel.js +7 -0
- package/dist/shell/panel/languageModel.js.map +1 -0
- package/dist/shell/panel/make.css +73 -0
- package/dist/shell/panel/makeModel.d.ts +32 -0
- package/dist/shell/panel/makeModel.js +84 -0
- package/dist/shell/panel/makeModel.js.map +1 -0
- package/dist/shell/panel/markdown.d.ts +46 -0
- package/dist/shell/panel/markdown.js +100 -0
- package/dist/shell/panel/markdown.js.map +1 -0
- package/dist/shell/panel/nav.d.ts +12 -0
- package/dist/shell/panel/nav.js +50 -0
- package/dist/shell/panel/nav.js.map +1 -0
- package/dist/shell/panel/panel.css +401 -0
- package/dist/shell/panel/panelMessages.d.ts +803 -0
- package/dist/shell/panel/panelMessages.js +779 -0
- package/dist/shell/panel/panelMessages.js.map +1 -0
- package/dist/shell/panel/plugins.css +59 -0
- package/dist/shell/panel/setupModel.d.ts +87 -0
- package/dist/shell/panel/setupModel.js +152 -0
- package/dist/shell/panel/setupModel.js.map +1 -0
- package/dist/shell/panel/site.d.ts +259 -0
- package/dist/shell/panel/site.js +25 -0
- package/dist/shell/panel/site.js.map +1 -0
- package/dist/shell/panel/strings.css +7 -0
- package/dist/shell/panel/themes.d.ts +10 -0
- package/dist/shell/panel/themes.js +8 -0
- package/dist/shell/panel/themes.js.map +1 -0
- package/dist/shell/profileModel.d.ts +3 -2
- package/dist/shell/profileModel.js +7 -5
- package/dist/shell/profileModel.js.map +1 -1
- package/dist/shell/store.css +250 -0
- package/dist/shell/storeMessages.d.ts +193 -0
- package/dist/shell/storeMessages.js +196 -0
- package/dist/shell/storeMessages.js.map +1 -0
- package/dist/shell/storeModel.d.ts +205 -0
- package/dist/shell/storeModel.js +146 -0
- package/dist/shell/storeModel.js.map +1 -0
- package/dist/shell/stringPacksModel.d.ts +44 -0
- package/dist/shell/stringPacksModel.js +99 -0
- package/dist/shell/stringPacksModel.js.map +1 -0
- package/dist/shell/updateModel.d.ts +2 -1
- package/dist/shell/updateModel.js +5 -4
- package/dist/shell/updateModel.js.map +1 -1
- package/dist/shell/windowControls.d.ts +4 -0
- package/dist/shell/windowControls.js +5 -0
- package/dist/shell/windowControls.js.map +1 -1
- package/i18n-config.schema.json +29 -0
- package/i18n-meta.schema.json +26 -0
- package/i18n.schema.json +42 -0
- package/icons.schema.json +655 -0
- package/languages.schema.json +23 -0
- package/locales/ar-x-fm.json +11 -0
- package/locales/ar.json +1388 -0
- package/locales/bg.json +1387 -0
- package/locales/bn.json +1387 -0
- package/locales/ca.json +1387 -0
- package/locales/cs.json +1387 -0
- package/locales/da.json +1387 -0
- package/locales/de.json +1387 -0
- package/locales/el.json +1387 -0
- package/locales/en-GB.json +27 -0
- package/locales/en.json +1386 -0
- package/locales/en.meta.json +51 -0
- package/locales/es.json +1387 -0
- package/locales/eu.json +1387 -0
- package/locales/fa.json +1388 -0
- package/locales/fi.json +1387 -0
- package/locales/fil.json +1387 -0
- package/locales/fr.json +1387 -0
- package/locales/ga.json +1387 -0
- package/locales/gl.json +1387 -0
- package/locales/gu.json +1387 -0
- package/locales/ha.json +1387 -0
- package/locales/he.json +1388 -0
- package/locales/hi.json +1387 -0
- package/locales/hr.json +1387 -0
- package/locales/hu.json +1387 -0
- package/locales/id.json +1387 -0
- package/locales/ig.json +1387 -0
- package/locales/it.json +1387 -0
- package/locales/ja.json +1387 -0
- package/locales/kn.json +1387 -0
- package/locales/ko.json +1387 -0
- package/locales/mr.json +1387 -0
- package/locales/ms.json +1387 -0
- package/locales/nb.json +1387 -0
- package/locales/nl.json +1387 -0
- package/locales/pl.json +1387 -0
- package/locales/pt.json +1387 -0
- package/locales/ro.json +1387 -0
- package/locales/ru.json +1387 -0
- package/locales/sk.json +1387 -0
- package/locales/sr.json +1387 -0
- package/locales/sv.json +1387 -0
- package/locales/ta.json +1387 -0
- package/locales/th.json +1387 -0
- package/locales/tr.json +1387 -0
- package/locales/uk.json +1387 -0
- package/locales/ur.json +1388 -0
- package/locales/vi.json +1387 -0
- package/locales/yo.json +1387 -0
- package/locales/zh-Hans.json +1387 -0
- package/locales/zh-Hant.json +1387 -0
- package/package.json +138 -5
- package/store-catalog.schema.json +78 -0
- package/theme.schema.json +4668 -0
- package/windows/installer.nsi +946 -0
- package/windows/languages/Basque.nsh +28 -0
- package/windows/languages/Catalan.nsh +28 -0
- package/windows/languages/Croatian.nsh +28 -0
- package/windows/languages/Czech.nsh +28 -0
- package/windows/languages/Danish.nsh +28 -0
- package/windows/languages/Finnish.nsh +28 -0
- package/windows/languages/Galician.nsh +28 -0
- package/windows/languages/Greek.nsh +28 -0
- package/windows/languages/Hungarian.nsh +28 -0
- package/windows/languages/Indonesian.nsh +28 -0
- package/windows/languages/Irish.nsh +28 -0
- package/windows/languages/Malay.nsh +28 -0
- package/windows/languages/Polish.nsh +28 -0
- package/windows/languages/Romanian.nsh +28 -0
- package/windows/languages/Serbian.nsh +28 -0
- package/windows/languages/Slovak.nsh +28 -0
- package/windows/languages/Thai.nsh +28 -0
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// The app menu ("Settings and more"). Its trigger is the app's choice (`ShellMenu.trigger`): by
|
|
3
|
+
// default each pane's wayBetter button at the end of its toolbar (`PaneMenuButton`; the menu hangs
|
|
4
|
+
// under the button that was clicked, so in split view each pane opens it under itself), else a
|
|
5
|
+
// button at the end of the titlebar or the titlebar's logo. It also opens on Alt+F and F10
|
|
6
|
+
// (Shell.tsx; a focused tab forwards the keys), from the command palette and from the last row of
|
|
7
|
+
// the account menu: under the focused pane's button (the first one on screen when no pane is
|
|
8
|
+
// focused). Its rows are the app's (`ShellSite.menu`, menuModel.ts): built-in ones by name, each
|
|
9
|
+
// shown only when the app serves what it needs, and the app's own.
|
|
10
|
+
//
|
|
11
|
+
// Like every shell menu it is an overlay: while open the shell is raised above the tab webviews
|
|
12
|
+
// (`setOverlay`), panes stay visible and nothing is dimmed. Keyboard: arrows, Home/End, type-ahead,
|
|
13
|
+
// the arrow pointing along the reading direction opens a submenu and the other one closes it,
|
|
14
|
+
// Enter or Space chooses, Esc closes one level. Submenus fly out beside the menu and stay inside
|
|
15
|
+
// the window (in a narrow one they lie over it). Words: menuMessages.en.json (`menuText`).
|
|
16
|
+
import { useEffect, useLayoutEffect, useReducer, useRef, useState } from 'react';
|
|
17
|
+
import { createPortal } from 'react-dom';
|
|
18
|
+
import { parseUpdateStatus } from "../updateModel.js";
|
|
19
|
+
import { invoke, listen } from "./bridge.js";
|
|
20
|
+
import { describe, diag } from "./diag.js";
|
|
21
|
+
import { decks } from "./deckStore.js";
|
|
22
|
+
import { refreshDev } from "./devHost.js";
|
|
23
|
+
import { useDev, usePaneTick, useT } from "./hooks.js";
|
|
24
|
+
import { formatNumber } from "./i18n.js";
|
|
25
|
+
import { Icon } from "./icons.js";
|
|
26
|
+
import { DEFAULT_MENU, ZOOM_MAX, ZOOM_MIN, findSpec, joinGroups, languageChoice, languageRows, menuText, moveStop, planMenu, resolveTrigger, shortcutText, specRows, submenuKeys, submenuLeft, typeahead, updateRow, } from "./menuModel.js";
|
|
27
|
+
import { ReportDialog } from "./ReportDialog.js";
|
|
28
|
+
import { safe, setSafeMode } from "./safeStore.js";
|
|
29
|
+
import { hasFeature, site } from "./site.js";
|
|
30
|
+
import { isWeb, setOverlay, shell } from "./state.js";
|
|
31
|
+
import { openWorkspaceSave } from "./TitlebarExtras.js";
|
|
32
|
+
import { os, startDrag, systemMenu } from "./windowBinding.js";
|
|
33
|
+
import { loadWorkspaces, restoreWorkspace, workspaceList, workspaces } from "./workspaces.js";
|
|
34
|
+
import { readZoom, stepZoom, zoomOf, zoomTarget } from "./zoomStore.js";
|
|
35
|
+
import './appMenu.css';
|
|
36
|
+
/** Opens the app menu (Alt+F / F10, the palette, the account menu's last row). */
|
|
37
|
+
export const openAppMenu = (request = {}) => window.dispatchEvent(new CustomEvent('bt-app-menu', { detail: request }));
|
|
38
|
+
/** Opens the "Report a problem" summary (the native menu bar's Help item). */
|
|
39
|
+
export const openReport = () => window.dispatchEvent(new CustomEvent('bt-report'));
|
|
40
|
+
const mac = os === 'macos';
|
|
41
|
+
/** The keys that open the menu, as the button's tooltip and the account menu write them. */
|
|
42
|
+
export const APP_MENU_KEYS = mac ? 'F10' : 'Alt+F';
|
|
43
|
+
const getError = (error) => (error instanceof Error ? error.message : String(error)).replace(/^Error: /, '');
|
|
44
|
+
const percent = (factor) => formatNumber(factor, { style: 'percent', maximumFractionDigits: 0 });
|
|
45
|
+
/** One trigger, as a key: `pane:<id>` for a pane's button. */
|
|
46
|
+
const triggerKey = (at, pane) => (at === 'pane' ? `pane:${pane ?? ''}` : at);
|
|
47
|
+
/** Which trigger the open menu hangs from (null: closed), for the triggers' `aria-expanded`. */
|
|
48
|
+
let openAt = null;
|
|
49
|
+
function noteOpen(key) {
|
|
50
|
+
if (openAt === key)
|
|
51
|
+
return;
|
|
52
|
+
openAt = key;
|
|
53
|
+
window.dispatchEvent(new CustomEvent('bt-app-menu-state'));
|
|
54
|
+
}
|
|
55
|
+
/** `aria-expanded` of one trigger. */
|
|
56
|
+
function useExpanded(key) {
|
|
57
|
+
const [expanded, setExpanded] = useState(openAt === key);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const update = () => setExpanded(openAt === key);
|
|
60
|
+
update();
|
|
61
|
+
window.addEventListener('bt-app-menu-state', update);
|
|
62
|
+
return () => window.removeEventListener('bt-app-menu-state', update);
|
|
63
|
+
}, [key]);
|
|
64
|
+
return expanded;
|
|
65
|
+
}
|
|
66
|
+
/** Pane `id`'s menu button on screen; else the focused pane's; else the first one on screen. */
|
|
67
|
+
function paneButton(id) {
|
|
68
|
+
const shown = Array.from(document.querySelectorAll('.bt-pane-menu[data-pane]')).filter(el => el.getClientRects().length > 0);
|
|
69
|
+
const find = (want) => (want ? shown.find(el => el.dataset.pane === want) : undefined);
|
|
70
|
+
return find(id) ?? find(shell.api?.activePanel?.id) ?? shown[0] ?? null;
|
|
71
|
+
}
|
|
72
|
+
/** The rows of one level a key can stop on: enabled items; the zoom row counts once. */
|
|
73
|
+
function stopsOf(menu) {
|
|
74
|
+
const all = Array.from(menu.querySelectorAll('[data-item]:not(:disabled)'));
|
|
75
|
+
const active = document.activeElement;
|
|
76
|
+
const zoom = all.filter(el => 'zoom' in el.dataset);
|
|
77
|
+
const keep = zoom.find(el => el === active) ?? zoom.find(el => el.getAttribute('aria-disabled') !== 'true') ?? zoom[0];
|
|
78
|
+
return all.filter(el => !('zoom' in el.dataset) || el === keep);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The menu (mounted once, in the titlebar). `trigger`: where clicks and keys open it; default the
|
|
82
|
+
* app's `ShellMenu.trigger`, else `pane`. `true` / `false` are the older spellings of `button` / `logo`.
|
|
83
|
+
*/
|
|
84
|
+
export function AppMenu({ trigger } = {}) {
|
|
85
|
+
const t = useT();
|
|
86
|
+
usePaneTick();
|
|
87
|
+
const dev = useDev();
|
|
88
|
+
const config = site();
|
|
89
|
+
const menu = config.menu ?? {};
|
|
90
|
+
const [open, setOpen] = useState(false);
|
|
91
|
+
const [path, setPath] = useState([]);
|
|
92
|
+
const [anchor, setAnchor] = useState({ at: 'button', top: 50, right: 8 });
|
|
93
|
+
const [mode, setMode] = useState(null);
|
|
94
|
+
const [modeStatus, setModeStatus] = useState(null);
|
|
95
|
+
const [asking, setAsking] = useState(null);
|
|
96
|
+
const [version, setVersion] = useState('');
|
|
97
|
+
const [update, setUpdate] = useState({ state: 'idle' });
|
|
98
|
+
const [askedUpdate, setAskedUpdate] = useState(false);
|
|
99
|
+
const [reporting, setReporting] = useState(false);
|
|
100
|
+
const [error, setError] = useState('');
|
|
101
|
+
const [, tick] = useReducer((n) => n + 1, 0);
|
|
102
|
+
const button = useRef(null);
|
|
103
|
+
const layers = useRef(null);
|
|
104
|
+
const focusNext = useRef(null);
|
|
105
|
+
const before = useRef(null);
|
|
106
|
+
const hover = useRef({ el: null });
|
|
107
|
+
const typed = useRef({ text: '', at: 0 });
|
|
108
|
+
const live = useRef({ open, path });
|
|
109
|
+
live.current = { open, path };
|
|
110
|
+
// Whatever a row shows may change while the menu is open (a language or theme picked in it).
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const events = ['bt-zoom', 'bt-workspaces', 'bt-decks', 'bt-site', 'bt-safe'];
|
|
113
|
+
for (const event of events)
|
|
114
|
+
window.addEventListener(event, tick);
|
|
115
|
+
const off = [config.themes?.subscribe(tick), menu.languages?.subscribe(tick)];
|
|
116
|
+
return () => {
|
|
117
|
+
for (const event of events)
|
|
118
|
+
window.removeEventListener(event, tick);
|
|
119
|
+
for (const dispose of off)
|
|
120
|
+
dispose?.();
|
|
121
|
+
};
|
|
122
|
+
}, []);
|
|
123
|
+
/** Where a request without an anchor opens: the app's trigger. */
|
|
124
|
+
const home = () => menuTrigger(trigger);
|
|
125
|
+
/** The element a request hangs the menu under (null: none on screen). */
|
|
126
|
+
function triggerOf(at, pane) {
|
|
127
|
+
if (at === 'pane')
|
|
128
|
+
return paneButton(pane);
|
|
129
|
+
if (at === 'logo')
|
|
130
|
+
return document.querySelector('button.bt-titlebar-logo');
|
|
131
|
+
return button.current;
|
|
132
|
+
}
|
|
133
|
+
function show(request = {}) {
|
|
134
|
+
const at = request.anchor ?? home();
|
|
135
|
+
const el = triggerOf(at, request.pane);
|
|
136
|
+
const pane = at === 'pane' ? el?.dataset.pane : undefined;
|
|
137
|
+
diag(`menu open: anchor=${at} keyboard=${!!request.keyboard} hasFocus=${document.hasFocus()} active=${describe(document.activeElement)}`);
|
|
138
|
+
const rect = el?.getBoundingClientRect();
|
|
139
|
+
if (rect) {
|
|
140
|
+
const top = Math.round(rect.bottom + 6);
|
|
141
|
+
const rtl = getComputedStyle(el).direction === 'rtl';
|
|
142
|
+
if (at === 'logo')
|
|
143
|
+
setAnchor({ at, top, left: Math.max(8, Math.round(rect.left - 3)) });
|
|
144
|
+
// The menu's end edge lines up with its button's, so it opens over the pane the button is in.
|
|
145
|
+
else if (rtl)
|
|
146
|
+
setAnchor({ at, pane, top, left: Math.max(8, Math.round(rect.left)) });
|
|
147
|
+
else
|
|
148
|
+
setAnchor({ at, pane, top, right: Math.max(8, Math.round(window.innerWidth - rect.right)) });
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// No trigger on screen (only the app's own tab, a deck): under the titlebar, at its end.
|
|
152
|
+
setAnchor({ at, pane, top: 50, right: 8 });
|
|
153
|
+
}
|
|
154
|
+
const active = document.activeElement;
|
|
155
|
+
before.current = active && active !== document.body && document.hasFocus() ? active : null;
|
|
156
|
+
// Keys pressed in a tab open the menu: the shell takes the keyboard. A click on the shell already
|
|
157
|
+
// gave it the keyboard, so nothing native is asked for then.
|
|
158
|
+
if (!document.hasFocus())
|
|
159
|
+
invoke('shell_focus').catch(() => { });
|
|
160
|
+
setAsking(null);
|
|
161
|
+
setError('');
|
|
162
|
+
setPath(request.submenu ? [request.submenu] : []);
|
|
163
|
+
focusNext.current = request.submenu ? { level: 1, edge: 'first' } : { level: 0, edge: request.keyboard ? 'first' : 'menu' };
|
|
164
|
+
setOpen(true);
|
|
165
|
+
// What the rows show, read fresh.
|
|
166
|
+
void readZoom(zoomTarget());
|
|
167
|
+
if (hasFeature('developer'))
|
|
168
|
+
void refreshDev();
|
|
169
|
+
if (hasFeature('workspaces') && !workspaces.loaded)
|
|
170
|
+
void loadWorkspaces().catch(() => { });
|
|
171
|
+
void config.themes?.loadThemes();
|
|
172
|
+
menu.modes?.current().then(setMode, () => { });
|
|
173
|
+
menu.modes?.status?.().then(setModeStatus, () => { });
|
|
174
|
+
if (!version)
|
|
175
|
+
menu.version?.().then(setVersion, () => { });
|
|
176
|
+
setAskedUpdate(false);
|
|
177
|
+
if (hasFeature('updates'))
|
|
178
|
+
invoke('update_status').then(value => setUpdate(parseUpdateStatus(value)), () => { });
|
|
179
|
+
}
|
|
180
|
+
/** `escape`: back to what had focus before. `action` and `away`: nothing focused, so typing returns to the active pane. */
|
|
181
|
+
function close(how = 'away', why = '') {
|
|
182
|
+
diag(`menu close: ${how}${why ? ` (${why})` : ''} hasFocus=${document.hasFocus()}`);
|
|
183
|
+
clearTimeout(hover.current.timer);
|
|
184
|
+
hover.current = { el: null };
|
|
185
|
+
focusNext.current = null;
|
|
186
|
+
setOpen(false);
|
|
187
|
+
setPath([]);
|
|
188
|
+
setAsking(null);
|
|
189
|
+
setError('');
|
|
190
|
+
if (how === 'escape' && before.current?.isConnected)
|
|
191
|
+
before.current.focus();
|
|
192
|
+
before.current = null;
|
|
193
|
+
}
|
|
194
|
+
// The updater's steps (checking, downloading, ready) as they happen.
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (!hasFeature('updates'))
|
|
197
|
+
return;
|
|
198
|
+
let disposed = false;
|
|
199
|
+
let unlisten;
|
|
200
|
+
listen('update-status', event => setUpdate(parseUpdateStatus(event.payload)))
|
|
201
|
+
.then(dispose => { if (disposed)
|
|
202
|
+
dispose();
|
|
203
|
+
else
|
|
204
|
+
unlisten = dispose; })
|
|
205
|
+
.catch(() => { });
|
|
206
|
+
return () => { disposed = true; unlisten?.(); };
|
|
207
|
+
}, []);
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
const onRequest = (event) => {
|
|
210
|
+
const request = event.detail ?? {};
|
|
211
|
+
// Asked again from another trigger, the open menu moves there instead of closing.
|
|
212
|
+
const at = request.anchor ?? home();
|
|
213
|
+
const key = triggerKey(at, at === 'pane' ? request.pane ?? paneButton()?.dataset.pane : undefined);
|
|
214
|
+
if (live.current.open && request.toggle && key === openAt)
|
|
215
|
+
close('escape');
|
|
216
|
+
else
|
|
217
|
+
show(request);
|
|
218
|
+
};
|
|
219
|
+
window.addEventListener('bt-app-menu', onRequest);
|
|
220
|
+
return () => window.removeEventListener('bt-app-menu', onRequest);
|
|
221
|
+
}, [version]);
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
const show = () => setReporting(true);
|
|
224
|
+
window.addEventListener('bt-report', show);
|
|
225
|
+
return () => window.removeEventListener('bt-report', show);
|
|
226
|
+
}, []);
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (!open)
|
|
229
|
+
return;
|
|
230
|
+
// Native tab webviews would cover the menu.
|
|
231
|
+
setOverlay(1);
|
|
232
|
+
// The window lost the keyboard: the menu closes, unless the focus is back a moment later (taking the
|
|
233
|
+
// keyboard from a tab for Alt+F / F10 can pass through a blur on its way to the shell).
|
|
234
|
+
let leaving;
|
|
235
|
+
const away = () => {
|
|
236
|
+
diag(`menu: window blur hasFocus=${document.hasFocus()}`);
|
|
237
|
+
clearTimeout(leaving);
|
|
238
|
+
leaving = setTimeout(() => { if (!document.hasFocus())
|
|
239
|
+
close('away', 'blur');
|
|
240
|
+
else
|
|
241
|
+
diag('menu: blur passed, focus is back'); }, 160);
|
|
242
|
+
};
|
|
243
|
+
const resized = () => close();
|
|
244
|
+
// Keys while nothing in the menu has focus (a click on its padding): Esc closes, arrows enter it.
|
|
245
|
+
const onKey = (event) => {
|
|
246
|
+
if (layers.current?.contains(event.target))
|
|
247
|
+
return;
|
|
248
|
+
if (event.key === 'Escape') {
|
|
249
|
+
event.preventDefault();
|
|
250
|
+
close('escape');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
focusNext.current = { level: live.current.path.length, edge: event.key === 'ArrowDown' ? 'first' : 'last' };
|
|
256
|
+
tick();
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
window.addEventListener('blur', away);
|
|
260
|
+
window.addEventListener('resize', resized);
|
|
261
|
+
window.addEventListener('keydown', onKey);
|
|
262
|
+
return () => { clearTimeout(leaving); setOverlay(-1); window.removeEventListener('blur', away); window.removeEventListener('resize', resized); window.removeEventListener('keydown', onKey); };
|
|
263
|
+
}, [open, anchor.at]);
|
|
264
|
+
// The trigger the menu hangs from shows it is open.
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
noteOpen(open ? triggerKey(anchor.at, anchor.pane) : null);
|
|
267
|
+
return () => noteOpen(null);
|
|
268
|
+
}, [open, anchor.at, anchor.pane]);
|
|
269
|
+
const layer = (level) => layers.current?.querySelector(`[data-level="${level}"]`) ?? null;
|
|
270
|
+
// After every render: submenus sit beside their row, inside the window; then the focus that was asked for.
|
|
271
|
+
useLayoutEffect(() => {
|
|
272
|
+
if (!open)
|
|
273
|
+
return;
|
|
274
|
+
// The menu stays inside the window: a button near the start edge (a narrow pane in split view)
|
|
275
|
+
// would push it out, so it slides back in.
|
|
276
|
+
const root = layer(0);
|
|
277
|
+
if (root) {
|
|
278
|
+
const edge = 8;
|
|
279
|
+
root.style.left = anchor.left === undefined ? '' : `${anchor.left}px`;
|
|
280
|
+
root.style.right = anchor.right === undefined ? '' : `${anchor.right}px`;
|
|
281
|
+
const box = root.getBoundingClientRect();
|
|
282
|
+
if (box.left < edge || box.right > window.innerWidth - edge) {
|
|
283
|
+
root.style.left = `${Math.round(Math.max(edge, Math.min(box.left, window.innerWidth - edge - box.width)))}px`;
|
|
284
|
+
root.style.right = 'auto';
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
for (let level = 1; level <= path.length; level += 1) {
|
|
288
|
+
const el = layer(level);
|
|
289
|
+
const parent = layer(level - 1);
|
|
290
|
+
const row = parent?.querySelector(`[data-item="${CSS.escape(path[level - 1])}"]`);
|
|
291
|
+
if (!el || !parent)
|
|
292
|
+
continue;
|
|
293
|
+
const edge = 8;
|
|
294
|
+
const box = parent.getBoundingClientRect();
|
|
295
|
+
el.style.maxHeight = `${window.innerHeight - edge * 2}px`;
|
|
296
|
+
const { width, height } = el.getBoundingClientRect();
|
|
297
|
+
const left = submenuLeft(box, width, window.innerWidth, edge);
|
|
298
|
+
// It slides in from the side it hangs on.
|
|
299
|
+
el.style.setProperty('--bt-app-sub-from', left < box.left ? '6px' : '-6px');
|
|
300
|
+
const top = Math.max(edge, Math.min((row?.getBoundingClientRect().top ?? box.top) - 6, window.innerHeight - height - edge));
|
|
301
|
+
el.style.left = `${Math.round(left)}px`;
|
|
302
|
+
el.style.top = `${Math.round(top)}px`;
|
|
303
|
+
}
|
|
304
|
+
const want = focusNext.current;
|
|
305
|
+
const el = want ? layer(want.level) : null;
|
|
306
|
+
if (!want || !el)
|
|
307
|
+
return;
|
|
308
|
+
focusNext.current = null;
|
|
309
|
+
const stops = stopsOf(el);
|
|
310
|
+
const target = want.id ? el.querySelector(`[data-item="${CSS.escape(want.id)}"]:not(:disabled)`)
|
|
311
|
+
: want.edge === 'menu' ? el : want.edge === 'last' ? stops[stops.length - 1] : stops[0];
|
|
312
|
+
(target ?? el).focus();
|
|
313
|
+
});
|
|
314
|
+
const openSub = (level, id, focus = false) => {
|
|
315
|
+
setPath(current => [...current.slice(0, level), id]);
|
|
316
|
+
setAsking(null);
|
|
317
|
+
setError('');
|
|
318
|
+
if (focus)
|
|
319
|
+
focusNext.current = { level: level + 1, edge: 'first' };
|
|
320
|
+
};
|
|
321
|
+
/** Closes the submenu at `level`; its row gets the focus back. */
|
|
322
|
+
const closeSub = (level) => {
|
|
323
|
+
focusNext.current = { level: level - 1, id: path[level - 1] };
|
|
324
|
+
setPath(current => current.slice(0, level - 1));
|
|
325
|
+
setAsking(null);
|
|
326
|
+
setError('');
|
|
327
|
+
};
|
|
328
|
+
async function activate(row, level) {
|
|
329
|
+
if (row.disabled)
|
|
330
|
+
return;
|
|
331
|
+
if (row.submenu) {
|
|
332
|
+
if (path[level] === row.id) {
|
|
333
|
+
focusNext.current = { level: level + 1, edge: 'first' };
|
|
334
|
+
tick();
|
|
335
|
+
}
|
|
336
|
+
else
|
|
337
|
+
openSub(level, row.id, true);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
setError('');
|
|
341
|
+
if (!row.keepOpen)
|
|
342
|
+
close('action');
|
|
343
|
+
try {
|
|
344
|
+
await row.run?.();
|
|
345
|
+
}
|
|
346
|
+
catch (failure) {
|
|
347
|
+
if (row.keepOpen)
|
|
348
|
+
setError(getError(failure));
|
|
349
|
+
else
|
|
350
|
+
console.error('[shell] menu', failure);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// -------------------------------------------------------------------------------------------
|
|
354
|
+
// Rows
|
|
355
|
+
const item = (id, label, icon, rest = {}) => ({ kind: 'item', id, label, icon, ...rest });
|
|
356
|
+
/** "Plugins…": a row that opens a section of the app's panel. */
|
|
357
|
+
const section = (name) => menuText('section', { section: name });
|
|
358
|
+
const active = shell.api?.activePanel;
|
|
359
|
+
const activeParams = active?.params;
|
|
360
|
+
const canSplit = !!active && (isWeb(active) || active.group.panels.length > 1);
|
|
361
|
+
const languageName = (code) => menu.languages?.list.find(language => language.code === code)?.name;
|
|
362
|
+
const themeState = config.themes?.current();
|
|
363
|
+
const themeId = config.themes && themeState ? config.themes.adapter.preset(themeState.choice) : '';
|
|
364
|
+
const modeList = menu.modes?.list() ?? [];
|
|
365
|
+
/** Settings → Language: the panel's Settings, scrolled to that group once it is there. */
|
|
366
|
+
function openSettingsAt(anchor) {
|
|
367
|
+
shell.openPanelSection('settings');
|
|
368
|
+
let tries = 0;
|
|
369
|
+
const find = () => {
|
|
370
|
+
const heading = document.getElementById(`${anchor}-h`);
|
|
371
|
+
if (heading)
|
|
372
|
+
heading.scrollIntoView({ block: 'start' });
|
|
373
|
+
else if ((tries += 1) < 40)
|
|
374
|
+
setTimeout(find, 50);
|
|
375
|
+
};
|
|
376
|
+
find();
|
|
377
|
+
}
|
|
378
|
+
function builtin(id) {
|
|
379
|
+
switch (id) {
|
|
380
|
+
case 'new-tab': return item(id, t('wb.shell.tab.new'), 'plus', { shortcut: 'Ctrl+T', run: () => shell.shortcut('new-tab') });
|
|
381
|
+
case 'split-right': return item(id, t('wb.shell.menu.splitRight'), 'split', { shortcut: 'Ctrl+\\', disabled: !canSplit, run: () => shell.shortcut('split-right') });
|
|
382
|
+
case 'split-down': return item(id, t('wb.shell.menu.splitDown'), 'splitDown', { shortcut: 'Ctrl+Shift+\\', disabled: !canSplit, run: () => shell.shortcut('split-down') });
|
|
383
|
+
case 'new-column': return item(id, menuText('newColumn'), 'columns', {
|
|
384
|
+
run: () => {
|
|
385
|
+
// The deck in front, else the first one; with none yet, the deck tab itself (it starts one).
|
|
386
|
+
const target = activeParams?.kind === 'deck' ? activeParams.deckId : decks.list[0]?.id;
|
|
387
|
+
shell.run(target ? { type: 'deck-add', deckId: target } : { type: 'deck' });
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
case 'language': return item(id, t('wb.settings.language.title'), 'globe', { submenu: true, hint: languageName(menu.languages?.state().locale) });
|
|
391
|
+
case 'theme': return item(id, menuText('theme'), 'palette', { submenu: true, hint: themeId ? config.themes.themeName(themeId) : undefined });
|
|
392
|
+
case 'mode': {
|
|
393
|
+
const label = modeList.find(entry => entry.id === mode)?.label;
|
|
394
|
+
return item(id, t('wb.modes.section'), 'layers', { submenu: true, hint: label && modeStatus?.custom ? t('wb.modes.custom', { mode: label }) : label });
|
|
395
|
+
}
|
|
396
|
+
case 'zoom': return { kind: 'zoom', id: 'zoom' };
|
|
397
|
+
case 'plugins': return item(id, section(t('wb.panel.section.plugins')), 'plug', { run: () => shell.openPanelSection('plugins') });
|
|
398
|
+
case 'store': return item(id, section(t('wb.panel.section.store')), 'bag', { run: () => shell.openPanelSection('store') });
|
|
399
|
+
case 'strings': return item(id, section(menuText('strings')), 'globe', { run: () => shell.openPanelSection('languages') });
|
|
400
|
+
case 'workspaces': return item(id, t('wb.ws.title'), 'layout', { submenu: true, hint: workspaces.store.items.find(entry => entry.id === workspaces.active)?.name });
|
|
401
|
+
case 'find': return item(id, menuText('find'), 'search', { shortcut: 'Ctrl+F', disabled: !(shell.api?.panels ?? []).some(panel => isWeb(panel) && panel.api.isVisible), run: () => shell.shortcut('find') });
|
|
402
|
+
case 'shortcuts': return item(id, menuText('shortcuts'), 'keyboard', { run: () => (menu.shortcuts ? menu.shortcuts() : shell.shortcut('command-palette')) });
|
|
403
|
+
case 'palette': return item(id, menuText('palette'), 'command', { shortcut: 'Ctrl+K', run: () => shell.shortcut('command-palette') });
|
|
404
|
+
case 'developer': return item(id, menuText('developer'), 'code', { submenu: true });
|
|
405
|
+
case 'updates': {
|
|
406
|
+
// A real check: the row asks the updater now and says what came of it (the titlebar chip stays the loud part).
|
|
407
|
+
const row = updateRow(update, askedUpdate, version);
|
|
408
|
+
return item(id, row.label, 'reload', {
|
|
409
|
+
hint: row.hint, title: row.title, keepOpen: row.action === 'check' || row.action === 'wait',
|
|
410
|
+
run: async () => {
|
|
411
|
+
if (row.action === 'open') {
|
|
412
|
+
shell.openPanelSection('updates');
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (row.action === 'restart') {
|
|
416
|
+
await invoke('apply_update');
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (row.action !== 'check')
|
|
420
|
+
return;
|
|
421
|
+
setAskedUpdate(true);
|
|
422
|
+
setUpdate({ state: 'checking' });
|
|
423
|
+
// One request, no retry: whatever the updater answers is what the row says.
|
|
424
|
+
try {
|
|
425
|
+
setUpdate(parseUpdateStatus(await invoke('update_check')));
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
setUpdate({ state: 'error', message: '' });
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
// Off: a submenu that says what it does before anything reloads. On: one row that leaves it.
|
|
434
|
+
case 'safe-mode': return safe.active
|
|
435
|
+
? item(id, menuText('safeLeave'), 'shield', { run: () => setSafeMode(false) })
|
|
436
|
+
: item(id, menuText('safeRestart'), 'shield', { submenu: true });
|
|
437
|
+
case 'report': return item(id, menuText('report'), 'flag', { run: () => setReporting(true) });
|
|
438
|
+
case 'about': return item(id, menuText('about', { product: config.product }), 'info', { run: () => shell.openPanelSection('home') });
|
|
439
|
+
case 'settings': return item(id, section(t('wb.panel.section.settings')), 'settings', { run: () => shell.openPanelSection('settings') });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function submenuRows(id) {
|
|
443
|
+
switch (id) {
|
|
444
|
+
case 'language': {
|
|
445
|
+
const languages = menu.languages;
|
|
446
|
+
if (!languages)
|
|
447
|
+
return [];
|
|
448
|
+
const rows = languageRows(languages.list, languages.state(), { site: t('wb.settings.language.followSite'), system: t('wb.settings.language.followSystem'), more: menuText('moreLanguages') });
|
|
449
|
+
return rows.map(row => {
|
|
450
|
+
if (row.kind !== 'item')
|
|
451
|
+
return row;
|
|
452
|
+
const choice = languageChoice(row.id);
|
|
453
|
+
// Switching is live and the menu stays: what was picked can be read (or undone) on the spot.
|
|
454
|
+
return choice ? { ...row, keepOpen: true, run: () => languages.set(choice) } : { ...row, run: () => openSettingsAt('language') };
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
case 'theme': {
|
|
458
|
+
const themes = config.themes;
|
|
459
|
+
if (!themes || !themeState)
|
|
460
|
+
return [];
|
|
461
|
+
const list = themes.allThemes(themeState.choice).map(theme => item(`theme:${theme.id}`, theme.name, undefined, {
|
|
462
|
+
check: 'radio', checked: theme.id === themeId, keepOpen: true, swatch: themes.adapter.dots(theme),
|
|
463
|
+
// As Appearance does: a theme starts without the customizations made on another one.
|
|
464
|
+
run: () => themes.setChoice(themes.adapter.choiceForId(themeState.choice, theme.id)),
|
|
465
|
+
}));
|
|
466
|
+
return joinGroups([list, [
|
|
467
|
+
item('theme:builder', section(t('wb.panel.section.builder')), 'edit', { run: () => shell.openPanelSection('builder') }),
|
|
468
|
+
item('theme:more', menuText('moreAppearance'), 'palette', { run: () => shell.openPanelSection('themes') }),
|
|
469
|
+
]]);
|
|
470
|
+
}
|
|
471
|
+
case 'mode': {
|
|
472
|
+
const modes = menu.modes;
|
|
473
|
+
if (!modes)
|
|
474
|
+
return [];
|
|
475
|
+
const name = (modeId) => modeList.find(entry => entry.id === modeId)?.label ?? modeId;
|
|
476
|
+
const apply = async (to, keep) => { await modes.apply(to, keep); setMode(to); close('action'); };
|
|
477
|
+
const at = path.indexOf('mode') + 1;
|
|
478
|
+
if (asking?.reapply && modeStatus) {
|
|
479
|
+
// What applying the mode again turns on and off, said before it happens.
|
|
480
|
+
return [
|
|
481
|
+
{ kind: 'note', id: 'mode:reapply-hint', title: t('wb.modes.reapply', { mode: name(asking.to) }), text: t('wb.modes.reapplyHint', { mode: name(asking.to) }) },
|
|
482
|
+
...(modeStatus.on.length ? [{ kind: 'note', id: 'mode:reapply-on', title: '', text: t('wb.modes.turnsOn', { list: modeStatus.on.join(', ') }) }] : []),
|
|
483
|
+
...(modeStatus.off.length ? [{ kind: 'note', id: 'mode:reapply-off', title: '', text: t('wb.modes.turnsOff', { list: modeStatus.off.join(', ') }) }] : []),
|
|
484
|
+
item('mode:reapply-yes', t('wb.modes.reapply', { mode: name(asking.to) }), 'reload', { keepOpen: true, run: async () => { await modes.reapply?.(); close('action'); } }),
|
|
485
|
+
item('mode:cancel', t('wb.common.cancel'), 'close', { keepOpen: true, run: () => { focusNext.current = { level: at, id: 'mode:reapply' }; setAsking(null); } }),
|
|
486
|
+
];
|
|
487
|
+
}
|
|
488
|
+
if (asking) {
|
|
489
|
+
// The question Settings asks: what was changed by hand stays, or the mode is taken as it is.
|
|
490
|
+
return [
|
|
491
|
+
{ kind: 'note', id: 'mode:question', title: t('wb.modes.keepQuestion'), text: t('wb.modes.keepHint', { count: asking.changes }) },
|
|
492
|
+
item('mode:keep', t('wb.modes.keep'), 'check', { keepOpen: true, run: () => apply(asking.to, true) }),
|
|
493
|
+
item('mode:reset', t('wb.modes.reset', { mode: name(asking.to) }), 'reload', { keepOpen: true, run: () => apply(asking.to, false) }),
|
|
494
|
+
item('mode:cancel', t('wb.common.cancel'), 'close', { keepOpen: true, run: () => { focusNext.current = { level: at, id: `mode:${asking.to}` }; setAsking(null); } }),
|
|
495
|
+
];
|
|
496
|
+
}
|
|
497
|
+
const list = modeList.map(entry => item(`mode:${entry.id}`, entry.label, undefined, {
|
|
498
|
+
check: 'radio', checked: entry.id === mode, keepOpen: true,
|
|
499
|
+
run: async () => {
|
|
500
|
+
if (entry.id === mode) {
|
|
501
|
+
close('action');
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const changes = await modes.changes(entry.id);
|
|
505
|
+
if (changes > 0) {
|
|
506
|
+
focusNext.current = { level: at, edge: 'first' };
|
|
507
|
+
setAsking({ to: entry.id, changes });
|
|
508
|
+
}
|
|
509
|
+
else
|
|
510
|
+
await apply(entry.id, false);
|
|
511
|
+
},
|
|
512
|
+
}));
|
|
513
|
+
if (!mode || !modeStatus?.custom || !modes.reapply)
|
|
514
|
+
return list;
|
|
515
|
+
// The mode is a starting point: the features moved on, and nothing applies it again unasked.
|
|
516
|
+
const grew = modeStatus.added > 0 ? [{ kind: 'note', id: 'mode:grew', title: '', text: t('wb.modes.grew', { mode: name(mode), count: modeStatus.added }) }] : [];
|
|
517
|
+
return joinGroups([list, [
|
|
518
|
+
{ kind: 'note', id: 'mode:custom', title: t('wb.modes.custom', { mode: name(mode) }) },
|
|
519
|
+
...grew,
|
|
520
|
+
item('mode:reapply', t('wb.modes.reapply', { mode: name(mode) }), 'reload', {
|
|
521
|
+
keepOpen: true,
|
|
522
|
+
run: () => {
|
|
523
|
+
focusNext.current = { level: at, edge: 'first' };
|
|
524
|
+
setAsking({ to: mode, changes: 0, reapply: true });
|
|
525
|
+
if (modeStatus.added > 0)
|
|
526
|
+
void modes.seen?.().then(() => setModeStatus({ ...modeStatus, added: 0 }), () => { });
|
|
527
|
+
},
|
|
528
|
+
}),
|
|
529
|
+
]]);
|
|
530
|
+
}
|
|
531
|
+
case 'workspaces': {
|
|
532
|
+
const list = workspaceList();
|
|
533
|
+
const rows = list.length
|
|
534
|
+
? list.map(entry => item(`ws:${entry.id}`, entry.name, undefined, { check: 'radio', checked: entry.id === workspaces.active, hint: t('wb.ws.tabs', { count: entry.tabs }), run: () => restoreWorkspace(entry.id) }))
|
|
535
|
+
: [{ kind: 'note', id: 'ws:none', title: '', text: menuText('noWorkspaces') }];
|
|
536
|
+
return joinGroups([rows, [item('ws:save', menuText('saveLayout'), 'plus', { run: openWorkspaceSave })]]);
|
|
537
|
+
}
|
|
538
|
+
case 'safe-mode': return [
|
|
539
|
+
{ kind: 'note', id: 'safe:question', title: menuText('safeQuestion'), text: menuText('safeExplain') },
|
|
540
|
+
item('safe:confirm', menuText('safeConfirm'), 'shield', { run: () => setSafeMode(true) }),
|
|
541
|
+
item('safe:cancel', t('wb.common.cancel'), 'close', { keepOpen: true, run: () => closeSub(path.indexOf('safe-mode') + 1) }),
|
|
542
|
+
];
|
|
543
|
+
case 'developer': return [
|
|
544
|
+
item('dev:mode', t('wb.dev.mode'), undefined, { check: 'checkbox', checked: dev.enabled, keepOpen: true, run: async () => { await invoke('dev_set', { enabled: !dev.enabled }); await refreshDev(); } }),
|
|
545
|
+
item('dev:folder', menuText('pluginsFolder'), 'folder', { run: () => invoke('plugins_open_folder') }),
|
|
546
|
+
];
|
|
547
|
+
default: return specRows(findSpec(groups, id)?.submenu?.() ?? []);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const groups = menu.groups ?? DEFAULT_MENU;
|
|
551
|
+
const env = {
|
|
552
|
+
decks: !!config.deck, languages: !!menu.languages, themes: !!config.themes, modes: modeList.length > 0,
|
|
553
|
+
zoom: hasFeature('zoom'), find: hasFeature('find'), safeMode: hasFeature('safeMode'), store: hasFeature('store'), strings: config.features?.strings === true, workspaces: hasFeature('workspaces'), developer: hasFeature('developer'), updates: hasFeature('updates'),
|
|
554
|
+
};
|
|
555
|
+
const rootRows = () => joinGroups(planMenu(groups, env).map(group => group.flatMap(entry => (typeof entry === 'string' ? [builtin(entry)] : specRows([entry])))));
|
|
556
|
+
const labelOf = (id, rows) => rows.find((row) => row.kind === 'item' && row.id === id)?.label ?? '';
|
|
557
|
+
// -------------------------------------------------------------------------------------------
|
|
558
|
+
// Keyboard and pointer
|
|
559
|
+
function onKey(event) {
|
|
560
|
+
const target = event.target;
|
|
561
|
+
const menuEl = target.closest('[data-level]');
|
|
562
|
+
if (!menuEl)
|
|
563
|
+
return;
|
|
564
|
+
const level = Number(menuEl.dataset.level);
|
|
565
|
+
const { key } = event;
|
|
566
|
+
// The keyboard leads now: the row under a resting pointer takes the focus back only when the pointer moves on.
|
|
567
|
+
clearTimeout(hover.current.timer);
|
|
568
|
+
hover.current = { el: null };
|
|
569
|
+
const keys = submenuKeys(getComputedStyle(menuEl).direction === 'rtl');
|
|
570
|
+
const done = () => { event.preventDefault(); event.stopPropagation(); };
|
|
571
|
+
if (key === 'Escape') {
|
|
572
|
+
done();
|
|
573
|
+
if (level > 0)
|
|
574
|
+
closeSub(level);
|
|
575
|
+
else
|
|
576
|
+
close('escape');
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (key === 'Tab') {
|
|
580
|
+
done();
|
|
581
|
+
close('escape');
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
// The zoom row is one stop: left and right move through − + and reset (laid out left to right).
|
|
585
|
+
if ('zoom' in target.dataset && (key === 'ArrowLeft' || key === 'ArrowRight')) {
|
|
586
|
+
done();
|
|
587
|
+
const controls = Array.from(menuEl.querySelectorAll('[data-zoom]'));
|
|
588
|
+
controls[Math.max(0, Math.min(controls.length - 1, controls.indexOf(target) + (key === 'ArrowRight' ? 1 : -1)))]?.focus();
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (key === 'ArrowDown' || key === 'ArrowUp' || key === 'Home' || key === 'End') {
|
|
592
|
+
done();
|
|
593
|
+
const stops = stopsOf(menuEl);
|
|
594
|
+
stops[moveStop(stops.indexOf(target), key, stops.length)]?.focus();
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (key === keys.open) {
|
|
598
|
+
if (target.getAttribute('aria-haspopup') === 'menu' && target.dataset.item) {
|
|
599
|
+
done();
|
|
600
|
+
openSub(level, target.dataset.item, true);
|
|
601
|
+
}
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (key === keys.close) {
|
|
605
|
+
if (level > 0) {
|
|
606
|
+
done();
|
|
607
|
+
closeSub(level);
|
|
608
|
+
}
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const now = Date.now();
|
|
612
|
+
const typing = !!typed.current.text && now - typed.current.at <= 700;
|
|
613
|
+
// Enter, and Space outside a typed word: the row's own click chooses it; on the menu itself nothing is chosen.
|
|
614
|
+
if (key === 'Enter' || (key === ' ' && !typing)) {
|
|
615
|
+
if (target === menuEl)
|
|
616
|
+
done();
|
|
617
|
+
typed.current.text = '';
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (key.length !== 1 || event.ctrlKey || event.metaKey || event.altKey)
|
|
621
|
+
return;
|
|
622
|
+
// Type-ahead: the next row whose label starts with what was typed.
|
|
623
|
+
done();
|
|
624
|
+
typed.current = { text: typing ? typed.current.text + key : key, at: now };
|
|
625
|
+
const stops = stopsOf(menuEl);
|
|
626
|
+
const found = typeahead(stops.map(el => el.dataset.label ?? ''), stops.indexOf(target), typed.current.text);
|
|
627
|
+
if (found >= 0)
|
|
628
|
+
stops[found].focus();
|
|
629
|
+
}
|
|
630
|
+
/** The pointer moves the focus, like the arrows do: one highlight. A row with a submenu opens it after a beat. */
|
|
631
|
+
function onHover(row, level, el) {
|
|
632
|
+
if (hover.current.el === el)
|
|
633
|
+
return;
|
|
634
|
+
clearTimeout(hover.current.timer);
|
|
635
|
+
hover.current = { el };
|
|
636
|
+
if (document.activeElement !== el)
|
|
637
|
+
el.focus({ preventScroll: true });
|
|
638
|
+
if (row?.submenu) {
|
|
639
|
+
if (path[level] !== row.id)
|
|
640
|
+
hover.current.timer = setTimeout(() => openSub(level, row.id), 170);
|
|
641
|
+
}
|
|
642
|
+
else if (path.length > level) {
|
|
643
|
+
hover.current.timer = setTimeout(() => { setPath(current => current.slice(0, level)); setAsking(null); }, 280);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
// -------------------------------------------------------------------------------------------
|
|
647
|
+
function renderRow(row, level, index) {
|
|
648
|
+
if (row.kind === 'separator')
|
|
649
|
+
return _jsx("hr", { role: "separator" }, `sep${index}`);
|
|
650
|
+
if (row.kind === 'note') {
|
|
651
|
+
return _jsxs("div", { className: `bt-app-note${row.tone === 'error' ? ' is-error' : ''}`, role: row.tone === 'error' ? 'alert' : 'presentation', children: [row.title && _jsx("strong", { children: row.title }), row.text && _jsx("span", { children: row.text })] }, row.id);
|
|
652
|
+
}
|
|
653
|
+
if (row.kind === 'zoom') {
|
|
654
|
+
const tab = zoomTarget();
|
|
655
|
+
const factor = zoomOf(tab);
|
|
656
|
+
const control = (id, label, icon, direction, off, className = '') => _jsx("button", { type: "button", role: "menuitem", tabIndex: -1, className: className, "data-item": `zoom:${id}`, "data-zoom": "", "data-label": menuText('zoom'), "aria-label": label, title: tab ? label : menuText('zoomNone'), "aria-disabled": off, onPointerMove: event => onHover(null, level, event.currentTarget), onClick: () => { if (!off)
|
|
657
|
+
void stepZoom(direction, tab); }, children: _jsx(Icon, { name: icon }) });
|
|
658
|
+
return _jsxs("div", { className: `bt-app-zoom${tab ? '' : ' is-off'}`, role: "group", "aria-label": menuText('zoom'), title: tab ? undefined : menuText('zoomNone'), children: [_jsx(Icon, { name: "zoom" }), _jsx("span", { className: "bt-menu-label", children: menuText('zoom') }), _jsxs("span", { className: "bt-app-zoom-step", children: [control('out', menuText('zoomOut'), 'minus', -1, !tab || factor <= ZOOM_MIN), _jsx("output", { className: "bt-app-zoom-value", "aria-live": "polite", children: percent(factor) }), control('in', menuText('zoomIn'), 'plus', 1, !tab || factor >= ZOOM_MAX)] }), control('reset', menuText('zoomReset'), 'reload', 0, !tab || factor === 1, 'bt-app-zoom-reset')] }, "zoom");
|
|
659
|
+
}
|
|
660
|
+
const expanded = row.submenu ? path[level] === row.id : undefined;
|
|
661
|
+
return _jsxs("button", { type: "button", tabIndex: -1, disabled: row.disabled, "data-item": row.id, "data-label": row.label, title: row.title, role: row.check === 'radio' ? 'menuitemradio' : row.check ? 'menuitemcheckbox' : 'menuitem', "aria-checked": row.check ? !!row.checked : undefined, "aria-haspopup": row.submenu ? 'menu' : undefined, "aria-expanded": expanded, onPointerMove: event => onHover(row, level, event.currentTarget), onClick: () => void activate(row, level), children: [row.check ? _jsx("span", { className: "bt-app-check", children: row.checked && _jsx(Icon, { name: "check" }) }) : row.icon ? _jsx(Icon, { name: row.icon }) : _jsx("span", { className: "bt-app-check" }), _jsx("span", { className: "bt-menu-label", children: row.lang ? _jsx("bdi", { lang: row.lang, dir: row.dir, children: row.label }) : row.label }), row.swatch && _jsxs("span", { className: "wb-dots", "aria-hidden": "true", style: { background: row.swatch.page }, children: [_jsx("i", { style: { background: row.swatch.bg, boxShadow: `inset 0 0 0 1px ${row.swatch.border}` } }), _jsx("i", { style: { background: row.swatch.text } }), _jsx("i", { style: { background: row.swatch.accent } })] }), row.hint && _jsx("span", { className: "bt-app-hint", children: row.hint }), row.shortcut && _jsx("span", { className: "bt-menu-keys", children: shortcutText(row.shortcut, mac) }), row.submenu && _jsx(Icon, { name: "chevronRight", className: "bt-app-chevron" })] }, row.id);
|
|
662
|
+
}
|
|
663
|
+
function renderMenus() {
|
|
664
|
+
const root = rootRows();
|
|
665
|
+
const levels = [{ rows: root, label: menuText('label') }];
|
|
666
|
+
path.forEach((id, index) => levels.push({ rows: submenuRows(id), label: labelOf(id, levels[index].rows) }));
|
|
667
|
+
// An error belongs to the level it happened on: the deepest one open.
|
|
668
|
+
if (error)
|
|
669
|
+
levels[levels.length - 1].rows = [...levels[levels.length - 1].rows, { kind: 'note', id: 'error', title: '', text: error, tone: 'error' }];
|
|
670
|
+
return levels.map(({ rows, label }, level) => _jsx("div", { role: "menu", "aria-label": label, tabIndex: -1, "data-level": level, className: level === 0 ? `bt-menu bt-app-menu bt-pop${anchor.at === 'logo' ? '' : ' bt-pop--right'}` : 'bt-menu bt-app-menu bt-app-sub', style: level === 0 ? { top: anchor.top, left: anchor.left, right: anchor.right, maxHeight: `calc(100vh - ${anchor.top + 8}px)` } : undefined, children: rows.map((row, index) => renderRow(row, level, index)) }, level === 0 ? 'root' : `${level}:${path[level - 1]}`));
|
|
671
|
+
}
|
|
672
|
+
const label = menuText('label');
|
|
673
|
+
return _jsxs(_Fragment, { children: [home() === 'button' && _jsx("button", { ref: button, type: "button", className: "bt-app-button", "aria-haspopup": "menu", "aria-expanded": open && anchor.at === 'button', "aria-label": label, title: `${label} (${APP_MENU_KEYS})`, onClick: event => (open && anchor.at === 'button' ? close('escape') : show({ keyboard: event.detail === 0 })), children: _jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("circle", { cx: "12", cy: "5.75", r: "1.6" }), _jsx("circle", { cx: "12", cy: "12", r: "1.6" }), _jsx("circle", { cx: "12", cy: "18.25", r: "1.6" })] }) }), open && createPortal(_jsx("div", { className: "bt-menu-backdrop", onMouseDown: () => close(), onContextMenu: event => { event.preventDefault(); close(); }, children: _jsx("div", { ref: layers, className: "bt-app-layers", onMouseDown: event => event.stopPropagation(), onContextMenu: event => { event.preventDefault(); event.stopPropagation(); }, onKeyDown: onKey, children: renderMenus() }) }), document.body), reporting && _jsx(ReportDialog, { onClose: () => setReporting(false) })] });
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* The app's logo at the start of the titlebar: a second trigger of the same menu, hung under the
|
|
677
|
+
* logo. A click opens it; a press that turns into a drag moves the window, as the titlebar around
|
|
678
|
+
* it does; a right-click shows the window's system menu, where a titlebar icon has always had it.
|
|
679
|
+
*/
|
|
680
|
+
export function AppMenuLogo() {
|
|
681
|
+
useT();
|
|
682
|
+
const expanded = useExpanded('logo');
|
|
683
|
+
const press = useRef(null);
|
|
684
|
+
const { product, logo } = site();
|
|
685
|
+
const label = menuText('label');
|
|
686
|
+
return _jsx("button", { type: "button", className: "bt-titlebar-logo", "aria-haspopup": "menu", "aria-expanded": expanded, "aria-label": `${product}: ${label}`, title: `${product} · ${label}`, onPointerDown: event => { if (event.button === 0)
|
|
687
|
+
press.current = { x: event.clientX, y: event.clientY, dragged: false }; }, onPointerMove: event => {
|
|
688
|
+
const start = press.current;
|
|
689
|
+
if (!start || start.dragged || !(event.buttons & 1))
|
|
690
|
+
return;
|
|
691
|
+
// The press became a drag: the window moves, and no menu opens when the button is released.
|
|
692
|
+
if (Math.hypot(event.clientX - start.x, event.clientY - start.y) > 5) {
|
|
693
|
+
start.dragged = true;
|
|
694
|
+
startDrag();
|
|
695
|
+
}
|
|
696
|
+
}, onClick: event => {
|
|
697
|
+
const dragged = press.current?.dragged;
|
|
698
|
+
press.current = null;
|
|
699
|
+
if (!dragged)
|
|
700
|
+
openAppMenu({ anchor: 'logo', toggle: true, keyboard: event.detail === 0 });
|
|
701
|
+
}, onContextMenu: event => {
|
|
702
|
+
event.preventDefault();
|
|
703
|
+
event.stopPropagation();
|
|
704
|
+
const scale = window.devicePixelRatio || 1;
|
|
705
|
+
systemMenu(Math.round(event.screenX * scale), Math.round(event.screenY * scale));
|
|
706
|
+
}, children: _jsx("img", { src: logo, alt: "", draggable: false }) });
|
|
707
|
+
}
|
|
708
|
+
/** The app's menu trigger: `trigger` as `AppMenu` takes it, else `ShellMenu.trigger`, else `pane`. */
|
|
709
|
+
export const menuTrigger = (trigger) => resolveTrigger(trigger, site().menu?.trigger);
|
|
710
|
+
/**
|
|
711
|
+
* One pane's menu button, at the end of its toolbar (the app's mark inside): opens the app menu
|
|
712
|
+
* under itself, or closes it when it is open there. Shown when the app's trigger is `pane`.
|
|
713
|
+
*/
|
|
714
|
+
export function PaneMenuButton({ pane, children }) {
|
|
715
|
+
useT();
|
|
716
|
+
const expanded = useExpanded(triggerKey('pane', pane));
|
|
717
|
+
const label = menuText('label');
|
|
718
|
+
const { product } = site();
|
|
719
|
+
return _jsx("button", { type: "button", className: "bt-brand-button bt-pane-menu", "data-pane": pane, "aria-haspopup": "menu", "aria-expanded": expanded, "aria-label": `${product}: ${label}`, title: `${product} · ${label} (${APP_MENU_KEYS})`, onClick: event => openAppMenu({ anchor: 'pane', pane, toggle: true, keyboard: event.detail === 0 }), children: children });
|
|
720
|
+
}
|
|
721
|
+
//# sourceMappingURL=AppMenu.js.map
|