synabun 2.0.5 → 2.0.7
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/CHANGELOG.md +40 -0
- package/README.md +2 -1
- package/THIRD-PARTY-LICENSES.md +13 -0
- package/docs/whatsapp.md +151 -16
- package/hooks/claude-code/shared.mjs +6 -3
- package/lib/child-path.js +303 -0
- package/lib/claude-hooks.js +2 -1
- package/lib/data-home-migration.js +2 -0
- package/lib/desktop-app.js +265 -0
- package/lib/external-tools.js +7 -3
- package/lib/log-redact.js +102 -0
- package/lib/packaged-runtime.js +138 -11
- package/mcp-server/dist/http.d.ts +3 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +15 -4
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +9 -6
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/budget-judgments.js +153 -0
- package/mcp-server/dist/services/budget-judgments.js.map +1 -0
- package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +41 -4
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +11 -0
- package/mcp-server/dist/services/identity.d.ts.map +1 -1
- package/mcp-server/dist/services/identity.js +24 -0
- package/mcp-server/dist/services/identity.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +13 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +34 -2
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +4 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -7
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +34 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
- package/mcp-server/dist/tui.js +2 -1
- package/mcp-server/dist/tui.js.map +1 -1
- package/mcp-server/dist/version.d.ts +5 -0
- package/mcp-server/dist/version.d.ts.map +1 -0
- package/mcp-server/dist/version.js +21 -0
- package/mcp-server/dist/version.js.map +1 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/run.mjs +15 -9
- package/mcp-server/src/http.ts +18 -3
- package/mcp-server/src/index.ts +9 -6
- package/mcp-server/src/services/budget-judgments.ts +183 -0
- package/mcp-server/src/services/codex-config-heal.ts +35 -4
- package/mcp-server/src/services/identity.ts +27 -0
- package/mcp-server/src/services/neural-interface.ts +34 -2
- package/mcp-server/src/services/typesafe-config.ts +4 -1
- package/mcp-server/src/tools/agents.ts +33 -14
- package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
- package/mcp-server/src/tui.ts +2 -1
- package/mcp-server/src/version.ts +22 -0
- package/neural-interface/i18n/en.json +627 -57
- package/neural-interface/i18n/pt-BR.json +626 -56
- package/neural-interface/lib/account-usage.js +163 -0
- package/neural-interface/lib/assistant-api.js +255 -52
- package/neural-interface/lib/assistant-brains/claude.js +15 -2
- package/neural-interface/lib/assistant-brains/codex.js +3 -0
- package/neural-interface/lib/assistant-brains/opencode.js +17 -4
- package/neural-interface/lib/assistant-budget-planner.js +390 -0
- package/neural-interface/lib/assistant-budget-split.js +408 -0
- package/neural-interface/lib/assistant-budget.js +94 -95
- package/neural-interface/lib/assistant-config.js +62 -14
- package/neural-interface/lib/assistant-dispatch.js +432 -125
- package/neural-interface/lib/assistant-jev.js +26 -3
- package/neural-interface/lib/assistant-persona.js +68 -11
- package/neural-interface/lib/assistant-plan-permissions.js +8 -1
- package/neural-interface/lib/assistant-route-accounts.js +14 -0
- package/neural-interface/lib/assistant-route-gate.js +19 -3
- package/neural-interface/lib/assistant-router.js +373 -42
- package/neural-interface/lib/assistant-runtime.js +307 -47
- package/neural-interface/lib/assistant-user-writer.js +4 -2
- package/neural-interface/lib/augmented-path.js +317 -53
- package/neural-interface/lib/claude-accounts.js +342 -31
- package/neural-interface/lib/claude-agent-bridge.js +90 -3
- package/neural-interface/lib/claude-executable.js +6 -1
- package/neural-interface/lib/claude-model-catalog.js +3 -3
- package/neural-interface/lib/claude-panel-auth.js +221 -0
- package/neural-interface/lib/claude-profile-api-key.js +62 -0
- package/neural-interface/lib/cli-install-api.js +137 -0
- package/neural-interface/lib/cli-install.js +797 -0
- package/neural-interface/lib/cli-path.js +130 -0
- package/neural-interface/lib/cli-resolver.js +744 -0
- package/neural-interface/lib/cli-update-planner.js +143 -7
- package/neural-interface/lib/codex-account-auth.js +178 -0
- package/neural-interface/lib/codex-accounts.js +489 -0
- package/neural-interface/lib/codex-capabilities.js +2 -1
- package/neural-interface/lib/codex-compat-rpc.js +8 -2
- package/neural-interface/lib/codex-mcp-approvals.js +5 -1
- package/neural-interface/lib/codex-panel-auth.js +62 -0
- package/neural-interface/lib/codex-session-settings.js +1 -1
- package/neural-interface/lib/codex-settings-schema.js +120 -0
- package/neural-interface/lib/desktop/config.js +3 -0
- package/neural-interface/lib/git-bash/bzip2.js +339 -0
- package/neural-interface/lib/git-bash/claude-settings.js +73 -0
- package/neural-interface/lib/git-bash/extract-worker.js +34 -0
- package/neural-interface/lib/git-bash/install.js +529 -0
- package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
- package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
- package/neural-interface/lib/git-bash/manager.js +268 -0
- package/neural-interface/lib/git-bash/pins.js +206 -0
- package/neural-interface/lib/git-bash/pins.json +34 -0
- package/neural-interface/lib/git-bash/resolver.js +194 -0
- package/neural-interface/lib/git-read.js +34 -2
- package/neural-interface/lib/install-details.js +145 -0
- package/neural-interface/lib/mcp-installer.js +14 -2
- package/neural-interface/lib/native-loop-providers.js +16 -3
- package/neural-interface/lib/native-loop-runtime.js +9 -4
- package/neural-interface/lib/os-path.js +319 -0
- package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
- package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
- package/neural-interface/lib/pty-host/core.js +2 -1
- package/neural-interface/lib/pty-host/manager.js +27 -2
- package/neural-interface/lib/remote-policy.js +20 -11
- package/neural-interface/lib/session-title-generator.js +4 -3
- package/neural-interface/lib/system-about.js +340 -0
- package/neural-interface/lib/system-activity.js +66 -0
- package/neural-interface/lib/terminal-env.js +11 -3
- package/neural-interface/lib/terminal-path.js +55 -0
- package/neural-interface/lib/virtual-socket.js +12 -1
- package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
- package/neural-interface/lib/whatsapp/bridge.js +78 -34
- package/neural-interface/lib/whatsapp/commands.js +13 -14
- package/neural-interface/lib/whatsapp/config.js +134 -20
- package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
- package/neural-interface/lib/whatsapp/host-core.js +168 -35
- package/neural-interface/lib/whatsapp/inbound.js +5 -3
- package/neural-interface/lib/whatsapp/local-file.js +524 -0
- package/neural-interface/lib/whatsapp/manager.js +92 -32
- package/neural-interface/lib/whatsapp/paths.js +11 -1
- package/neural-interface/lib/whatsapp/protocol.js +65 -5
- package/neural-interface/lib/whatsapp/redact.js +63 -0
- package/neural-interface/lib/whatsapp/service.js +343 -91
- package/neural-interface/lib/whatsapp/transcription.js +463 -0
- package/neural-interface/package-lock.json +2 -2
- package/neural-interface/package.json +1 -1
- package/neural-interface/public/index.html +2 -0
- package/neural-interface/public/index2d.html +2 -0
- package/neural-interface/public/offline.html +16 -1
- package/neural-interface/public/onboarding.html +96 -2
- package/neural-interface/public/shared/account-i18n.js +7 -0
- package/neural-interface/public/shared/account-menu.js +539 -0
- package/neural-interface/public/shared/account-usage.js +19 -0
- package/neural-interface/public/shared/account-view.js +335 -0
- package/neural-interface/public/shared/agent-runtime-options.js +28 -8
- package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
- package/neural-interface/public/shared/assistant/asst-api.js +32 -2
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +124 -25
- package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
- package/neural-interface/public/shared/assistant/asst-mascot.js +126 -7
- package/neural-interface/public/shared/assistant/asst-menu.js +15 -3
- package/neural-interface/public/shared/assistant/asst-panel.js +48 -14
- package/neural-interface/public/shared/assistant/asst-render.js +38 -7
- package/neural-interface/public/shared/assistant/asst-route.js +72 -14
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +45 -6
- package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
- package/neural-interface/public/shared/assistant/asst-state.js +1 -1
- package/neural-interface/public/shared/assistant/asst-styles.js +99 -17
- package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
- package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
- package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +48 -107
- package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +128 -107
- package/neural-interface/public/shared/cdx/cdx-tabs.js +261 -664
- package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
- package/neural-interface/public/shared/claude-account-view.js +3 -0
- package/neural-interface/public/shared/cli-install-view.js +392 -0
- package/neural-interface/public/shared/cli-status.js +282 -22
- package/neural-interface/public/shared/cp/cp-events.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +46 -0
- package/neural-interface/public/shared/desktop-bridge.js +39 -0
- package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
- package/neural-interface/public/shared/desktop-titlebar.js +153 -0
- package/neural-interface/public/shared/desktop-update-view.js +462 -0
- package/neural-interface/public/shared/editor/editor-panel.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +1 -1
- package/neural-interface/public/shared/git-bash-view.js +145 -0
- package/neural-interface/public/shared/html-shell.js +1 -0
- package/neural-interface/public/shared/i18n.js +19 -1
- package/neural-interface/public/shared/install-details-view.js +86 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +32 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +17 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +118 -0
- package/neural-interface/public/shared/pwa-install-page.js +3 -1
- package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
- package/neural-interface/public/shared/settings/settings-about.js +254 -0
- package/neural-interface/public/shared/settings/settings-ia.js +15 -0
- package/neural-interface/public/shared/showcase-privacy.js +489 -0
- package/neural-interface/public/shared/storage.js +30 -3
- package/neural-interface/public/shared/styles.css +278 -31
- package/neural-interface/public/shared/synabun-mascot.js +48 -2
- package/neural-interface/public/shared/ui-claude-panel.js +212 -213
- package/neural-interface/public/shared/ui-cli-banner.js +355 -0
- package/neural-interface/public/shared/ui-git-bash.js +124 -0
- package/neural-interface/public/shared/ui-judgments.js +10 -1
- package/neural-interface/public/shared/ui-loading.js +30 -6
- package/neural-interface/public/shared/ui-menubar.js +13 -3
- package/neural-interface/public/shared/ui-navbar.js +3 -1
- package/neural-interface/public/shared/ui-provider-auth.js +104 -0
- package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
- package/neural-interface/public/shared/ui-sessions.js +9 -3
- package/neural-interface/public/shared/ui-settings.js +55 -3
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
- package/neural-interface/public/shared/ui-sync.js +6 -0
- package/neural-interface/public/shared/ui-terminal.js +17 -0
- package/neural-interface/public/shared/ui-tool-updates.js +5 -0
- package/neural-interface/public/shared/ui-update.js +594 -5
- package/neural-interface/public/shared/ui-whatsapp.js +21 -6
- package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
- package/neural-interface/public/shared/worker-end.js +2 -0
- package/neural-interface/public/sw.js +1 -1
- package/neural-interface/public/variant/2d/main.js +10 -1
- package/neural-interface/public/variant/3d/main.js +10 -1
- package/neural-interface/server.js +1219 -608
- package/package.json +4 -1
- package/setup.js +9 -8
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — the desktop app's title bar: what it decides
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// Inside the SynaBun desktop app (packaging/desktop) the window has no native
|
|
6
|
+
// title bar: the page's own top bar (#title-bar) is it, with the system's
|
|
7
|
+
// caption buttons (Windows, Linux) or traffic lights (macOS) drawn over one of
|
|
8
|
+
// its ends. desktop-titlebar.js does the page's part; this file decides, from
|
|
9
|
+
// plain values: the colours the caption buttons get, whether the bar makes room
|
|
10
|
+
// for them now, where they are when the page has no Window Controls Overlay API,
|
|
11
|
+
// and whether something drawn over the bar has to get its clicks.
|
|
12
|
+
//
|
|
13
|
+
// No imports and no DOM: `node --test` loads this file as it is
|
|
14
|
+
// (tests/desktop-titlebar-view.test.mjs).
|
|
15
|
+
|
|
16
|
+
/** 'rgb(r, g, b)', 'rgba(r, g, b, a)', 'rgb(r g b / a)', '#rgb', '#rrggbb', '#rrggbbaa' or 'transparent' → { r, g, b, a }, or null. */
|
|
17
|
+
export function parseCssColor(text) {
|
|
18
|
+
const value = String(text ?? '').trim().toLowerCase();
|
|
19
|
+
if (!value) return null;
|
|
20
|
+
if (value === 'transparent') return { r: 0, g: 0, b: 0, a: 0 };
|
|
21
|
+
const hex = /^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/.exec(value);
|
|
22
|
+
if (hex) {
|
|
23
|
+
let digits = hex[1];
|
|
24
|
+
if (digits.length <= 4) digits = digits.split('').map(ch => ch + ch).join('');
|
|
25
|
+
const byte = (at) => parseInt(digits.slice(at, at + 2), 16);
|
|
26
|
+
return { r: byte(0), g: byte(2), b: byte(4), a: digits.length === 8 ? byte(6) / 255 : 1 };
|
|
27
|
+
}
|
|
28
|
+
const fn = /^rgba?\(\s*([^)]*)\)$/.exec(value);
|
|
29
|
+
if (!fn) return null;
|
|
30
|
+
const parts = fn[1].split(/\s*[,/]\s*|\s+/).filter(Boolean);
|
|
31
|
+
if (parts.length < 3) return null;
|
|
32
|
+
const channel = (part) => (part.endsWith('%') ? (parseFloat(part) / 100) * 255 : parseFloat(part));
|
|
33
|
+
const [r, g, b] = parts.slice(0, 3).map(channel);
|
|
34
|
+
let a = 1;
|
|
35
|
+
if (parts[3] !== undefined) a = parts[3].endsWith('%') ? parseFloat(parts[3]) / 100 : parseFloat(parts[3]);
|
|
36
|
+
if (![r, g, b, a].every(Number.isFinite)) return null;
|
|
37
|
+
const clamp = (n, max) => Math.max(0, Math.min(max, n));
|
|
38
|
+
return { r: clamp(r, 255), g: clamp(g, 255), b: clamp(b, 255), a: clamp(a, 1) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** An { r, g, b } colour as #rrggbb. */
|
|
42
|
+
export function toHex({ r, g, b }) {
|
|
43
|
+
return `#${[r, g, b].map(part => Math.max(0, Math.min(255, Math.round(part))).toString(16).padStart(2, '0')).join('')}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Colours stacked on top of each other (`layers`, top first, any alpha) over an
|
|
48
|
+
* opaque `base`: the one opaque colour the eye sees. Layers that cannot be read
|
|
49
|
+
* are skipped; so is everything under the first opaque one.
|
|
50
|
+
*/
|
|
51
|
+
export function flatten(layers, base) {
|
|
52
|
+
const stack = [];
|
|
53
|
+
for (const layer of layers || []) {
|
|
54
|
+
const color = typeof layer === 'string' ? parseCssColor(layer) : layer;
|
|
55
|
+
if (!color) continue;
|
|
56
|
+
stack.push(color);
|
|
57
|
+
if (color.a >= 1) break;
|
|
58
|
+
}
|
|
59
|
+
let out = (typeof base === 'string' ? parseCssColor(base) : base) || { r: 5, g: 5, b: 5, a: 1 };
|
|
60
|
+
for (const color of stack.reverse()) {
|
|
61
|
+
out = { r: color.r * color.a + out.r * (1 - color.a), g: color.g * color.a + out.g * (1 - color.a), b: color.b * color.a + out.b * (1 - color.a), a: 1 };
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The caption buttons' colours (Windows, Linux) for the bar as it is drawn now:
|
|
68
|
+
* `bar` the bar's own background, `under` the backgrounds below it where the
|
|
69
|
+
* buttons are (top first), `base` the page's background, `text` the bar's text
|
|
70
|
+
* tone (DESIGN.md nav-bar: text-primary). Two opaque #rrggbb colours: the
|
|
71
|
+
* buttons are drawn over the page, so a see-through colour would show what is
|
|
72
|
+
* under them through Windows' hover fill.
|
|
73
|
+
*/
|
|
74
|
+
export function overlayColorsFor({ bar, under = [], base = '#050505', text = 'rgba(255, 255, 255, 0.7)' } = {}) {
|
|
75
|
+
const background = flatten([bar, ...under], base);
|
|
76
|
+
const symbol = flatten([text], background);
|
|
77
|
+
return { color: toHex(background), symbolColor: toHex(symbol) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Whether the bar makes room for the window's own controls now: while the
|
|
82
|
+
* overlay is shown (Electron's WCO API says so; without that API, while the
|
|
83
|
+
* app says it has one) and the window is not full screen, where the system
|
|
84
|
+
* hides its buttons and the bar takes its usual layout.
|
|
85
|
+
*/
|
|
86
|
+
export function titleBarActive({ overlayVisible = null, hasOverlayApi = true, fullScreen = false } = {}) {
|
|
87
|
+
if (fullScreen) return false;
|
|
88
|
+
return hasOverlayApi ? overlayVisible === true : true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Where the window's controls are, for a page without the WCO API (its CSS
|
|
93
|
+
* variables then never resolve): the title bar area the page may use, as the
|
|
94
|
+
* page's CSS variables --titlebar-area-x / -width / -height. Windows' and
|
|
95
|
+
* Linux's caption buttons are about 46 px each, three of them; macOS's traffic
|
|
96
|
+
* lights end about 70 px in, plus the same inset after them.
|
|
97
|
+
*/
|
|
98
|
+
export function fallbackTitlebarArea(facts, viewportWidth) {
|
|
99
|
+
const width = Math.max(0, Number(viewportWidth) || 0);
|
|
100
|
+
const height = Number(facts?.height) || 48;
|
|
101
|
+
if (facts?.controls === 'left') {
|
|
102
|
+
const x = 86;
|
|
103
|
+
return { x, width: Math.max(0, width - x), height };
|
|
104
|
+
}
|
|
105
|
+
const buttons = 138;
|
|
106
|
+
return { x: 0, width: Math.max(0, width - buttons), height };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Whether something is drawn over the bar that has to get its clicks (a
|
|
111
|
+
* dialog, the tour, a menu's catch-all layer): the bar's empty parts drag the
|
|
112
|
+
* window, and the system takes a drag region's clicks before the page sees
|
|
113
|
+
* them, whatever is painted there. `samples` are what is topmost at points along
|
|
114
|
+
* the bar: { inBar } for the bar or anything in it, { page: true } for the page's
|
|
115
|
+
* own root (nothing there), anything else is something over the bar.
|
|
116
|
+
*/
|
|
117
|
+
export function barCovered(samples) {
|
|
118
|
+
return (samples || []).some(sample => sample && !sample.inBar && !sample.page);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** A stable key for a pair of colours: unchanged colours are never sent twice. */
|
|
122
|
+
export const colorsKey = (colors) => (colors ? `${colors.color}/${colors.symbolColor}` : '');
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — the desktop app's title bar (page side)
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// The SynaBun desktop app's main window has no native title bar
|
|
6
|
+
// (packaging/desktop/src/titlebar.mjs): the page's own top bar, #title-bar,
|
|
7
|
+
// is the window's title bar, the way Discord's is. This makes it one:
|
|
8
|
+
//
|
|
9
|
+
// layout body.wco-active (the Window Controls Overlay rules in styles.css,
|
|
10
|
+
// the installed PWA's too) while the system's buttons are over the
|
|
11
|
+
// bar: the bar is as tall as they are and leaves their corner
|
|
12
|
+
// free; off in full screen, where the system hides them
|
|
13
|
+
// colours the caption buttons (Windows, Linux) take the colours of the bar
|
|
14
|
+
// under them, again whenever those change (a skin, the Memory
|
|
15
|
+
// Explorer making the bar opaque, a stylesheet that loads late)
|
|
16
|
+
// menu Windows and Linux: a button at the bar's left opens the app's
|
|
17
|
+
// menu, the one the hidden menu bar had (its shortcuts still work)
|
|
18
|
+
// clicks the bar's empty parts drag the window (the system double-clicks
|
|
19
|
+
// them too); while something is drawn over the bar (a dialog, the
|
|
20
|
+
// tour), that thing gets the clicks: body.desktop-titlebar-covered
|
|
21
|
+
//
|
|
22
|
+
// In a browser and in the installed PWA nothing here runs: initDesktopTitleBar()
|
|
23
|
+
// returns null, and the PWA's own overlay handling (ui-navbar.js) is untouched.
|
|
24
|
+
// What it decides is desktop-titlebar-view.js (DOM-free, tested under node); a
|
|
25
|
+
// page without the navbar uses desktop-titlebar-page.js.
|
|
26
|
+
|
|
27
|
+
import { desktopWindow } from './desktop-bridge.js';
|
|
28
|
+
import { colorSender, textTone } from './desktop-titlebar-page.js';
|
|
29
|
+
import { barCovered, fallbackTitlebarArea, overlayColorsFor, titleBarActive } from './desktop-titlebar-view.js';
|
|
30
|
+
import { t } from './i18n.js';
|
|
31
|
+
|
|
32
|
+
const MENU_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16"/><path d="M4 12h16"/><path d="M4 17h16"/></svg>';
|
|
33
|
+
const COLOR_DELAY_MS = 120;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The map pages (index.html, index2d.html): #title-bar becomes the window's title bar. Returns the controller, or
|
|
37
|
+
* null outside the desktop app's main window (a browser, the PWA) or on a page without the bar.
|
|
38
|
+
*/
|
|
39
|
+
export function initDesktopTitleBar({ doc = document, view = window } = {}) {
|
|
40
|
+
const api = desktopWindow(view);
|
|
41
|
+
const bar = doc.getElementById('title-bar');
|
|
42
|
+
if (!api || !bar) return null;
|
|
43
|
+
const facts = api.titleBar;
|
|
44
|
+
const html = doc.documentElement;
|
|
45
|
+
html.classList.add('desktop-titlebar', `desktop-titlebar--${facts.platform}`);
|
|
46
|
+
const wco = view.navigator?.windowControlsOverlay || null;
|
|
47
|
+
let state = { maximized: false, fullScreen: false, focused: true };
|
|
48
|
+
const send = colorSender(api, doc);
|
|
49
|
+
|
|
50
|
+
// ── The menu button (Windows, Linux): the app's menu, under the button ──
|
|
51
|
+
let menuButton = null;
|
|
52
|
+
if (facts.appMenu) {
|
|
53
|
+
menuButton = doc.createElement('button');
|
|
54
|
+
menuButton.type = 'button';
|
|
55
|
+
menuButton.id = 'desktop-app-menu-btn';
|
|
56
|
+
menuButton.className = 'bar-icon desktop-app-menu-btn';
|
|
57
|
+
const label = t('desktop.titleBar.appMenu');
|
|
58
|
+
menuButton.setAttribute('aria-label', label);
|
|
59
|
+
menuButton.setAttribute('aria-haspopup', 'menu');
|
|
60
|
+
menuButton.dataset.tooltip = label;
|
|
61
|
+
menuButton.innerHTML = MENU_ICON;
|
|
62
|
+
menuButton.addEventListener('click', (event) => {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
const rect = menuButton.getBoundingClientRect();
|
|
65
|
+
Promise.resolve(api.showMenu({ x: Math.max(0, rect.left), y: Math.max(0, rect.bottom) })).catch(() => {});
|
|
66
|
+
});
|
|
67
|
+
const left = bar.querySelector('.bar-left');
|
|
68
|
+
(left || bar).prepend(menuButton);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Covered: something over the bar takes its clicks, so the bar stops dragging the window ──
|
|
72
|
+
let coverFrame = 0;
|
|
73
|
+
const checkCover = () => {
|
|
74
|
+
coverFrame = 0;
|
|
75
|
+
if (!doc.body.classList.contains('wco-active')) { doc.body.classList.remove('desktop-titlebar-covered'); return; }
|
|
76
|
+
const rect = bar.getBoundingClientRect();
|
|
77
|
+
const area = wco && wco.visible ? wco.getTitlebarAreaRect() : null;
|
|
78
|
+
const left = Math.max(rect.left, area ? area.x : rect.left) + 2;
|
|
79
|
+
const right = Math.min(rect.right, area ? area.x + area.width : rect.right) - 2;
|
|
80
|
+
if (!(right > left) || !(rect.height > 0)) { doc.body.classList.remove('desktop-titlebar-covered'); return; }
|
|
81
|
+
const y = Math.round(rect.top + rect.height / 2);
|
|
82
|
+
const samples = [];
|
|
83
|
+
for (let i = 0; i <= 8; i++) {
|
|
84
|
+
const el = doc.elementFromPoint(Math.round(left + ((right - left) * i) / 8), y);
|
|
85
|
+
samples.push({ inBar: !!el && (el === bar || bar.contains(el)), page: !el || el === html || el === doc.body });
|
|
86
|
+
}
|
|
87
|
+
doc.body.classList.toggle('desktop-titlebar-covered', barCovered(samples));
|
|
88
|
+
};
|
|
89
|
+
const scheduleCover = () => { if (!coverFrame) coverFrame = view.requestAnimationFrame(checkCover); };
|
|
90
|
+
|
|
91
|
+
// ── Colours: the bar as drawn where the buttons are, over whatever is under it there ──
|
|
92
|
+
let colorTimer = null;
|
|
93
|
+
const computeColors = () => {
|
|
94
|
+
const rect = bar.getBoundingClientRect();
|
|
95
|
+
const x = Math.round(facts.controls === 'left' ? rect.left + 2 : rect.right - 2);
|
|
96
|
+
const y = Math.round(rect.top + rect.height / 2);
|
|
97
|
+
const under = [];
|
|
98
|
+
let below = false;
|
|
99
|
+
for (const el of (typeof doc.elementsFromPoint === 'function' ? doc.elementsFromPoint(x, y) : [])) {
|
|
100
|
+
if (el === bar) { below = true; continue; }
|
|
101
|
+
if (!below || bar.contains(el) || el === html) continue;
|
|
102
|
+
under.push(view.getComputedStyle(el).backgroundColor);
|
|
103
|
+
}
|
|
104
|
+
return overlayColorsFor({
|
|
105
|
+
bar: view.getComputedStyle(bar).backgroundColor,
|
|
106
|
+
under,
|
|
107
|
+
base: view.getComputedStyle(doc.body).backgroundColor,
|
|
108
|
+
text: textTone(doc, view),
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
const scheduleColors = () => {
|
|
112
|
+
clearTimeout(colorTimer);
|
|
113
|
+
colorTimer = setTimeout(() => { try { send(computeColors()); } catch {} }, COLOR_DELAY_MS);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// ── Layout: room for the window's controls while they are over the bar ──
|
|
117
|
+
const layout = () => {
|
|
118
|
+
const active = titleBarActive({ overlayVisible: wco ? wco.visible : null, hasOverlayApi: !!wco, fullScreen: !!state.fullScreen });
|
|
119
|
+
doc.body.classList.toggle('wco-active', active);
|
|
120
|
+
html.classList.toggle('desktop-titlebar--maximized', !!state.maximized);
|
|
121
|
+
if (!wco) {
|
|
122
|
+
const area = fallbackTitlebarArea(facts, view.innerWidth);
|
|
123
|
+
html.style.setProperty('--desktop-titlebar-x', `${area.x}px`);
|
|
124
|
+
html.style.setProperty('--desktop-titlebar-width', `${area.width}px`);
|
|
125
|
+
html.style.setProperty('--desktop-titlebar-height', `${area.height}px`);
|
|
126
|
+
}
|
|
127
|
+
scheduleCover();
|
|
128
|
+
scheduleColors();
|
|
129
|
+
};
|
|
130
|
+
const setState = (next) => { if (next && typeof next === 'object') state = { ...state, ...next }; layout(); };
|
|
131
|
+
|
|
132
|
+
layout();
|
|
133
|
+
Promise.resolve(api.state()).then(setState).catch(() => {});
|
|
134
|
+
if (typeof api.onState === 'function') api.onState(setState);
|
|
135
|
+
wco?.addEventListener('geometrychange', layout);
|
|
136
|
+
view.addEventListener('resize', () => { scheduleCover(); scheduleColors(); });
|
|
137
|
+
bar.addEventListener('transitionend', scheduleColors);
|
|
138
|
+
|
|
139
|
+
// What changes the bar's colours: its own class and style (the Memory Explorer, a sidebar), the stylesheets (a
|
|
140
|
+
// skin's <link>, which draws once it has loaded) and the interface settings (variables on <html>).
|
|
141
|
+
new view.MutationObserver(scheduleColors).observe(bar, { attributes: true, attributeFilter: ['class', 'style'] });
|
|
142
|
+
new view.MutationObserver((records) => {
|
|
143
|
+
for (const record of records) {
|
|
144
|
+
for (const node of record.addedNodes || []) if (node.tagName === 'LINK') node.addEventListener('load', scheduleColors, { once: true });
|
|
145
|
+
}
|
|
146
|
+
scheduleColors();
|
|
147
|
+
}).observe(doc.head, { childList: true, subtree: true, attributes: true, attributeFilter: ['href', 'media', 'disabled'] });
|
|
148
|
+
new view.MutationObserver(scheduleColors).observe(html, { attributes: true, attributeFilter: ['style', 'class'] });
|
|
149
|
+
// What can come over the bar: anything added, shown or moved on the page.
|
|
150
|
+
new view.MutationObserver(scheduleCover).observe(doc.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style', 'hidden', 'open'] });
|
|
151
|
+
|
|
152
|
+
return { facts, menuButton, refresh: layout, state: () => ({ ...state }) };
|
|
153
|
+
}
|