waybetter 0.6.0 → 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 +1 -2
- package/bin/waybetter.mjs +2 -5
- package/cli/buildenv.mjs +1 -1
- package/cli/release.mjs +11 -18
- package/cli/releasecheck.mjs +15 -0
- package/cli/remote.mjs +4 -4
- package/cli/store.mjs +80 -28
- package/cli/strings.mjs +2 -0
- package/dist/runtime/stringPackHook.js +2 -2
- package/dist/runtime/stringPackHook.js.map +1 -1
- package/dist/runtime/stringPackTranslate.d.ts +3 -1
- package/dist/runtime/stringPackTranslate.js +6 -3
- package/dist/runtime/stringPackTranslate.js.map +1 -1
- package/dist/runtime/stringPacks.d.ts +53 -5
- package/dist/runtime/stringPacks.js +161 -25
- package/dist/runtime/stringPacks.js.map +1 -1
- 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/themeComponents.js +16 -3
- package/dist/runtime/themeComponents.js.map +1 -1
- package/dist/runtime/themeDecor.js +3 -1
- package/dist/runtime/themeDecor.js.map +1 -1
- 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 +44 -3
- package/dist/runtime/themeDoctor.js +86 -25
- package/dist/runtime/themeDoctor.js.map +1 -1
- 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 +1 -1
- package/dist/runtime/themeIcons.js +13 -5
- package/dist/runtime/themeIcons.js.map +1 -1
- 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 +15 -1
- package/dist/runtime/themeRuntime.js +78 -5
- package/dist/runtime/themeRuntime.js.map +1 -1
- package/dist/runtime/themeSpec.d.ts +5 -1
- package/dist/runtime/themeSpec.js +9 -13
- package/dist/runtime/themeSpec.js.map +1 -1
- package/dist/shell/InstallDialog.js +14 -8
- package/dist/shell/InstallDialog.js.map +1 -1
- package/dist/shell/StoreView.d.ts +12 -0
- package/dist/shell/StoreView.js +9 -4
- package/dist/shell/StoreView.js.map +1 -1
- package/dist/shell/frame/AppMenu.d.ts +26 -3
- package/dist/shell/frame/AppMenu.js +106 -27
- package/dist/shell/frame/AppMenu.js.map +1 -1
- package/dist/shell/frame/FindBar.js +1 -1
- package/dist/shell/frame/FindBar.js.map +1 -1
- package/dist/shell/frame/Shell.d.ts +1 -0
- package/dist/shell/frame/Shell.js +10 -2
- package/dist/shell/frame/Shell.js.map +1 -1
- package/dist/shell/frame/Titlebar.d.ts +2 -1
- package/dist/shell/frame/Titlebar.js +5 -3
- package/dist/shell/frame/Titlebar.js.map +1 -1
- package/dist/shell/frame/WebPanel.js +4 -1
- package/dist/shell/frame/WebPanel.js.map +1 -1
- package/dist/shell/frame/accountSwitcher.css +10 -10
- package/dist/shell/frame/appMenu.css +19 -13
- package/dist/shell/frame/boot.js +2 -0
- package/dist/shell/frame/boot.js.map +1 -1
- package/dist/shell/frame/chromeTheme.d.ts +19 -1
- package/dist/shell/frame/chromeTheme.js +51 -14
- package/dist/shell/frame/chromeTheme.js.map +1 -1
- package/dist/shell/frame/deck.css +30 -30
- package/dist/shell/frame/edges.css +159 -0
- package/dist/shell/frame/index.d.ts +2 -2
- package/dist/shell/frame/index.js +1 -1
- package/dist/shell/frame/index.js.map +1 -1
- package/dist/shell/frame/mediaModel.d.ts +13 -0
- package/dist/shell/frame/mediaModel.js +14 -0
- package/dist/shell/frame/mediaModel.js.map +1 -1
- package/dist/shell/frame/menuMessages.en.json +43 -1
- package/dist/shell/frame/menuModel.d.ts +42 -0
- package/dist/shell/frame/menuModel.js +8 -0
- package/dist/shell/frame/menuModel.js.map +1 -1
- 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.js +3 -1
- package/dist/shell/frame/paletteModel.js.map +1 -1
- package/dist/shell/frame/paneTheme.css +1 -1
- package/dist/shell/frame/reportModel.d.ts +1 -1
- package/dist/shell/frame/reportModel.js +1 -1
- package/dist/shell/frame/reportModel.js.map +1 -1
- package/dist/shell/frame/shell.css +56 -51
- package/dist/shell/frame/state.d.ts +2 -0
- package/dist/shell/frame/state.js +3 -1
- package/dist/shell/frame/state.js.map +1 -1
- package/dist/shell/frame/themeStore.js +21 -6
- package/dist/shell/frame/themeStore.js.map +1 -1
- package/dist/shell/frame/workspaces.css +12 -12
- package/dist/shell/installController.d.ts +16 -1
- package/dist/shell/installController.js +31 -5
- package/dist/shell/installController.js.map +1 -1
- package/dist/shell/panel/Appearance.js +12 -3
- package/dist/shell/panel/Appearance.js.map +1 -1
- package/dist/shell/panel/Settings.d.ts +1 -1
- package/dist/shell/panel/Settings.js +4 -3
- package/dist/shell/panel/Settings.js.map +1 -1
- package/dist/shell/panel/StoreSection.d.ts +2 -1
- package/dist/shell/panel/StoreSection.js +31 -1
- package/dist/shell/panel/StoreSection.js.map +1 -1
- package/dist/shell/panel/StringPacks.d.ts +12 -3
- package/dist/shell/panel/StringPacks.js +33 -8
- package/dist/shell/panel/StringPacks.js.map +1 -1
- package/dist/shell/panel/backup.css +1 -1
- package/dist/shell/panel/builder.css +48 -40
- package/dist/shell/panel/make.css +8 -7
- package/dist/shell/panel/panel.css +401 -401
- package/dist/shell/panel/panelMessages.d.ts +41 -1
- package/dist/shell/panel/panelMessages.js +13 -1
- package/dist/shell/panel/panelMessages.js.map +1 -1
- package/dist/shell/panel/plugins.css +5 -5
- package/dist/shell/panel/strings.css +1 -1
- package/dist/shell/store.css +34 -14
- package/dist/shell/storeMessages.d.ts +24 -0
- package/dist/shell/storeMessages.js +24 -0
- package/dist/shell/storeMessages.js.map +1 -1
- package/dist/shell/storeModel.d.ts +31 -7
- package/dist/shell/storeModel.js +15 -2
- package/dist/shell/storeModel.js.map +1 -1
- package/dist/shell/stringPacksModel.js +7 -2
- package/dist/shell/stringPacksModel.js.map +1 -1
- package/icons.schema.json +9 -0
- package/locales/ar.json +109 -2
- package/locales/bg.json +109 -2
- package/locales/bn.json +109 -2
- package/locales/ca.json +109 -2
- package/locales/cs.json +109 -2
- package/locales/da.json +109 -2
- package/locales/de.json +109 -2
- package/locales/el.json +109 -2
- package/locales/en.json +108 -2
- package/locales/es.json +109 -2
- package/locales/eu.json +109 -2
- package/locales/fa.json +109 -2
- package/locales/fi.json +109 -2
- package/locales/fil.json +109 -2
- package/locales/fr.json +109 -2
- package/locales/ga.json +109 -2
- package/locales/gl.json +109 -2
- package/locales/gu.json +109 -2
- package/locales/ha.json +109 -2
- package/locales/he.json +109 -2
- package/locales/hi.json +109 -2
- package/locales/hr.json +109 -2
- package/locales/hu.json +110 -2
- package/locales/id.json +109 -2
- package/locales/ig.json +109 -2
- package/locales/it.json +109 -2
- package/locales/ja.json +109 -2
- package/locales/kn.json +109 -2
- package/locales/ko.json +109 -2
- package/locales/mr.json +109 -2
- package/locales/ms.json +109 -2
- package/locales/nb.json +109 -2
- package/locales/nl.json +109 -2
- package/locales/pl.json +109 -2
- package/locales/pt.json +109 -2
- package/locales/ro.json +109 -2
- package/locales/ru.json +109 -2
- package/locales/sk.json +109 -2
- package/locales/sr.json +109 -2
- package/locales/sv.json +109 -2
- package/locales/ta.json +109 -2
- package/locales/th.json +109 -2
- package/locales/tr.json +109 -2
- package/locales/uk.json +109 -2
- package/locales/ur.json +109 -2
- package/locales/vi.json +109 -2
- package/locales/yo.json +109 -2
- package/locales/zh-Hans.json +109 -2
- package/locales/zh-Hant.json +109 -2
- package/package.json +2 -3
- package/store-catalog.schema.json +18 -5
- package/theme.schema.json +9 -0
- package/windows/installer.nsi +22 -2
- package/cli/privacy.mjs +0 -768
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"themeStore.js","sourceRoot":"","sources":["../../../../../../shell/src/frame/themeStore.ts"],"names":[],"mappings":"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","sourcesContent":["// The shell's view of theming: the global theme choice, account/pane themes (scopes), installed\n// Google fonts and a theme editor's live preview. The app's settings, pane menus and the chrome\n// share it (they all live in the shell webview). Native code persists everything and themes the\n// site tabs; this module themes the app's own window with the same documents. What a theme\n// document is belongs to the app (`ThemeAdapter`); `createThemeStore(adapter)` makes the store the\n// app keeps and hands to the frame (`site.themes`).\n\nimport type { IconPack } from '../../../runtime/src/themeIcons.ts';\nimport { setChromeIconPack } from './iconPack.tsx';\nimport type { DockviewApi } from 'dockview';\nimport { BOOT_CACHE_KEY } from './boot.ts';\nimport { invoke } from './bridge.ts';\nimport { CHROME_VAR_NAMES, chromeVars, type ChromeTheme } from './chromeTheme.ts';\nimport { SCOPES_DEFAULT, normalizeScopes, prunePanes, themeIdFor, type ScopeSource, type ThemeScopes } from './themeScopes.ts';\nimport { safe } from './safeStore.ts';\nimport { shell, type TabParams } from './state.ts';\n\nexport type Weights = { kind: 'range'; min: number; max: number } | { kind: 'list'; values: number[] };\nexport type InstalledFont = { family: string; weights: Weights; bytes: number; faces: number; installedAt: string };\ntype FaceData = { weight: [number, number]; unicodeRange: string | null; data: string };\n\n/** A theme document as the frame needs it: an id and a name (the rest is the app's). */\nexport type ThemeDoc = { id: string; name: string };\n\n/** What the app's themes are and how native code stores them. */\nexport type ThemeAdapter<Choice = unknown, Spec extends ThemeDoc = ThemeDoc> = {\n /** The choice before anything is read (and when reading fails). */\n defaultChoice: Choice;\n /** Reads the persisted global choice (normalized). */\n load(): Promise<Choice>;\n /** Persists `choice` and themes every site tab (throws native code's error). */\n commit(choice: Choice): Promise<void>;\n /** Shows `spec` (a draft) in every site tab until it is saved or closed; null ends it. */\n preview(spec: Spec | null): Promise<void>;\n /** The theme document a choice shows (its customizations applied). */\n resolve(choice: Choice): Spec;\n /** The chosen theme's id. */\n preset(choice: Choice): string;\n /** The choice a theme id stands for: the global choice itself, or that theme without customizations. */\n choiceForId(choice: Choice, id: string): Choice;\n /** Every theme that can be picked: built-in ones and the choice's imported ones. */\n all(choice: Choice): Spec[];\n /** Google font families a theme uses (in `roles` only, when given). */\n googleFonts(spec: Spec, roles?: readonly string[]): string[];\n /** The chrome's colors and fonts for a theme. */\n chrome(spec: Spec): ChromeTheme;\n /** The icon pack a theme suggests for the chrome (theme engine v2); absent: the frame's own glyphs. */\n iconPack?(spec: Spec, choice?: Choice): IconPack | null;\n /** Three swatch colors for menus (page, text, accent, on the theme's surface and border). */\n dots(spec: Spec): { page: string; bg: string; border: string; text: string; accent: string };\n};\n\nexport type ThemeState<Choice, Spec> = { ready: boolean; choice: Choice; scopes: ThemeScopes; fonts: InstalledFont[]; preview: Spec | null };\n\nconst getError = (error: unknown) => error instanceof Error ? error.message : String(error);\n\nexport type ThemeStore<Choice = unknown, Spec extends ThemeDoc = ThemeDoc> = ReturnType<typeof createThemeStore<Choice, Spec>>;\n\nexport function createThemeStore<Choice, Spec extends ThemeDoc>(adapter: ThemeAdapter<Choice, Spec>) {\n const state: ThemeState<Choice, Spec> = { ready: false, choice: adapter.defaultChoice, scopes: SCOPES_DEFAULT, fonts: [], preview: null };\n const listeners = new Set<() => void>();\n let snapshot = { ...state };\n const emit = () => { snapshot = { ...state }; for (const listener of [...listeners]) listener(); applyChrome(); };\n\n let loading: Promise<void> | null = null;\n /** Reads the persisted theme state once (every caller shares the same load). */\n function loadThemes(): Promise<void> {\n loading ??= Promise.all([\n adapter.load().catch(() => adapter.defaultChoice),\n invoke<unknown>('theme_scopes_get').then(normalizeScopes).catch(() => SCOPES_DEFAULT),\n invoke<InstalledFont[]>('gfonts_installed').catch(() => [] as InstalledFont[]),\n ]).then(([choice, scopes, fonts]) => {\n Object.assign(state, { ready: true, choice, scopes, fonts: Array.isArray(fonts) ? fonts : [] });\n committed = choice;\n emit();\n });\n return loading;\n }\n\n /** The last choice native code acknowledged (an error reverts to it). */\n let committed: Choice | null = null;\n /** Shows `next` right away; `commit` persists it and themes every site tab (reverted on error). */\n async function setChoice(next: Choice, commit = true): Promise<void> {\n const previous = state.choice;\n state.choice = next;\n emit();\n if (!commit) return;\n try { await adapter.commit(next); committed = next; }\n catch (error) { state.choice = committed ?? previous; emit(); throw new Error(getError(error)); }\n }\n async function setScopes(next: ThemeScopes): Promise<void> {\n const previous = state.scopes;\n state.scopes = normalizeScopes(next);\n emit();\n try { await invoke('theme_scopes_set', { scopes: state.scopes }); }\n catch (error) { state.scopes = previous; emit(); throw new Error(getError(error)); }\n }\n\n /** A theme editor's draft, shown in every site tab and the chrome until it is saved or closed. */\n async function setPreview(theme: Spec | null): Promise<void> {\n state.preview = theme;\n emit();\n try { await adapter.preview(theme); } catch { /* Preview is best effort (browser preview). */ }\n }\n\n const themeState = () => state;\n const allThemes = (choice: Choice = state.choice) => adapter.all(choice);\n const knownTheme = (id: string, choice: Choice = state.choice) => allThemes(choice).some(theme => theme.id === id);\n const themeName = (id: string) => allThemes().find(theme => theme.id === id)?.name ?? id;\n\n /** The theme a tab (or pane) shows, as the chrome and the theme lists describe it. */\n function tabTheme(tab: { id?: string; profile?: string }): { id: string; source: ScopeSource; theme: Spec } {\n const found = themeIdFor(state.scopes, tab, adapter.preset(state.choice), id => knownTheme(id));\n return { ...found, theme: adapter.resolve(adapter.choiceForId(state.choice, found.id)) };\n }\n\n // -------------------------------------------------------------------------------------------\n // Google fonts in the shell (chrome, previews): registered from bytes like in the site's page.\n\n const shellFonts = new Map<string, Promise<boolean>>();\n function register(family: string, faces: FaceData[]) {\n for (const face of faces) {\n const bytes = Uint8Array.from(atob(face.data), char => char.charCodeAt(0));\n const [min, max] = face.weight;\n const descriptors: FontFaceDescriptors = { weight: min === max ? String(min) : `${min} ${max}`, style: 'normal', display: 'swap' };\n if (face.unicodeRange) descriptors.unicodeRange = face.unicodeRange;\n const fontFace = new FontFace(family, bytes.buffer, descriptors);\n document.fonts.add(fontFace);\n void fontFace.load().catch(() => {});\n }\n }\n /** Registers an installed Google font in the shell document once. */\n function ensureShellFont(family: string): Promise<boolean> {\n let pending = shellFonts.get(family);\n if (!pending) {\n pending = invoke<{ faces: FaceData[] }>('gfonts_faces', { family })\n .then(result => { register(family, result.faces); return true; })\n .catch(() => { shellFonts.delete(family); return false; });\n shellFonts.set(family, pending);\n }\n return pending;\n }\n\n const previews = new Map<string, Promise<string | null>>();\n let previewSeq = 0;\n /**\n * A text-subset preview of any Google font (a few KB, fetched natively): registers it under a\n * private family name and resolves to that name, or null when it failed.\n */\n function previewFont(family: string, weights: Weights, text: string): Promise<string | null> {\n const key = `${family}|${JSON.stringify(weights)}|${text}`;\n let pending = previews.get(key);\n if (!pending) {\n const alias = `wbp-${++previewSeq}`;\n const attempt = (tries: number): Promise<string | null> => invoke<FaceData[]>('gfonts_preview', { family, weights, text })\n .then(faces => { register(alias, faces); return alias; })\n .catch(error => {\n // Six previews at most are in flight natively; the list retries shortly.\n if (tries > 0 && /busy/i.test(getError(error))) return new Promise<string | null>(done => setTimeout(() => done(attempt(tries - 1)), 250 + Math.random() * 400));\n previews.delete(key);\n return null;\n });\n pending = attempt(12);\n previews.set(key, pending);\n }\n return pending;\n }\n\n async function installFont(family: string, weights: Weights): Promise<InstalledFont> {\n const font = await invoke<InstalledFont>('gfonts_install', { family, weights });\n state.fonts = [...state.fonts.filter(item => item.family !== family), font].sort((a, b) => a.family.localeCompare(b.family));\n shellFonts.delete(family);\n emit();\n return font;\n }\n\n async function removeFont(family: string): Promise<void> {\n await invoke('gfonts_remove', { family });\n state.fonts = state.fonts.filter(item => item.family !== family);\n emit();\n }\n\n const isInstalled = (family: string) => state.fonts.some(font => font.family === family);\n /** Google families a theme needs that are not downloaded yet. */\n const missingFonts = (theme: Spec) => adapter.googleFonts(theme).filter(family => !isInstalled(family));\n\n // -------------------------------------------------------------------------------------------\n // Chrome\n\n function setVars(element: HTMLElement, vars: Record<string, string>) {\n for (const name of CHROME_VAR_NAMES) if (!(name in vars)) element.style.removeProperty(name);\n for (const [name, value] of Object.entries(vars)) element.style.setProperty(name, value);\n }\n\n function chromeFontsFor(theme: Spec) {\n for (const family of adapter.googleFonts(theme, ['chrome', 'mono'])) if (isInstalled(family)) void ensureShellFont(family);\n }\n\n /** The chrome follows the global theme (or the editor's draft); panes with their own theme follow theirs. */\n function applyChrome() {\n if (typeof document === 'undefined') return;\n // Safe mode: the frame's own look, whatever the theme (a theme may be what broke the chrome). The\n // first-frame cache is left alone, so the next normal start paints the owner's theme again.\n if (safe.active) { setVars(document.documentElement, {}); syncGroups(); return; }\n if (!state.ready) return;\n const own = state.scopes.chrome === 'own';\n const global = state.preview ?? adapter.resolve(state.choice);\n chromeFontsFor(global);\n const vars = chromeVars(adapter.chrome(global), own);\n setVars(document.documentElement, vars);\n try { setChromeIconPack(own ? null : adapter.iconPack?.(global, state.choice) ?? null); } catch { setChromeIconPack(null); }\n // The next start paints these from its first frame (boot.ts); an editor's draft is not kept.\n if (!state.preview) { try { localStorage.setItem(BOOT_CACHE_KEY, JSON.stringify(vars)); } catch { /* Best effort. */ } }\n syncGroups();\n }\n\n let dock: DockviewApi | null = null;\n /** Pane chrome: a dock group whose active tab shows another theme than the global one gets that theme. */\n function syncGroups() {\n const api = dock ?? shell.api;\n if (!api) return;\n const own = state.scopes.chrome === 'own';\n for (const group of api.groups) {\n const params = group.activePanel?.params as TabParams | undefined;\n const tab = params && (params.kind === 'web' || params.kind === 'start') ? { id: params.kind === 'web' ? group.activePanel!.id : undefined, profile: params.profile ?? 'default' } : null;\n const scoped = !own && !safe.active && !state.preview && tab ? tabTheme(tab) : null;\n const element = group.element;\n if (scoped && scoped.source !== 'global') {\n chromeFontsFor(scoped.theme);\n element.classList.add('wb-pane-theme');\n setVars(element, chromeVars(adapter.chrome(scoped.theme)));\n element.dataset.wbTheme = scoped.id;\n } else if (element.classList.contains('wb-pane-theme')) {\n element.classList.remove('wb-pane-theme');\n setVars(element, {});\n delete element.dataset.wbTheme;\n }\n }\n }\n\n // Safe mode switched: the chrome drops (or takes back) the theme at once.\n if (typeof window !== 'undefined') window.addEventListener('bt-safe', applyChrome);\n\n /** Shell.tsx hands over the dock once it exists: pane chrome follows focus and layout changes. */\n function attachDock(api: DockviewApi) {\n dock = api;\n const sync = () => requestAnimationFrame(syncGroups);\n api.onDidActivePanelChange(sync);\n api.onDidLayoutChange(sync);\n api.onDidAddPanel(sync);\n api.onDidRemovePanel(() => {\n sync();\n // A closed pane's theme goes with it.\n const open = api.panels.map(panel => panel.id);\n const pruned = prunePanes(state.scopes, open);\n if (pruned !== state.scopes && !shell.restoring) void setScopes(pruned).catch(() => {});\n });\n window.addEventListener('bt-profiles', sync);\n void loadThemes().then(sync);\n }\n\n /** For `useThemeState` (hooks.ts): subscribe, and the current snapshot. */\n const subscribe = (listener: () => void) => { listeners.add(listener); return () => { listeners.delete(listener); }; };\n const current = () => snapshot;\n\n return {\n adapter, loadThemes, setChoice, setScopes, setPreview, themeState, allThemes, knownTheme, themeName, tabTheme,\n ensureShellFont, previewFont, installFont, removeFont, isInstalled, missingFonts, attachDock, subscribe, current,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"themeStore.js","sourceRoot":"","sources":["../../../../../../shell/src/frame/themeStore.ts"],"names":[],"mappings":"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","sourcesContent":["// The shell's view of theming: the global theme choice, account/pane themes (scopes), installed\n// Google fonts and a theme editor's live preview. The app's settings, pane menus and the chrome\n// share it (they all live in the shell webview). Native code persists everything and themes the\n// site tabs; this module themes the app's own window with the same documents. What a theme\n// document is belongs to the app (`ThemeAdapter`); `createThemeStore(adapter)` makes the store the\n// app keeps and hands to the frame (`site.themes`).\n\nimport type { IconPack } from '../../../runtime/src/themeIcons.ts';\nimport { setChromeIconPack } from './iconPack.tsx';\nimport type { DockviewApi } from 'dockview';\nimport { BOOT_CACHE_KEY } from './boot.ts';\nimport { invoke } from './bridge.ts';\nimport { CHROME_VAR_NAMES, chromeEdges, chromeVars, type ChromeTheme } from './chromeTheme.ts';\nimport { SCOPES_DEFAULT, normalizeScopes, prunePanes, themeIdFor, type ScopeSource, type ThemeScopes } from './themeScopes.ts';\nimport { safe } from './safeStore.ts';\nimport { shell, type TabParams } from './state.ts';\n\nexport type Weights = { kind: 'range'; min: number; max: number } | { kind: 'list'; values: number[] };\nexport type InstalledFont = { family: string; weights: Weights; bytes: number; faces: number; installedAt: string };\ntype FaceData = { weight: [number, number]; unicodeRange: string | null; data: string };\n\n/** A theme document as the frame needs it: an id and a name (the rest is the app's). */\nexport type ThemeDoc = { id: string; name: string };\n\n/** What the app's themes are and how native code stores them. */\nexport type ThemeAdapter<Choice = unknown, Spec extends ThemeDoc = ThemeDoc> = {\n /** The choice before anything is read (and when reading fails). */\n defaultChoice: Choice;\n /** Reads the persisted global choice (normalized). */\n load(): Promise<Choice>;\n /** Persists `choice` and themes every site tab (throws native code's error). */\n commit(choice: Choice): Promise<void>;\n /** Shows `spec` (a draft) in every site tab until it is saved or closed; null ends it. */\n preview(spec: Spec | null): Promise<void>;\n /** The theme document a choice shows (its customizations applied). */\n resolve(choice: Choice): Spec;\n /** The chosen theme's id. */\n preset(choice: Choice): string;\n /** The choice a theme id stands for: the global choice itself, or that theme without customizations. */\n choiceForId(choice: Choice, id: string): Choice;\n /** Every theme that can be picked: built-in ones and the choice's imported ones. */\n all(choice: Choice): Spec[];\n /** Google font families a theme uses (in `roles` only, when given). */\n googleFonts(spec: Spec, roles?: readonly string[]): string[];\n /** The chrome's colors and fonts for a theme. */\n chrome(spec: Spec): ChromeTheme;\n /** The icon pack a theme suggests for the chrome (theme engine v2); absent: the frame's own glyphs. */\n iconPack?(spec: Spec, choice?: Choice): IconPack | null;\n /** Three swatch colors for menus (page, text, accent, on the theme's surface and border). */\n dots(spec: Spec): { page: string; bg: string; border: string; text: string; accent: string };\n};\n\nexport type ThemeState<Choice, Spec> = { ready: boolean; choice: Choice; scopes: ThemeScopes; fonts: InstalledFont[]; preview: Spec | null };\n\nconst getError = (error: unknown) => error instanceof Error ? error.message : String(error);\n\nexport type ThemeStore<Choice = unknown, Spec extends ThemeDoc = ThemeDoc> = ReturnType<typeof createThemeStore<Choice, Spec>>;\n\nexport function createThemeStore<Choice, Spec extends ThemeDoc>(adapter: ThemeAdapter<Choice, Spec>) {\n const state: ThemeState<Choice, Spec> = { ready: false, choice: adapter.defaultChoice, scopes: SCOPES_DEFAULT, fonts: [], preview: null };\n const listeners = new Set<() => void>();\n let snapshot = { ...state };\n const emit = () => { snapshot = { ...state }; for (const listener of [...listeners]) listener(); applyChrome(); };\n\n let loading: Promise<void> | null = null;\n /** Reads the persisted theme state once (every caller shares the same load). */\n function loadThemes(): Promise<void> {\n loading ??= Promise.all([\n adapter.load().catch(() => adapter.defaultChoice),\n invoke<unknown>('theme_scopes_get').then(normalizeScopes).catch(() => SCOPES_DEFAULT),\n invoke<InstalledFont[]>('gfonts_installed').catch(() => [] as InstalledFont[]),\n ]).then(([choice, scopes, fonts]) => {\n Object.assign(state, { ready: true, choice, scopes, fonts: Array.isArray(fonts) ? fonts : [] });\n committed = choice;\n emit();\n });\n return loading;\n }\n\n /** The last choice native code acknowledged (an error reverts to it). */\n let committed: Choice | null = null;\n /** Shows `next` right away; `commit` persists it and themes every site tab (reverted on error). */\n async function setChoice(next: Choice, commit = true): Promise<void> {\n const previous = state.choice;\n state.choice = next;\n emit();\n if (!commit) return;\n try { await adapter.commit(next); committed = next; }\n catch (error) { state.choice = committed ?? previous; emit(); throw new Error(getError(error)); }\n }\n async function setScopes(next: ThemeScopes): Promise<void> {\n const previous = state.scopes;\n state.scopes = normalizeScopes(next);\n emit();\n try { await invoke('theme_scopes_set', { scopes: state.scopes }); }\n catch (error) { state.scopes = previous; emit(); throw new Error(getError(error)); }\n }\n\n /** A theme editor's draft, shown in every site tab and the chrome until it is saved or closed. */\n async function setPreview(theme: Spec | null): Promise<void> {\n state.preview = theme;\n emit();\n try { await adapter.preview(theme); } catch { /* Preview is best effort (browser preview). */ }\n }\n\n const themeState = () => state;\n const allThemes = (choice: Choice = state.choice) => adapter.all(choice);\n const knownTheme = (id: string, choice: Choice = state.choice) => allThemes(choice).some(theme => theme.id === id);\n const themeName = (id: string) => allThemes().find(theme => theme.id === id)?.name ?? id;\n\n /** The theme a tab (or pane) shows, as the chrome and the theme lists describe it. */\n function tabTheme(tab: { id?: string; profile?: string }): { id: string; source: ScopeSource; theme: Spec } {\n const found = themeIdFor(state.scopes, tab, adapter.preset(state.choice), id => knownTheme(id));\n return { ...found, theme: adapter.resolve(adapter.choiceForId(state.choice, found.id)) };\n }\n\n // -------------------------------------------------------------------------------------------\n // Google fonts in the shell (chrome, previews): registered from bytes like in the site's page.\n\n const shellFonts = new Map<string, Promise<boolean>>();\n function register(family: string, faces: FaceData[]) {\n for (const face of faces) {\n const bytes = Uint8Array.from(atob(face.data), char => char.charCodeAt(0));\n const [min, max] = face.weight;\n const descriptors: FontFaceDescriptors = { weight: min === max ? String(min) : `${min} ${max}`, style: 'normal', display: 'swap' };\n if (face.unicodeRange) descriptors.unicodeRange = face.unicodeRange;\n const fontFace = new FontFace(family, bytes.buffer, descriptors);\n document.fonts.add(fontFace);\n void fontFace.load().catch(() => {});\n }\n }\n /** Registers an installed Google font in the shell document once. */\n function ensureShellFont(family: string): Promise<boolean> {\n let pending = shellFonts.get(family);\n if (!pending) {\n pending = invoke<{ faces: FaceData[] }>('gfonts_faces', { family })\n .then(result => { register(family, result.faces); return true; })\n .catch(() => { shellFonts.delete(family); return false; });\n shellFonts.set(family, pending);\n }\n return pending;\n }\n\n const previews = new Map<string, Promise<string | null>>();\n let previewSeq = 0;\n /**\n * A text-subset preview of any Google font (a few KB, fetched natively): registers it under a\n * private family name and resolves to that name, or null when it failed.\n */\n function previewFont(family: string, weights: Weights, text: string): Promise<string | null> {\n const key = `${family}|${JSON.stringify(weights)}|${text}`;\n let pending = previews.get(key);\n if (!pending) {\n const alias = `wbp-${++previewSeq}`;\n const attempt = (tries: number): Promise<string | null> => invoke<FaceData[]>('gfonts_preview', { family, weights, text })\n .then(faces => { register(alias, faces); return alias; })\n .catch(error => {\n // Six previews at most are in flight natively; the list retries shortly.\n if (tries > 0 && /busy/i.test(getError(error))) return new Promise<string | null>(done => setTimeout(() => done(attempt(tries - 1)), 250 + Math.random() * 400));\n previews.delete(key);\n return null;\n });\n pending = attempt(12);\n previews.set(key, pending);\n }\n return pending;\n }\n\n async function installFont(family: string, weights: Weights): Promise<InstalledFont> {\n const font = await invoke<InstalledFont>('gfonts_install', { family, weights });\n state.fonts = [...state.fonts.filter(item => item.family !== family), font].sort((a, b) => a.family.localeCompare(b.family));\n shellFonts.delete(family);\n emit();\n return font;\n }\n\n async function removeFont(family: string): Promise<void> {\n await invoke('gfonts_remove', { family });\n state.fonts = state.fonts.filter(item => item.family !== family);\n emit();\n }\n\n const isInstalled = (family: string) => state.fonts.some(font => font.family === family);\n /** Google families a theme needs that are not downloaded yet. */\n const missingFonts = (theme: Spec) => adapter.googleFonts(theme).filter(family => !isInstalled(family));\n\n // -------------------------------------------------------------------------------------------\n // Chrome\n\n function setVars(element: HTMLElement, vars: Record<string, string>) {\n for (const name of CHROME_VAR_NAMES) if (!(name in vars)) element.style.removeProperty(name);\n for (const [name, value] of Object.entries(vars)) element.style.setProperty(name, value);\n }\n\n /** The window's edge style (frame/edges.css). Global only: a pane theme brings its colors, fonts and radii, not its edges. */\n function setEdges(edges: string | null) {\n if (edges) document.documentElement.dataset.wbEdges = edges;\n else delete document.documentElement.dataset.wbEdges;\n }\n\n function chromeFontsFor(theme: Spec) {\n for (const family of adapter.googleFonts(theme, ['chrome', 'mono'])) if (isInstalled(family)) void ensureShellFont(family);\n }\n\n /** The chrome follows the global theme (or the editor's draft); panes with their own theme follow theirs. */\n function applyChrome() {\n if (typeof document === 'undefined') return;\n // Safe mode: the frame's own look, whatever the theme (a theme may be what broke the chrome). The\n // first-frame cache is left alone, so the next normal start paints the owner's theme again.\n if (safe.active) { setVars(document.documentElement, {}); setEdges(null); syncGroups(); return; }\n if (!state.ready) return;\n const own = state.scopes.chrome === 'own';\n const global = state.preview ?? adapter.resolve(state.choice);\n chromeFontsFor(global);\n const vars = chromeVars(adapter.chrome(global), own);\n setVars(document.documentElement, vars);\n setEdges(chromeEdges(vars));\n try { setChromeIconPack(own ? null : adapter.iconPack?.(global, state.choice) ?? null); } catch { setChromeIconPack(null); }\n // The next start paints these from its first frame (boot.ts); an editor's draft is not kept.\n if (!state.preview) { try { localStorage.setItem(BOOT_CACHE_KEY, JSON.stringify(vars)); } catch { /* Best effort. */ } }\n syncGroups();\n }\n\n let dock: DockviewApi | null = null;\n /** Pane chrome: a dock group whose active tab shows another theme than the global one gets that theme. */\n function syncGroups() {\n const api = dock ?? shell.api;\n if (!api) return;\n const own = state.scopes.chrome === 'own';\n for (const group of api.groups) {\n const params = group.activePanel?.params as TabParams | undefined;\n const tab = params && (params.kind === 'web' || params.kind === 'start') ? { id: params.kind === 'web' ? group.activePanel!.id : undefined, profile: params.profile ?? 'default' } : null;\n const scoped = !own && !safe.active && !state.preview && tab ? tabTheme(tab) : null;\n const element = group.element;\n if (scoped && scoped.source !== 'global') {\n chromeFontsFor(scoped.theme);\n element.classList.add('wb-pane-theme');\n const vars = chromeVars(adapter.chrome(scoped.theme));\n setVars(element, vars);\n element.dataset.wbTheme = scoped.id;\n // A pane's edges are its own theme's (edges.css: `.dv-groupview.wb-bevel`).\n element.classList.toggle('wb-bevel', chromeEdges(vars) === 'bevel');\n } else {\n if (element.classList.contains('wb-pane-theme')) {\n element.classList.remove('wb-pane-theme');\n setVars(element, {});\n delete element.dataset.wbTheme;\n }\n element.classList.toggle('wb-bevel', !own && !safe.active && document.documentElement.dataset.wbEdges === 'bevel');\n }\n }\n }\n\n // Safe mode switched: the chrome drops (or takes back) the theme at once.\n if (typeof window !== 'undefined') window.addEventListener('bt-safe', applyChrome);\n\n /** Shell.tsx hands over the dock once it exists: pane chrome follows focus and layout changes. */\n function attachDock(api: DockviewApi) {\n dock = api;\n const sync = () => requestAnimationFrame(syncGroups);\n api.onDidActivePanelChange(sync);\n api.onDidLayoutChange(sync);\n api.onDidAddPanel(sync);\n api.onDidRemovePanel(() => {\n sync();\n // A closed pane's theme goes with it.\n const open = api.panels.map(panel => panel.id);\n const pruned = prunePanes(state.scopes, open);\n if (pruned !== state.scopes && !shell.restoring) void setScopes(pruned).catch(() => {});\n });\n window.addEventListener('bt-profiles', sync);\n void loadThemes().then(sync);\n }\n\n /** For `useThemeState` (hooks.ts): subscribe, and the current snapshot. */\n const subscribe = (listener: () => void) => { listeners.add(listener); return () => { listeners.delete(listener); }; };\n const current = () => snapshot;\n\n return {\n adapter, loadThemes, setChoice, setScopes, setPreview, themeState, allThemes, knownTheme, themeName, tabTheme,\n ensureShellFont, previewFont, installFont, removeFont, isInstalled, missingFonts, attachDock, subscribe, current,\n };\n}\n"]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
.bt-ws-button {
|
|
7
7
|
display: inline-flex; align-items: center; gap: 7px; height: 30px; max-width: 170px; padding-block: 0; padding-inline: 8px 10px; box-sizing: border-box;
|
|
8
|
-
border: 1px solid var(--bt-line-soft); border-radius: 15px; background: rgb(var(--bt-text-rgb) / .035); color: var(--bt-text-2);
|
|
8
|
+
border: 1px solid var(--bt-line-soft); border-radius: calc(15px * var(--bt-rs, 1)); background: rgb(var(--bt-text-rgb) / .035); color: var(--bt-text-2);
|
|
9
9
|
font: 500 12.5px/1 var(--bt-ui); cursor: pointer; transition: background-color var(--bt-t1) var(--bt-ease), border-color var(--bt-t1) var(--bt-ease), color var(--bt-t1) var(--bt-ease);
|
|
10
10
|
}
|
|
11
11
|
.bt-ws-button:not(:has(.bt-ws-button-name)) { width: 30px; padding: 0; justify-content: center; }
|
|
@@ -19,16 +19,16 @@
|
|
|
19
19
|
.bt-ws-head { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; }
|
|
20
20
|
.bt-ws-head strong { font: 600 13px/1.2 var(--bt-ui); color: var(--bt-text); }
|
|
21
21
|
.bt-ws-head span { font: 400 12px/1.35 var(--bt-ui); color: var(--bt-muted); }
|
|
22
|
-
.bt-ws-empty { margin: 2px 6px 6px; padding: 10px 12px; border-radius: 9px; background: rgb(var(--bt-text-rgb) / .035); color: var(--bt-muted); font: 400 12.5px/1.45 var(--bt-ui); }
|
|
22
|
+
.bt-ws-empty { margin: 2px 6px 6px; padding: 10px 12px; border-radius: calc(9px * var(--bt-rs, 1)); background: rgb(var(--bt-text-rgb) / .035); color: var(--bt-muted); font: 400 12.5px/1.45 var(--bt-ui); }
|
|
23
23
|
|
|
24
|
-
.bt-ws-row { position: relative; display: flex; align-items: center; border-radius: 10px; }
|
|
24
|
+
.bt-ws-row { position: relative; display: flex; align-items: center; border-radius: calc(10px * var(--bt-rs, 1)); }
|
|
25
25
|
.bt-ws-row.is-active { background: rgb(var(--bt-accent-rgb) / .08); box-shadow: inset 0 0 0 1px rgb(var(--bt-accent-rgb) / .22); }
|
|
26
|
-
.bt-ws-row.is-active::before { content: ''; position: absolute; inset-inline-start: -5px; top: 10px; height: 22px; width: 3px; border-start-start-radius: 0; border-start-end-radius: 3px; border-end-end-radius: 3px; border-end-start-radius: 0; background: var(--bt-accent); }
|
|
27
|
-
.bt-menu .bt-ws-main { flex: 1; min-width: 0; height: auto; flex-direction: column; align-items: flex-start; gap: 4px; padding: 8px 10px; border-radius: 10px; }
|
|
26
|
+
.bt-ws-row.is-active::before { content: ''; position: absolute; inset-inline-start: -5px; top: 10px; height: 22px; width: 3px; border-start-start-radius: 0; border-start-end-radius: calc(3px * var(--bt-rs, 1)); border-end-end-radius: calc(3px * var(--bt-rs, 1)); border-end-start-radius: 0; background: var(--bt-accent); }
|
|
27
|
+
.bt-menu .bt-ws-main { flex: 1; min-width: 0; height: auto; flex-direction: column; align-items: flex-start; gap: 4px; padding: 8px 10px; border-radius: calc(10px * var(--bt-rs, 1)); }
|
|
28
28
|
.bt-menu .bt-ws-main:focus-visible, .bt-menu .bt-ws-save:focus-visible { box-shadow: var(--bt-ring); }
|
|
29
29
|
.bt-ws-name { display: flex; align-items: center; gap: 6px; max-width: 100%; font: 600 13.5px/1.15 var(--bt-ui); color: var(--bt-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
30
30
|
.bt-ws-meta { font: 400 12px/1.2 var(--bt-ui); color: var(--bt-muted); font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
|
|
31
|
-
.bt-ws-badge { flex: none; padding: 2px 6px; border-radius: 999px; background: rgb(var(--bt-accent-rgb) / .14); color: var(--bt-accent-ink); font: 500 10.5px/1.2 var(--bt-ui); }
|
|
31
|
+
.bt-ws-badge { flex: none; padding: 2px 6px; border-radius: calc(999px * var(--bt-rs, 1)); background: rgb(var(--bt-accent-rgb) / .14); color: var(--bt-accent-ink); font: 500 10.5px/1.2 var(--bt-ui); }
|
|
32
32
|
|
|
33
33
|
.bt-ws-actions { display: flex; gap: 1px; padding-inline-end: 4px; opacity: 0; transition: opacity var(--bt-t1) var(--bt-ease); }
|
|
34
34
|
.bt-ws-row:hover .bt-ws-actions, .bt-ws-row:focus-within .bt-ws-actions, .bt-ws-row.is-active .bt-ws-actions { opacity: 1; }
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
.bt-menu .bt-ws-icon .bt-glyph { width: 14px; height: 14px; }
|
|
37
37
|
.bt-menu .bt-ws-icon[aria-pressed=true] { color: var(--bt-accent-ink); }
|
|
38
38
|
|
|
39
|
-
.bt-menu .bt-ws-save { height: auto; gap: 11px; padding: 8px; border-radius: 10px; }
|
|
40
|
-
.bt-ws-save-icon { display: grid; place-items: center; width: 30px; height: 30px; flex: none; border-radius: 9px; box-shadow: inset 0 0 0 1px var(--bt-line-strong); color: var(--bt-muted); }
|
|
39
|
+
.bt-menu .bt-ws-save { height: auto; gap: 11px; padding: 8px; border-radius: calc(10px * var(--bt-rs, 1)); }
|
|
40
|
+
.bt-ws-save-icon { display: grid; place-items: center; width: 30px; height: 30px; flex: none; border-radius: calc(9px * var(--bt-rs, 1)); box-shadow: inset 0 0 0 1px var(--bt-line-strong); color: var(--bt-muted); }
|
|
41
41
|
.bt-ws-save-icon .bt-glyph { width: 15px; height: 15px; }
|
|
42
42
|
.bt-menu .bt-ws-save:hover .bt-ws-save-icon { box-shadow: inset 0 0 0 1px rgb(var(--bt-accent-rgb) / .6); color: var(--bt-accent-ink); }
|
|
43
43
|
.bt-ws-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
.bt-ws-form { display: flex; gap: 6px; padding: 6px; }
|
|
47
47
|
.bt-ws-input {
|
|
48
|
-
flex: 1; min-width: 0; height: 30px; padding: 0 10px; box-sizing: border-box; border: 1px solid var(--bt-line-strong); border-radius: 8px;
|
|
48
|
+
flex: 1; min-width: 0; height: 30px; padding: 0 10px; box-sizing: border-box; border: 1px solid var(--bt-line-strong); border-radius: calc(8px * var(--bt-rs, 1));
|
|
49
49
|
background: var(--bt-field); color: var(--bt-text); font: 500 13px/1 var(--bt-ui); outline: 0;
|
|
50
50
|
}
|
|
51
51
|
.bt-ws-input:focus { border-color: rgb(var(--bt-accent-rgb) / .6); box-shadow: 0 0 0 3px rgb(var(--bt-accent-rgb) / .14); }
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
.bt-menu .bt-ws-primary:disabled { background: var(--bt-hover); color: var(--bt-faint); }
|
|
55
55
|
|
|
56
56
|
.bt-ws-confirm {
|
|
57
|
-
display: flex; flex-direction: column; gap: 10px; margin: 2px 6px 6px; padding: 10px 12px; border-radius: 9px;
|
|
57
|
+
display: flex; flex-direction: column; gap: 10px; margin: 2px 6px 6px; padding: 10px 12px; border-radius: calc(9px * var(--bt-rs, 1));
|
|
58
58
|
background: rgba(229, 72, 77, .08); box-shadow: inset 0 0 0 1px rgba(229, 72, 77, .25); color: var(--bt-text-2); font: 400 12.5px/1.45 var(--bt-ui);
|
|
59
59
|
}
|
|
60
60
|
.bt-ws-confirm strong { color: var(--bt-text); font-weight: 600; }
|
|
@@ -63,12 +63,12 @@
|
|
|
63
63
|
.bt-menu .bt-ws-danger { background: var(--bt-danger); color: #fff; }
|
|
64
64
|
.bt-menu .bt-ws-danger:hover:not(:disabled) { background: #d93d42; }
|
|
65
65
|
.bt-menu .bt-ws-cancel { background: var(--bt-hover); }
|
|
66
|
-
.bt-ws-error { padding: 6px 10px; color:
|
|
66
|
+
.bt-ws-error { padding: 6px 10px; color: var(--bt-danger-ink); font: 12px/1.4 var(--bt-ui); }
|
|
67
67
|
|
|
68
68
|
/* Quiet chips: only while there is something to say. */
|
|
69
69
|
.bt-tb-chip {
|
|
70
70
|
display: inline-flex; align-items: center; gap: 7px; height: 28px; max-width: 220px; padding-block: 0; padding-inline: 9px 11px; box-sizing: border-box;
|
|
71
|
-
border: 1px solid var(--bt-line-soft); border-radius: 14px; background: rgb(var(--bt-text-rgb) / .035); color: var(--bt-text-2);
|
|
71
|
+
border: 1px solid var(--bt-line-soft); border-radius: calc(14px * var(--bt-rs, 1)); background: rgb(var(--bt-text-rgb) / .035); color: var(--bt-text-2);
|
|
72
72
|
font: 500 12px/1 var(--bt-ui); white-space: nowrap; cursor: pointer; transition: background-color var(--bt-t1) var(--bt-ease), border-color var(--bt-t1) var(--bt-ease);
|
|
73
73
|
}
|
|
74
74
|
.bt-tb-chip:hover { background: var(--bt-hover); border-color: var(--bt-line-strong); color: var(--bt-text); }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type IconPack } from '../runtime/themeIcons.ts';
|
|
2
|
+
import { type InstallKind, type InstallPreview, type OpenedFile, type StoreItem } from './storeModel.ts';
|
|
2
3
|
import type { StoreText } from './storeMessages.ts';
|
|
3
4
|
/** How the controller reaches native code (the app passes Tauri's `invoke` and `listen`). */
|
|
4
5
|
export type StoreBackend = {
|
|
@@ -58,10 +59,12 @@ export type InstallRequest<Theme> = {
|
|
|
58
59
|
theme: Theme | null;
|
|
59
60
|
warnings: string[];
|
|
60
61
|
replaces: string | null;
|
|
62
|
+
pack?: IconPack | null;
|
|
61
63
|
} | {
|
|
62
64
|
status: 'installing';
|
|
63
65
|
preview: InstallPreview;
|
|
64
66
|
theme: Theme | null;
|
|
67
|
+
pack?: IconPack | null;
|
|
65
68
|
} | {
|
|
66
69
|
status: 'done';
|
|
67
70
|
kind: InstallKind;
|
|
@@ -85,6 +88,16 @@ export type InstallControllerOptions<Theme> = {
|
|
|
85
88
|
onPluginsChanged?(): void;
|
|
86
89
|
/** A theme was installed (the app refreshes its store/theme views). */
|
|
87
90
|
onThemesChanged?(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Adds the icon pack the owner confirmed (already sanitized) to the app's icon packs, replacing
|
|
93
|
+
* one with the same id, and makes it the current pack when `enable`. Absent: icon packs cannot
|
|
94
|
+
* be installed (the screen says so).
|
|
95
|
+
*/
|
|
96
|
+
icons?: {
|
|
97
|
+
save(pack: IconPack, options: {
|
|
98
|
+
enable: boolean;
|
|
99
|
+
}): Promise<void>;
|
|
100
|
+
};
|
|
88
101
|
/** A string pack was installed (the app hands the site tabs the packs that are on). */
|
|
89
102
|
onStringsChanged?(): void | Promise<void>;
|
|
90
103
|
/**
|
|
@@ -107,6 +120,8 @@ export type InstallController<Theme> = {
|
|
|
107
120
|
size: number;
|
|
108
121
|
text(): Promise<string>;
|
|
109
122
|
}): Promise<void>;
|
|
123
|
+
/** A file the OS handed to the app ("Open with", double-click), asked for by its id: staged natively like a dropped file. */
|
|
124
|
+
requestOpened(file: OpenedFile): Promise<void>;
|
|
110
125
|
/** A store item: downloaded natively and checked against the signed catalog. */
|
|
111
126
|
requestStoreItem(item: StoreItem): Promise<void>;
|
|
112
127
|
linkRejected(error: string): void;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// The install screen's state machine, shared by every wayBetter app. Requests come from
|
|
2
2
|
// `waybetter://install` links (the native `install-link` event, plus links that arrived before the
|
|
3
|
-
// shell listened), files dropped on the window and the Store view. Each request is staged natively
|
|
3
|
+
// shell listened), files dropped on the window, files the OS handed over ("Open with") and the Store view. Each request is staged natively
|
|
4
4
|
// (downloaded/validated, hashed) and shown on the install screen; nothing is written until the owner
|
|
5
5
|
// presses Install there. The app supplies how it talks to native code (Tauri `invoke`/`listen`) and
|
|
6
6
|
// how its themes are parsed and saved; plugins need nothing app-specific.
|
|
7
|
+
import { parseIconPackJson } from "../runtime/themeIcons.js";
|
|
7
8
|
import { freeThemeId, installedThemeId, kindForFile, MAX_FILE_BYTES, errorText } from "./storeModel.js";
|
|
8
9
|
export function createInstallController(options) {
|
|
9
10
|
const { backend, t, theme: themes } = options;
|
|
@@ -23,6 +24,14 @@ export function createInstallController(options) {
|
|
|
23
24
|
async function prepared(preview) {
|
|
24
25
|
if (preview.kind === 'plugin' || preview.kind === 'strings')
|
|
25
26
|
return { status: 'ready', preview, theme: null, warnings: [], replaces: null };
|
|
27
|
+
if (preview.kind === 'icons') {
|
|
28
|
+
if (!options.icons)
|
|
29
|
+
return { status: 'error', title: t('wb.install.err.icons'), message: t('wb.install.err.iconsNone', { site: options.site }) };
|
|
30
|
+
const parsed = parseIconPackJson(preview.text);
|
|
31
|
+
if ('error' in parsed)
|
|
32
|
+
return { status: 'error', title: t('wb.install.err.icons'), message: parsed.error };
|
|
33
|
+
return { status: 'ready', preview, theme: null, warnings: parsed.warnings, replaces: null, pack: parsed.pack };
|
|
34
|
+
}
|
|
26
35
|
const parsed = themes.parse(preview.text);
|
|
27
36
|
if ('error' in parsed)
|
|
28
37
|
return { status: 'error', title: t('wb.install.err.theme'), message: parsed.error };
|
|
@@ -62,16 +71,20 @@ export function createInstallController(options) {
|
|
|
62
71
|
return;
|
|
63
72
|
}
|
|
64
73
|
if (file.size > MAX_FILE_BYTES[kind]) {
|
|
65
|
-
set({ status: 'error', title: t('wb.install.err.large', { file: file.name }), message: kind === 'strings' ? t('wb.install.err.stringsLarge') : t('wb.install.err.largeHint', { kind }) });
|
|
74
|
+
set({ status: 'error', title: t('wb.install.err.large', { file: file.name }), message: kind === 'strings' ? t('wb.install.err.stringsLarge') : kind === 'icons' ? t('wb.install.err.iconsLarge') : t('wb.install.err.largeHint', { kind }) });
|
|
66
75
|
return;
|
|
67
76
|
}
|
|
68
77
|
await stage(t('wb.install.stage.named', { name: file.name }), t('wb.install.stage.fileDetail'), async () => backend.invoke('install_stage_file', { name: file.name, text: await file.text() }));
|
|
69
78
|
},
|
|
79
|
+
requestOpened(file) {
|
|
80
|
+
return stage(t('wb.install.stage.named', { name: file.name }), t('wb.install.stage.fileDetail'), () => backend.invoke('install_stage_opened', { id: file.id }));
|
|
81
|
+
},
|
|
70
82
|
requestStoreItem(item) {
|
|
71
83
|
return stage(t('wb.install.stage.named', { name: item.name }), t('wb.install.stage.storeDetail'), () => backend.invoke('store_stage', { item: { kind: item.kind, id: item.id } }));
|
|
72
84
|
},
|
|
73
85
|
linkRejected(error) {
|
|
74
|
-
|
|
86
|
+
// `FILE:` errors are about a file the OS handed over, `LINK:` ones about a link.
|
|
87
|
+
set({ status: 'error', title: error.startsWith('FILE: ') ? t('wb.install.err.opened') : t('wb.install.err.link'), message: error.replace(/^(LINK|FILE): /, '') });
|
|
75
88
|
},
|
|
76
89
|
async cancel() {
|
|
77
90
|
const request = current;
|
|
@@ -85,8 +98,19 @@ export function createInstallController(options) {
|
|
|
85
98
|
if (!request || request.status !== 'ready')
|
|
86
99
|
return;
|
|
87
100
|
const { preview } = request;
|
|
88
|
-
set({ status: 'installing', preview, theme: request.theme });
|
|
101
|
+
set({ status: 'installing', preview, theme: request.theme, pack: request.pack });
|
|
89
102
|
try {
|
|
103
|
+
if (preview.kind === 'icons') {
|
|
104
|
+
const outcome = await backend.invoke('install_commit', { token: preview.token, enable, themeId: null });
|
|
105
|
+
// The exact bytes native code staged and hashed; parsed and sanitized again here.
|
|
106
|
+
const parsed = parseIconPackJson(outcome.iconsText ?? '');
|
|
107
|
+
if ('error' in parsed)
|
|
108
|
+
throw new Error(parsed.error);
|
|
109
|
+
await options.icons.save(parsed.pack, { enable });
|
|
110
|
+
options.onThemesChanged?.();
|
|
111
|
+
set({ status: 'done', kind: 'icons', name: parsed.pack.name, fonts: [], enabled: enable });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
90
114
|
if (preview.kind === 'plugin') {
|
|
91
115
|
await backend.invoke('install_commit', { token: preview.token, enable, themeId: null });
|
|
92
116
|
options.onPluginsChanged?.();
|
|
@@ -113,7 +137,7 @@ export function createInstallController(options) {
|
|
|
113
137
|
set({ status: 'done', kind: 'theme', name: themes.facts(parsed.theme).name, themeId: id, fonts: saved.fonts, enabled: saved.enabled });
|
|
114
138
|
}
|
|
115
139
|
catch (error) {
|
|
116
|
-
set({ status: 'error', title: preview.kind === 'strings' ? t('wb.install.err.stringsNotInstalled') : t('wb.install.err.notInstalled', { kind: preview.kind }), message: errorText(error) });
|
|
140
|
+
set({ status: 'error', title: preview.kind === 'strings' ? t('wb.install.err.stringsNotInstalled') : preview.kind === 'icons' ? t('wb.install.err.iconsNotInstalled') : t('wb.install.err.notInstalled', { kind: preview.kind }), message: errorText(error) });
|
|
117
141
|
}
|
|
118
142
|
},
|
|
119
143
|
start() {
|
|
@@ -125,6 +149,8 @@ export function createInstallController(options) {
|
|
|
125
149
|
options.onSetupLink?.(event.setup);
|
|
126
150
|
else if (event.link)
|
|
127
151
|
void controller.requestLink(event.link);
|
|
152
|
+
else if (event.opened)
|
|
153
|
+
void controller.requestOpened(event.opened);
|
|
128
154
|
else if (event.error)
|
|
129
155
|
controller.linkRejected(event.error);
|
|
130
156
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installController.js","sourceRoot":"","sources":["../../../../../shell/src/installController.ts"],"names":[],"mappings":"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","sourcesContent":["// The install screen's state machine, shared by every wayBetter app. Requests come from\n// `waybetter://install` links (the native `install-link` event, plus links that arrived before the\n// shell listened), files dropped on the window and the Store view. Each request is staged natively\n// (downloaded/validated, hashed) and shown on the install screen; nothing is written until the owner\n// presses Install there. The app supplies how it talks to native code (Tauri `invoke`/`listen`) and\n// how its themes are parsed and saved; plugins need nothing app-specific.\n\nimport { freeThemeId, installedThemeId, kindForFile, MAX_FILE_BYTES, errorText, type InstallKind, type InstallPreview, type InstallRecord, type LinkEvent, type StoreItem } from './storeModel.ts';\nimport type { StoreText } from './storeMessages.ts';\n\n/** How the controller reaches native code (the app passes Tauri's `invoke` and `listen`). */\nexport type StoreBackend = {\n invoke<T>(command: string, args?: Record<string, unknown>): Promise<T>;\n /** Subscribes to a shell event; resolves to the unsubscribe function. */\n listen<T>(event: string, handler: (payload: T) => void): Promise<() => void>;\n};\n\n/** What the install screen shows of a theme (the app's theme format stays the app's). */\nexport type ThemeFacts = {\n name: string;\n author?: string;\n description?: string;\n /** Theme CSS, shown as its own code block (it has custom-CSS trust). */\n css?: string;\n /** The theme's own colors for the tile at the top. */\n tile?: { background: string; color: string; border: string };\n};\n\nexport type ThemeAdapter<Theme> = {\n /** Parses (and sanitizes) a theme file the way the app's theme import does. */\n parse(text: string): { theme: Theme; warnings: string[] } | { error: string };\n /** Loads the app's theme state and resolves to the owner's own theme ids. */\n userThemes(): Promise<string[]>;\n /** Theme ids that ship with the app. */\n builtinThemes: readonly string[];\n /** The id a theme asks for (a free one is chosen from it). */\n idOf(theme: Theme): string;\n facts(theme: Theme): ThemeFacts;\n /**\n * Saves the theme the owner confirmed under `id` (replacing the owner's copy `replaces`), and\n * makes it the current theme when `enable`. Resolves to whether it is current now and the font\n * names it uses that are not available yet.\n */\n save(theme: Theme, id: string, options: { replaces: string | null; enable: boolean }): Promise<{ enabled: boolean; fonts: string[] }>;\n};\n\nexport type InstallRequest<Theme> =\n | { status: 'loading'; title: string; detail: string }\n | { status: 'ready'; preview: InstallPreview; theme: Theme | null; warnings: string[]; replaces: string | null }\n | { status: 'installing'; preview: InstallPreview; theme: Theme | null }\n | { status: 'done'; kind: InstallKind; name: string; themeId?: string; fonts: string[]; enabled: boolean }\n | { status: 'error'; title: string; message: string };\n\nexport type InstallControllerOptions<Theme> = {\n backend: StoreBackend;\n /** The app's translator (framework catalogs merged in); called at request time. */\n t: StoreText;\n /** The site's name in messages (\"X\"). */\n site: string;\n theme: ThemeAdapter<Theme>;\n /** A plugin was installed (the app refreshes its plugin list). */\n onPluginsChanged?(): void;\n /** A theme was installed (the app refreshes its store/theme views). */\n onThemesChanged?(): void;\n /** A string pack was installed (the app hands the site tabs the packs that are on). */\n onStringsChanged?(): void | Promise<void>;\n /**\n * A `waybetter://setup` link was opened (its text, shape-checked natively): the app shows its\n * \"Share my setup\" preview (panel/ShareSetup.tsx). Absent: such links are ignored.\n */\n onSetupLink?(text: string): void;\n};\n\nexport type InstallController<Theme> = {\n current(): InstallRequest<Theme> | null;\n subscribe(listener: () => void): () => void;\n /** A validated `waybetter://install` link: download it natively and show the screen. */\n requestLink(link: { kind: InstallKind; url: string }): Promise<void>;\n /** A file dropped on the window. */\n requestFile(file: { name: string; size: number; text(): Promise<string> }): Promise<void>;\n /** A store item: downloaded natively and checked against the signed catalog. */\n requestStoreItem(item: StoreItem): Promise<void>;\n linkRejected(error: string): void;\n /** Closes the screen and drops what was staged. */\n cancel(): Promise<void>;\n close(): void;\n /** The owner pressed Install. `enable`: turn the plugin on / use the theme now. */\n confirm(enable: boolean): Promise<void>;\n /** Listens for install links (once). Returns a disposer. */\n start(): () => void;\n facts(theme: Theme): ThemeFacts;\n};\n\nexport function createInstallController<Theme>(options: InstallControllerOptions<Theme>): InstallController<Theme> {\n const { backend, t, theme: themes } = options;\n let current: InstallRequest<Theme> | null = null;\n let records: Record<string, InstallRecord> = {};\n let started = false;\n const listeners = new Set<() => void>();\n const set = (next: InstallRequest<Theme> | null) => { current = next; for (const listener of [...listeners]) listener(); };\n\n async function refreshRecords() {\n try { records = (await backend.invoke<{ items: Record<string, InstallRecord> }>('install_records')).items ?? {}; } catch { /* Keep the last copy. */ }\n }\n\n /** Parses a staged theme the way the app's import does, and finds the theme it would update. */\n async function prepared(preview: InstallPreview): Promise<InstallRequest<Theme>> {\n if (preview.kind === 'plugin' || preview.kind === 'strings') return { status: 'ready', preview, theme: null, warnings: [], replaces: null };\n const parsed = themes.parse(preview.text);\n if ('error' in parsed) return { status: 'error', title: t('wb.install.err.theme'), message: parsed.error };\n const userThemes = await themes.userThemes();\n const replaces = preview.store ? installedThemeId(preview.store, records, userThemes) : null;\n return { status: 'ready', preview, theme: parsed.theme, warnings: parsed.warnings, replaces };\n }\n\n async function stage(title: string, detail: string, run: () => Promise<InstallPreview>) {\n set({ status: 'loading', title, detail });\n try {\n await Promise.all([themes.userThemes(), refreshRecords()]);\n const preview = await run();\n set(await prepared(preview));\n } catch (error) {\n set({ status: 'error', title, message: errorText(error) });\n }\n }\n\n const controller: InstallController<Theme> = {\n current: () => current,\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n requestLink(link) {\n let host = link.url;\n try { host = new URL(link.url).host; } catch { /* Shown as is. */ }\n return stage(t('wb.install.stage.link', { kind: link.kind, host }), t('wb.install.stage.linkDetail'), () => backend.invoke<InstallPreview>('install_stage_link', { kind: link.kind, url: link.url }));\n },\n async requestFile(file) {\n const kind = kindForFile(file.name);\n if (!kind) {\n set({ status: 'error', title: t('wb.install.err.kind', { file: file.name }), message: t('wb.install.err.kindHint', { site: options.site }) });\n return;\n }\n if (file.size > MAX_FILE_BYTES[kind]) {\n set({ status: 'error', title: t('wb.install.err.large', { file: file.name }), message: kind === 'strings' ? t('wb.install.err.stringsLarge') : t('wb.install.err.largeHint', { kind }) });\n return;\n }\n await stage(t('wb.install.stage.named', { name: file.name }), t('wb.install.stage.fileDetail'), async () => backend.invoke<InstallPreview>('install_stage_file', { name: file.name, text: await file.text() }));\n },\n requestStoreItem(item) {\n return stage(t('wb.install.stage.named', { name: item.name }), t('wb.install.stage.storeDetail'), () => backend.invoke<InstallPreview>('store_stage', { item: { kind: item.kind, id: item.id } }));\n },\n linkRejected(error) {\n set({ status: 'error', title: t('wb.install.err.link'), message: error.replace(/^LINK: /, '') });\n },\n async cancel() {\n const request = current;\n set(null);\n if (request && request.status === 'ready') await backend.invoke('install_cancel', { token: request.preview.token }).catch(() => {});\n },\n close: () => set(null),\n async confirm(enable) {\n const request = current;\n if (!request || request.status !== 'ready') return;\n const { preview } = request;\n set({ status: 'installing', preview, theme: request.theme });\n try {\n if (preview.kind === 'plugin') {\n await backend.invoke('install_commit', { token: preview.token, enable, themeId: null });\n options.onPluginsChanged?.();\n set({ status: 'done', kind: 'plugin', name: preview.plugin?.name ?? preview.file ?? 'Plugin', fonts: [], enabled: enable || Boolean(preview.existing?.enabled) });\n return;\n }\n if (preview.kind === 'strings') {\n await backend.invoke('install_commit', { token: preview.token, enable, themeId: null });\n await options.onStringsChanged?.();\n set({ status: 'done', kind: 'strings', name: preview.strings?.name ?? 'String pack', fonts: [], enabled: enable || Boolean(preview.existing?.enabled) });\n return;\n }\n const own = await themes.userThemes();\n const wanted = themes.idOf(request.theme!);\n const taken = [...themes.builtinThemes, ...own.filter(id => id !== request.replaces)];\n const id = request.replaces ?? freeThemeId(wanted, taken);\n const outcome = await backend.invoke<{ themeText: string | null }>('install_commit', { token: preview.token, enable, themeId: id });\n // The exact bytes native code staged and hashed; parsed and sanitized again here.\n const parsed = themes.parse(outcome.themeText ?? '');\n if ('error' in parsed) throw new Error(parsed.error);\n const saved = await themes.save(parsed.theme, id, { replaces: request.replaces, enable });\n options.onThemesChanged?.();\n set({ status: 'done', kind: 'theme', name: themes.facts(parsed.theme).name, themeId: id, fonts: saved.fonts, enabled: saved.enabled });\n } catch (error) {\n set({ status: 'error', title: preview.kind === 'strings' ? t('wb.install.err.stringsNotInstalled') : t('wb.install.err.notInstalled', { kind: preview.kind }), message: errorText(error) });\n }\n },\n start() {\n if (started) return () => {};\n started = true;\n const handle = (event: LinkEvent) => {\n if (event.setup) options.onSetupLink?.(event.setup);\n else if (event.link) void controller.requestLink(event.link);\n else if (event.error) controller.linkRejected(event.error);\n };\n // Native code queues every link and also emits it; a handled event clears the queue so a\n // reload of the shell does not show it again.\n const unlisten = backend.listen<LinkEvent>('install-link', event => { handle(event); void backend.invoke('install_pending').catch(() => {}); });\n // Links that arrived before the shell listened (the app was started by one): the newest.\n void unlisten.then(() => backend.invoke<LinkEvent[]>('install_pending').then(events => {\n const last = events[events.length - 1];\n if (last && current === null) handle(last);\n })).catch(() => {});\n return () => { started = false; void unlisten.then(off => off()).catch(() => {}); };\n },\n facts: theme => themes.facts(theme),\n };\n return controller;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"installController.js","sourceRoot":"","sources":["../../../../../shell/src/installController.ts"],"names":[],"mappings":"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","sourcesContent":["// The install screen's state machine, shared by every wayBetter app. Requests come from\n// `waybetter://install` links (the native `install-link` event, plus links that arrived before the\n// shell listened), files dropped on the window, files the OS handed over (\"Open with\") and the Store view. Each request is staged natively\n// (downloaded/validated, hashed) and shown on the install screen; nothing is written until the owner\n// presses Install there. The app supplies how it talks to native code (Tauri `invoke`/`listen`) and\n// how its themes are parsed and saved; plugins need nothing app-specific.\n\nimport { parseIconPackJson, type IconPack } from '../../runtime/src/themeIcons.ts';\nimport { freeThemeId, installedThemeId, kindForFile, MAX_FILE_BYTES, errorText, type InstallKind, type InstallPreview, type InstallRecord, type LinkEvent, type OpenedFile, type StoreItem } from './storeModel.ts';\nimport type { StoreText } from './storeMessages.ts';\n\n/** How the controller reaches native code (the app passes Tauri's `invoke` and `listen`). */\nexport type StoreBackend = {\n invoke<T>(command: string, args?: Record<string, unknown>): Promise<T>;\n /** Subscribes to a shell event; resolves to the unsubscribe function. */\n listen<T>(event: string, handler: (payload: T) => void): Promise<() => void>;\n};\n\n/** What the install screen shows of a theme (the app's theme format stays the app's). */\nexport type ThemeFacts = {\n name: string;\n author?: string;\n description?: string;\n /** Theme CSS, shown as its own code block (it has custom-CSS trust). */\n css?: string;\n /** The theme's own colors for the tile at the top. */\n tile?: { background: string; color: string; border: string };\n};\n\nexport type ThemeAdapter<Theme> = {\n /** Parses (and sanitizes) a theme file the way the app's theme import does. */\n parse(text: string): { theme: Theme; warnings: string[] } | { error: string };\n /** Loads the app's theme state and resolves to the owner's own theme ids. */\n userThemes(): Promise<string[]>;\n /** Theme ids that ship with the app. */\n builtinThemes: readonly string[];\n /** The id a theme asks for (a free one is chosen from it). */\n idOf(theme: Theme): string;\n facts(theme: Theme): ThemeFacts;\n /**\n * Saves the theme the owner confirmed under `id` (replacing the owner's copy `replaces`), and\n * makes it the current theme when `enable`. Resolves to whether it is current now and the font\n * names it uses that are not available yet.\n */\n save(theme: Theme, id: string, options: { replaces: string | null; enable: boolean }): Promise<{ enabled: boolean; fonts: string[] }>;\n};\n\nexport type InstallRequest<Theme> =\n | { status: 'loading'; title: string; detail: string }\n | { status: 'ready'; preview: InstallPreview; theme: Theme | null; warnings: string[]; replaces: string | null; pack?: IconPack | null }\n | { status: 'installing'; preview: InstallPreview; theme: Theme | null; pack?: IconPack | null }\n | { status: 'done'; kind: InstallKind; name: string; themeId?: string; fonts: string[]; enabled: boolean }\n | { status: 'error'; title: string; message: string };\n\nexport type InstallControllerOptions<Theme> = {\n backend: StoreBackend;\n /** The app's translator (framework catalogs merged in); called at request time. */\n t: StoreText;\n /** The site's name in messages (\"X\"). */\n site: string;\n theme: ThemeAdapter<Theme>;\n /** A plugin was installed (the app refreshes its plugin list). */\n onPluginsChanged?(): void;\n /** A theme was installed (the app refreshes its store/theme views). */\n onThemesChanged?(): void;\n /**\n * Adds the icon pack the owner confirmed (already sanitized) to the app's icon packs, replacing\n * one with the same id, and makes it the current pack when `enable`. Absent: icon packs cannot\n * be installed (the screen says so).\n */\n icons?: { save(pack: IconPack, options: { enable: boolean }): Promise<void> };\n /** A string pack was installed (the app hands the site tabs the packs that are on). */\n onStringsChanged?(): void | Promise<void>;\n /**\n * A `waybetter://setup` link was opened (its text, shape-checked natively): the app shows its\n * \"Share my setup\" preview (panel/ShareSetup.tsx). Absent: such links are ignored.\n */\n onSetupLink?(text: string): void;\n};\n\nexport type InstallController<Theme> = {\n current(): InstallRequest<Theme> | null;\n subscribe(listener: () => void): () => void;\n /** A validated `waybetter://install` link: download it natively and show the screen. */\n requestLink(link: { kind: InstallKind; url: string }): Promise<void>;\n /** A file dropped on the window. */\n requestFile(file: { name: string; size: number; text(): Promise<string> }): Promise<void>;\n /** A file the OS handed to the app (\"Open with\", double-click), asked for by its id: staged natively like a dropped file. */\n requestOpened(file: OpenedFile): Promise<void>;\n /** A store item: downloaded natively and checked against the signed catalog. */\n requestStoreItem(item: StoreItem): Promise<void>;\n linkRejected(error: string): void;\n /** Closes the screen and drops what was staged. */\n cancel(): Promise<void>;\n close(): void;\n /** The owner pressed Install. `enable`: turn the plugin on / use the theme now. */\n confirm(enable: boolean): Promise<void>;\n /** Listens for install links (once). Returns a disposer. */\n start(): () => void;\n facts(theme: Theme): ThemeFacts;\n};\n\nexport function createInstallController<Theme>(options: InstallControllerOptions<Theme>): InstallController<Theme> {\n const { backend, t, theme: themes } = options;\n let current: InstallRequest<Theme> | null = null;\n let records: Record<string, InstallRecord> = {};\n let started = false;\n const listeners = new Set<() => void>();\n const set = (next: InstallRequest<Theme> | null) => { current = next; for (const listener of [...listeners]) listener(); };\n\n async function refreshRecords() {\n try { records = (await backend.invoke<{ items: Record<string, InstallRecord> }>('install_records')).items ?? {}; } catch { /* Keep the last copy. */ }\n }\n\n /** Parses a staged theme the way the app's import does, and finds the theme it would update. */\n async function prepared(preview: InstallPreview): Promise<InstallRequest<Theme>> {\n if (preview.kind === 'plugin' || preview.kind === 'strings') return { status: 'ready', preview, theme: null, warnings: [], replaces: null };\n if (preview.kind === 'icons') {\n if (!options.icons) return { status: 'error', title: t('wb.install.err.icons'), message: t('wb.install.err.iconsNone', { site: options.site }) };\n const parsed = parseIconPackJson(preview.text);\n if ('error' in parsed) return { status: 'error', title: t('wb.install.err.icons'), message: parsed.error };\n return { status: 'ready', preview, theme: null, warnings: parsed.warnings, replaces: null, pack: parsed.pack };\n }\n const parsed = themes.parse(preview.text);\n if ('error' in parsed) return { status: 'error', title: t('wb.install.err.theme'), message: parsed.error };\n const userThemes = await themes.userThemes();\n const replaces = preview.store ? installedThemeId(preview.store, records, userThemes) : null;\n return { status: 'ready', preview, theme: parsed.theme, warnings: parsed.warnings, replaces };\n }\n\n async function stage(title: string, detail: string, run: () => Promise<InstallPreview>) {\n set({ status: 'loading', title, detail });\n try {\n await Promise.all([themes.userThemes(), refreshRecords()]);\n const preview = await run();\n set(await prepared(preview));\n } catch (error) {\n set({ status: 'error', title, message: errorText(error) });\n }\n }\n\n const controller: InstallController<Theme> = {\n current: () => current,\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n requestLink(link) {\n let host = link.url;\n try { host = new URL(link.url).host; } catch { /* Shown as is. */ }\n return stage(t('wb.install.stage.link', { kind: link.kind, host }), t('wb.install.stage.linkDetail'), () => backend.invoke<InstallPreview>('install_stage_link', { kind: link.kind, url: link.url }));\n },\n async requestFile(file) {\n const kind = kindForFile(file.name);\n if (!kind) {\n set({ status: 'error', title: t('wb.install.err.kind', { file: file.name }), message: t('wb.install.err.kindHint', { site: options.site }) });\n return;\n }\n if (file.size > MAX_FILE_BYTES[kind]) {\n set({ status: 'error', title: t('wb.install.err.large', { file: file.name }), message: kind === 'strings' ? t('wb.install.err.stringsLarge') : kind === 'icons' ? t('wb.install.err.iconsLarge') : t('wb.install.err.largeHint', { kind }) });\n return;\n }\n await stage(t('wb.install.stage.named', { name: file.name }), t('wb.install.stage.fileDetail'), async () => backend.invoke<InstallPreview>('install_stage_file', { name: file.name, text: await file.text() }));\n },\n requestOpened(file) {\n return stage(t('wb.install.stage.named', { name: file.name }), t('wb.install.stage.fileDetail'), () => backend.invoke<InstallPreview>('install_stage_opened', { id: file.id }));\n },\n requestStoreItem(item) {\n return stage(t('wb.install.stage.named', { name: item.name }), t('wb.install.stage.storeDetail'), () => backend.invoke<InstallPreview>('store_stage', { item: { kind: item.kind, id: item.id } }));\n },\n linkRejected(error) {\n // `FILE:` errors are about a file the OS handed over, `LINK:` ones about a link.\n set({ status: 'error', title: error.startsWith('FILE: ') ? t('wb.install.err.opened') : t('wb.install.err.link'), message: error.replace(/^(LINK|FILE): /, '') });\n },\n async cancel() {\n const request = current;\n set(null);\n if (request && request.status === 'ready') await backend.invoke('install_cancel', { token: request.preview.token }).catch(() => {});\n },\n close: () => set(null),\n async confirm(enable) {\n const request = current;\n if (!request || request.status !== 'ready') return;\n const { preview } = request;\n set({ status: 'installing', preview, theme: request.theme, pack: request.pack });\n try {\n if (preview.kind === 'icons') {\n const outcome = await backend.invoke<{ iconsText: string | null }>('install_commit', { token: preview.token, enable, themeId: null });\n // The exact bytes native code staged and hashed; parsed and sanitized again here.\n const parsed = parseIconPackJson(outcome.iconsText ?? '');\n if ('error' in parsed) throw new Error(parsed.error);\n await options.icons!.save(parsed.pack, { enable });\n options.onThemesChanged?.();\n set({ status: 'done', kind: 'icons', name: parsed.pack.name, fonts: [], enabled: enable });\n return;\n }\n if (preview.kind === 'plugin') {\n await backend.invoke('install_commit', { token: preview.token, enable, themeId: null });\n options.onPluginsChanged?.();\n set({ status: 'done', kind: 'plugin', name: preview.plugin?.name ?? preview.file ?? 'Plugin', fonts: [], enabled: enable || Boolean(preview.existing?.enabled) });\n return;\n }\n if (preview.kind === 'strings') {\n await backend.invoke('install_commit', { token: preview.token, enable, themeId: null });\n await options.onStringsChanged?.();\n set({ status: 'done', kind: 'strings', name: preview.strings?.name ?? 'String pack', fonts: [], enabled: enable || Boolean(preview.existing?.enabled) });\n return;\n }\n const own = await themes.userThemes();\n const wanted = themes.idOf(request.theme!);\n const taken = [...themes.builtinThemes, ...own.filter(id => id !== request.replaces)];\n const id = request.replaces ?? freeThemeId(wanted, taken);\n const outcome = await backend.invoke<{ themeText: string | null }>('install_commit', { token: preview.token, enable, themeId: id });\n // The exact bytes native code staged and hashed; parsed and sanitized again here.\n const parsed = themes.parse(outcome.themeText ?? '');\n if ('error' in parsed) throw new Error(parsed.error);\n const saved = await themes.save(parsed.theme, id, { replaces: request.replaces, enable });\n options.onThemesChanged?.();\n set({ status: 'done', kind: 'theme', name: themes.facts(parsed.theme).name, themeId: id, fonts: saved.fonts, enabled: saved.enabled });\n } catch (error) {\n set({ status: 'error', title: preview.kind === 'strings' ? t('wb.install.err.stringsNotInstalled') : preview.kind === 'icons' ? t('wb.install.err.iconsNotInstalled') : t('wb.install.err.notInstalled', { kind: preview.kind }), message: errorText(error) });\n }\n },\n start() {\n if (started) return () => {};\n started = true;\n const handle = (event: LinkEvent) => {\n if (event.setup) options.onSetupLink?.(event.setup);\n else if (event.link) void controller.requestLink(event.link);\n else if (event.opened) void controller.requestOpened(event.opened);\n else if (event.error) controller.linkRejected(event.error);\n };\n // Native code queues every link and also emits it; a handled event clears the queue so a\n // reload of the shell does not show it again.\n const unlisten = backend.listen<LinkEvent>('install-link', event => { handle(event); void backend.invoke('install_pending').catch(() => {}); });\n // Links that arrived before the shell listened (the app was started by one): the newest.\n void unlisten.then(() => backend.invoke<LinkEvent[]>('install_pending').then(events => {\n const last = events[events.length - 1];\n if (last && current === null) handle(last);\n })).catch(() => {});\n return () => { started = false; void unlisten.then(off => off()).catch(() => {}); };\n },\n facts: theme => themes.facts(theme),\n };\n return controller;\n}\n"]}
|
|
@@ -13,7 +13,7 @@ import { ThemeDots } from "../frame/PaneTheme.js";
|
|
|
13
13
|
import { shell } from "../frame/state.js";
|
|
14
14
|
import { assign } from "../frame/themeScopes.js";
|
|
15
15
|
import { tabProfile } from "../profileModel.js";
|
|
16
|
-
import { Button, ColorField, Row, Section, Segmented, Select, Slider, Toggle, useSettingsQuery } from "./controls.js";
|
|
16
|
+
import { Badge, Button, ColorField, Row, Section, Segmented, Select, Slider, Toggle, useSettingsQuery } from "./controls.js";
|
|
17
17
|
import { IconPackImport, IconPackReview } from "./IconPackInstall.js";
|
|
18
18
|
import { themeDoctor } from "../../runtime/themeDoctor.js";
|
|
19
19
|
import { bytesLabel, defaultWeights, loadCatalog, weightsLabel } from "./fonts/catalog.js";
|
|
@@ -170,9 +170,18 @@ export function Appearance({ notify }) {
|
|
|
170
170
|
}) })] }), _jsxs(Section, { title: searching ? t('wb.appearance.theme') : t('wb.appearance.customize', { name: base.name }), id: "customize", hint: searching ? undefined : t('wb.appearance.customizeHint'), action: !searching && hasOverrides(choice) ? _jsxs("div", { className: "bt-inline", children: [_jsx(Button, { size: "sm", tone: "ghost", onClick: () => void update({ ...choice, overrides: {}, accent: null }), children: t('wb.common.reset') }), _jsx(Button, { size: "sm", onClick: saveAsNew, children: t('wb.appearance.saveAsTheme') })] }) : undefined, children: [_jsx(Row, { label: t('wb.appearance.accent'), hint: t('wb.appearance.accentHint'), keywords: "color colour highlight link", wide: true, children: _jsx(ColorField, { label: t('wb.appearance.accentColor'), value: choice.accent, swatches: [...themes.accents], autoLabel: t('wb.appearance.theme'), onChange: accent => void update({ ...choice, accent }) }) }), themes.iconPacks?.length ? _jsx(Row, { label: t('wb.appearance.icons'), hint: t('wb.appearance.iconsHint'), keywords: "icon pack glyphs symbols pixel import", wide: true, children: _jsxs("div", { className: "wb-pack-row", children: [_jsxs("div", { className: "bt-inline", children: [_jsx(Select, { label: t('wb.appearance.icons'), value: choice.icons ?? 'theme', width: 220, onChange: icons => void update({ ...choice, icons: icons === 'theme' ? undefined : icons }), options: [{ value: 'theme', label: t('wb.appearance.icons.theme') }, { value: 'site', label: t('wb.appearance.icons.site') }, ...themes.iconPacks.filter(pack => !choice.packs?.some(item => item.id === pack.id)).map(pack => ({ value: pack.id, label: pack.name })), ...(choice.packs ?? []).map(pack => ({ value: pack.id, label: pack.name }))] }), _jsx(IconPackImport, { onError: message => notify(message, true), onParsed: (pack, warnings, bytes) => setPackReview({ pack, warnings, bytes }) })] }), choice.packs?.length ? _jsx("div", { className: "bt-inline wb-pack-list", "aria-label": t('wb.appearance.icons.imported'), children: choice.packs.map(pack => _jsx(Button, { size: "sm", tone: "ghost", onClick: () => void update({ ...choice, packs: choice.packs.filter(item => item.id !== pack.id), ...(choice.icons === pack.id ? { icons: undefined } : {}) }).then(() => notify(t('wb.appearance.icons.removed', { name: pack.name }))), children: t('wb.appearance.icons.remove', { name: pack.name }) }, pack.id)) }) : null, packReview && _jsx(IconPackReview, { ...packReview, replaces: !!choice.packs?.some(item => item.id === packReview.pack.id), onCancel: () => setPackReview(null), onInstall: () => { const { pack } = packReview; setPackReview(null); void update({ ...choice, packs: [...(choice.packs ?? []).filter(item => item.id !== pack.id), pack], icons: pack.id }).then(() => notify(t('wb.appearance.icons.installed', { name: pack.name }))); } })] }) }) : null, _jsx(Row, { label: t('wb.appearance.fonts'), hint: t('wb.appearance.fontsHint'), keywords: "typeface typography family font google fonts post text heading numbers code interface window dialogs", wide: true, children: _jsx(FontRoles, { font: effective.font, onRole: setRole, compact: true }) }), _jsx(Row, { label: t('wb.appearance.textSize'), hint: t('wb.appearance.textSizeHint'), keywords: "font scale bigger smaller zoom", children: _jsx(Slider, { label: t('wb.appearance.textSize'), min: 85, max: 120, value: Math.round(effective.font.scale * 100), format: v => formatPercent(v), onChange: v => void override({ font: { scale: v / 100 } }, false), onCommit: v => void override({ font: { scale: v / 100 } }) }) }), _jsx(Row, { label: t('wb.appearance.bold'), hint: t('wb.appearance.boldHint'), keywords: "font weight heavy", children: _jsx(Segmented, { label: t('wb.appearance.bold'), size: "sm", value: effective.font.bold >= 750 ? 800 : effective.font.bold >= 675 ? 700 : 600, onChange: bold => void override({ font: { bold } }), options: [{ value: 600, label: t('wb.appearance.bold.medium') }, { value: 700, label: t('wb.appearance.bold.bold') }, { value: 800, label: t('wb.appearance.bold.heavy') }] }) }), _jsx(Row, { label: t('wb.appearance.corners'), hint: t('wb.appearance.cornersHint'), keywords: "radius rounded square shape", children: _jsx(Segmented, { label: t('wb.appearance.corners'), size: "sm", value: cornerValue, onChange: value => { const corner = CORNERS.find(item => item.value === value); void override({ shape: { radius: corner.radius, button: corner.button } }); }, options: CORNERS.map(corner => ({ value: corner.value, label: t(corner.label) })) }) }), _jsx(Row, { label: t('wb.appearance.avatars'), keywords: "profile picture shape", children: _jsx(Segmented, { label: t('wb.appearance.avatars'), size: "sm", value: effective.shape.avatar, onChange: avatar => void override({ shape: { avatar } }), options: [{ value: 'circle', label: t('wb.appearance.avatars.circle') }, { value: 'rounded', label: t('wb.appearance.avatars.rounded') }, { value: 'square', label: t('wb.appearance.corners.square') }] }) }), _jsx(Row, { label: t('wb.appearance.density'), hint: t('wb.appearance.densityHint'), keywords: "compact spacing padding", children: _jsx(Segmented, { label: t('wb.appearance.density'), size: "sm", value: effective.layout.density, onChange: density => void override({ layout: { density } }), options: [{ value: 'compact', label: t('wb.appearance.density.compact') }, { value: 'default', label: t('wb.appearance.density.default') }, { value: 'comfortable', label: t('wb.appearance.density.roomy') }] }) }), _jsx(Row, { label: t('wb.appearance.timeline'), hint: t('wb.appearance.timelineHint'), keywords: "cards layout frame", children: _jsx(Segmented, { label: t('wb.appearance.timelineStyle'), size: "sm", value: timeline, onChange: value => void override({ layout: { cards: value === 'cards', frame: value === 'framed' } }), options: [{ value: 'flat', label: t('wb.appearance.timeline.flat') }, { value: 'framed', label: t('wb.appearance.timeline.framed') }, { value: 'cards', label: t('wb.appearance.timeline.cards') }] }) }), _jsx(Row, { label: t('wb.appearance.timelineWidth'), keywords: "column layout wide narrow", children: _jsx(Slider, { label: t('wb.appearance.timelineWidth'), min: 520, max: 820, step: 10, value: effective.layout.column, format: v => t('wb.appearance.px', { value: v }), onChange: column => void override({ layout: { column } }, false), onCommit: column => void override({ layout: { column } }) }) }), _jsx(Row, { label: t('wb.appearance.sidebarWidth'), keywords: "column layout trends", children: _jsx(Slider, { label: t('wb.appearance.sidebarWidth'), min: 290, max: 400, step: 10, value: effective.layout.sidebar, format: v => t('wb.appearance.px', { value: v }), onChange: sidebar => void override({ layout: { sidebar } }, false), onCommit: sidebar => void override({ layout: { sidebar } }) }) }), _jsx(Row, { label: t('wb.appearance.background'), hint: t('wb.appearance.backgroundHint'), keywords: "wallpaper pattern image photo canvas", children: _jsxs("div", { className: "bt-inline", children: [_jsx(Select, { label: t('wb.appearance.background'), value: backgroundChoice(effective.background), options: backgrounds(), width: 170, onChange: kind => { if (kind === 'image')
|
|
171
171
|
document.getElementById('bt-bg-file')?.click();
|
|
172
172
|
else
|
|
173
|
-
void override({ background: backgroundFor(kind, effective) }); } }), _jsx("input", { id: "bt-bg-file", type: "file", hidden: true, accept: "image/png,image/jpeg,image/webp,image/gif,image/avif", onChange: event => { void pickImage(event.target.files?.[0]); event.target.value = ''; } })] }) }), effective.background.kind === 'image' && _jsx(Row, { label: t('wb.appearance.dim'), hint: t('wb.appearance.dimHint'), keywords: "background image", children: _jsx(Slider, { label: t('wb.appearance.dim'), min: 35, max: 95, value: Math.round(effective.background.dim * 100), format: v => formatPercent(v), onChange: v => void override({ background: { ...effective.background, dim: v / 100 } }, false), onCommit: v => void override({ background: { ...effective.background, dim: v / 100 } }) }) }), _jsx(Row, { label: t('wb.appearance.motion'), hint: t('wb.appearance.motionHint'), keywords: "animation transition", children: _jsx(Segmented, { label: t('wb.appearance.motion'), size: "sm", value: effective.effects.motion === 0 ? 0 : effective.effects.motion < 200 ? 140 : 260, onChange: motion => void override({ effects: { motion } }), options: [{ value: 0, label: t('wb.common.off') }, { value: 140, label: t('wb.appearance.motion.subtle') }, { value: 260, label: t('wb.appearance.motion.lively') }] }) }), _jsx(Row, { label: t('wb.appearance.hover'), hint: t('wb.appearance.hoverHint'), keywords: "effect highlight lift glow", children: _jsx(Segmented, { label: t('wb.appearance.hoverEffect'), size: "sm", value: effective.effects.hover, onChange: hover => void override({ effects: { hover } }), options: [{ value: 'none', label: t('wb.appearance.hover.none') }, { value: 'tint', label: t('wb.appearance.hover.tint') }, { value: 'lift', label: t('wb.appearance.hover.lift') }, { value: 'glow', label: t('wb.appearance.bg.glow') }] }) }), _jsx(Row, { label: t('wb.appearance.crt'), hint: t('wb.appearance.crtHint'), keywords: "retro terminal effect", children: _jsx(Toggle, { label: t('wb.appearance.crt'), checked: effective.effects.crt, onChange: crt => void override({ effects: { crt } }) }) }), _jsx(ThemeCssEditor, { css: effective.css, onApply: text => { void override({ css: { text } }); notify(t('wb.appearance.cssApplied', { name: base.name })); } }, choice.preset)] }), _jsxs(Section, { title: t('wb.appearance.scopes'), id: "theme-scopes", hint: searching ? undefined : t('wb.appearance.scopesHint'), children: [_jsx(Row, { label: t('wb.appearance.chrome'), hint: t('wb.appearance.chromeHint'), keywords: "chrome window tabs titlebar panel colors own style", children: _jsx(Toggle, { label: t('wb.appearance.chrome'), checked: scopes.chrome === 'theme', onChange: on => void setScopes({ ...scopes, chrome: on ? 'theme' : 'own' }).catch(error => notify(getError(error), true)) }) }), _jsx(AccountThemes, { notify: notify }), _jsx(PaneThemes, { notify: notify })] }), _jsx(DownloadedFonts, { notify: notify }), (panelSite().appearance?.extra ?? []).map((Extra, index) => _jsx(Extra, { notify: notify, openSection: id => window.dispatchEvent(new CustomEvent('bt-panel-section', { detail: id })) }, index)), _jsx(CustomCssEditor, { notify: notify })] });
|
|
173
|
+
void override({ background: backgroundFor(kind, effective) }); } }), _jsx("input", { id: "bt-bg-file", type: "file", hidden: true, accept: "image/png,image/jpeg,image/webp,image/gif,image/avif", onChange: event => { void pickImage(event.target.files?.[0]); event.target.value = ''; } })] }) }), effective.background.kind === 'image' && _jsx(Row, { label: t('wb.appearance.dim'), hint: t('wb.appearance.dimHint'), keywords: "background image", children: _jsx(Slider, { label: t('wb.appearance.dim'), min: 35, max: 95, value: Math.round(effective.background.dim * 100), format: v => formatPercent(v), onChange: v => void override({ background: { ...effective.background, dim: v / 100 } }, false), onCommit: v => void override({ background: { ...effective.background, dim: v / 100 } }) }) }), _jsx(Row, { label: t('wb.appearance.motion'), hint: t('wb.appearance.motionHint'), keywords: "animation transition", children: _jsx(Segmented, { label: t('wb.appearance.motion'), size: "sm", value: effective.effects.motion === 0 ? 0 : effective.effects.motion < 200 ? 140 : 260, onChange: motion => void override({ effects: { motion } }), options: [{ value: 0, label: t('wb.common.off') }, { value: 140, label: t('wb.appearance.motion.subtle') }, { value: 260, label: t('wb.appearance.motion.lively') }] }) }), _jsx(Row, { label: t('wb.appearance.hover'), hint: t('wb.appearance.hoverHint'), keywords: "effect highlight lift glow", children: _jsx(Segmented, { label: t('wb.appearance.hoverEffect'), size: "sm", value: effective.effects.hover, onChange: hover => void override({ effects: { hover } }), options: [{ value: 'none', label: t('wb.appearance.hover.none') }, { value: 'tint', label: t('wb.appearance.hover.tint') }, { value: 'lift', label: t('wb.appearance.hover.lift') }, { value: 'glow', label: t('wb.appearance.bg.glow') }] }) }), _jsx(Row, { label: t('wb.appearance.crt'), hint: t('wb.appearance.crtHint'), keywords: "retro terminal effect", children: _jsx(Toggle, { label: t('wb.appearance.crt'), checked: effective.effects.crt, onChange: crt => void override({ effects: { crt } }) }) }), _jsx(ThemeCssEditor, { css: effective.css, onApply: text => { void override({ css: { text } }); notify(t('wb.appearance.cssApplied', { name: base.name })); } }, choice.preset)] }), _jsx(DoctorReport, { theme: effective }), _jsxs(Section, { title: t('wb.appearance.scopes'), id: "theme-scopes", hint: searching ? undefined : t('wb.appearance.scopesHint'), children: [_jsx(Row, { label: t('wb.appearance.chrome'), hint: t('wb.appearance.chromeHint'), keywords: "chrome window tabs titlebar panel colors own style", children: _jsx(Toggle, { label: t('wb.appearance.chrome'), checked: scopes.chrome === 'theme', onChange: on => void setScopes({ ...scopes, chrome: on ? 'theme' : 'own' }).catch(error => notify(getError(error), true)) }) }), _jsx(AccountThemes, { notify: notify }), _jsx(PaneThemes, { notify: notify })] }), _jsx(DownloadedFonts, { notify: notify }), (panelSite().appearance?.extra ?? []).map((Extra, index) => _jsx(Extra, { notify: notify, openSection: id => window.dispatchEvent(new CustomEvent('bt-panel-section', { detail: id })) }, index)), _jsx(CustomCssEditor, { notify: notify })] });
|
|
174
174
|
}
|
|
175
175
|
/** Theme choices for a scope row: follow (the level above) plus every theme with its swatch. */
|
|
176
|
+
/** The theme doctor's full report on the theme as it is now: every finding, where it is, what it found, how to fix it. Advice only. */
|
|
177
|
+
function DoctorReport({ theme }) {
|
|
178
|
+
const t = useT();
|
|
179
|
+
const advice = themeDoctor(theme);
|
|
180
|
+
const words = (item, part) => t(`wb.doctor.${item.key}.${part}`, item.params);
|
|
181
|
+
return _jsx(Section, { title: t('wb.doctor.title'), id: "theme-doctor", hint: t('wb.doctor.hint'), action: _jsx(Badge, { tone: advice.length ? undefined : 'ok', children: t('wb.doctor.count', { count: advice.length }) }), children: advice.length === 0
|
|
182
|
+
? _jsx("p", { className: "btp-hint", children: t('wb.doctor.empty') })
|
|
183
|
+
: _jsx("ul", { className: "bt-doctor", "aria-label": t('wb.doctor.title'), children: advice.map((item, index) => _jsxs("li", { className: `bt-doctor-item is-${item.kind}`, children: [_jsxs("span", { className: "bt-doctor-head", children: [_jsx(Badge, { children: t(`wb.doctor.kind.${item.kind}`) }), _jsx("strong", { children: words(item, 'found') })] }), _jsxs("small", { className: "bt-doctor-line", children: [t('wb.doctor.where'), ": ", _jsx("code", { children: item.where })] }), _jsxs("small", { className: "bt-doctor-line", children: [t('wb.doctor.fix'), ": ", words(item, 'fix')] })] }, `${item.id}:${item.where}:${index}`)) }) });
|
|
184
|
+
}
|
|
176
185
|
function scopeOptions(follow) {
|
|
177
186
|
// Called during render of components that use useT(), so the labels follow the language.
|
|
178
187
|
return [{ value: '', label: follow }, ...panelThemes().store.allThemes().map(theme => ({ value: theme.id, label: theme.name, icon: _jsx(ThemeDots, { theme: theme }) }))];
|
|
@@ -249,7 +258,7 @@ function ImportPanel({ onImport, onCancel }) {
|
|
|
249
258
|
}
|
|
250
259
|
// The theme doctor: advice only (never a block); contrast is already in the warnings.
|
|
251
260
|
const hints = themeDoctor(parsed.theme).filter(advice => advice.kind !== 'contrast');
|
|
252
|
-
onImport(parsed.theme, hints.length ? [...parsed.warnings, t('wb.appearance.doctorHints', { count: hints.length, hint: hints[0].
|
|
261
|
+
onImport(parsed.theme, hints.length ? [...parsed.warnings, t('wb.appearance.doctorHints', { count: hints.length, hint: t(`wb.doctor.${hints[0].key}.found`, hints[0].params) })] : parsed.warnings);
|
|
253
262
|
};
|
|
254
263
|
return _jsxs("div", { className: "bt-import", children: [_jsxs("div", { className: "bt-import-drop", onDragOver: event => event.preventDefault(), onDrop: event => { event.preventDefault(); void event.dataTransfer.files[0]?.text().then(run); }, children: [_jsx("strong", { children: t('wb.appearance.importTitle') }), _jsx("p", { children: t('wb.appearance.importText') }), _jsxs("div", { className: "bt-inline", children: [_jsx(Button, { size: "sm", tone: "primary", onClick: () => file.current?.click(), children: t('wb.common.chooseFile') }), _jsx(Button, { size: "sm", tone: "ghost", onClick: onCancel, children: t('wb.common.cancel') })] }), _jsx("input", { ref: file, type: "file", hidden: true, accept: "application/json,.json", onChange: event => { void event.target.files?.[0]?.text().then(run); event.target.value = ''; } })] }), _jsxs("div", { className: "bt-code is-compact", children: [_jsx("textarea", { "aria-label": t('wb.appearance.themeJson'), spellCheck: false, placeholder: `${t('wb.appearance.pasteJson')}\n{ "name": "My theme", "colors": { … } }`, value: text, onChange: event => { setText(event.target.value); setError(''); } }), _jsxs("div", { className: "bt-code-bar", children: [error ? _jsx("small", { className: "is-error", role: "alert", children: error }) : _jsx("small", { children: t('wb.appearance.importSafe') }), _jsx(Button, { size: "sm", disabled: !text.trim(), onClick: () => run(text), children: t('wb.appearance.importJson') })] })] })] });
|
|
255
264
|
}
|