@flyingrobots/bijou-tui 4.4.1 → 5.0.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 +92 -20
- package/dist/app-frame-actions.d.ts.map +1 -1
- package/dist/app-frame-actions.js +2 -0
- package/dist/app-frame-actions.js.map +1 -1
- package/dist/app-frame-i18n.d.ts.map +1 -1
- package/dist/app-frame-i18n.js +49 -0
- package/dist/app-frame-i18n.js.map +1 -1
- package/dist/app-frame-layers.d.ts +11 -1
- package/dist/app-frame-layers.d.ts.map +1 -1
- package/dist/app-frame-layers.js +19 -0
- package/dist/app-frame-layers.js.map +1 -1
- package/dist/app-frame-overlays.d.ts +82 -0
- package/dist/app-frame-overlays.d.ts.map +1 -0
- package/dist/app-frame-overlays.js +480 -0
- package/dist/app-frame-overlays.js.map +1 -0
- package/dist/app-frame-render.js +2 -2
- package/dist/app-frame-render.js.map +1 -1
- package/dist/app-frame-types.d.ts +119 -5
- package/dist/app-frame-types.d.ts.map +1 -1
- package/dist/app-frame-types.js +17 -1
- package/dist/app-frame-types.js.map +1 -1
- package/dist/app-frame-utils.d.ts.map +1 -1
- package/dist/app-frame-utils.js +6 -5
- package/dist/app-frame-utils.js.map +1 -1
- package/dist/app-frame.d.ts +18 -84
- package/dist/app-frame.d.ts.map +1 -1
- package/dist/app-frame.js +377 -625
- package/dist/app-frame.js.map +1 -1
- package/dist/browsable-list.d.ts +12 -0
- package/dist/browsable-list.d.ts.map +1 -1
- package/dist/browsable-list.js +17 -6
- package/dist/browsable-list.js.map +1 -1
- package/dist/canvas.d.ts.map +1 -1
- package/dist/canvas.js +27 -7
- package/dist/canvas.js.map +1 -1
- package/dist/collection-surface.d.ts +8 -0
- package/dist/collection-surface.d.ts.map +1 -1
- package/dist/collection-surface.js +72 -8
- package/dist/collection-surface.js.map +1 -1
- package/dist/css/text-style.d.ts +2 -0
- package/dist/css/text-style.d.ts.map +1 -1
- package/dist/css/text-style.js +18 -8
- package/dist/css/text-style.js.map +1 -1
- package/dist/debug-overlay.d.ts +19 -0
- package/dist/debug-overlay.d.ts.map +1 -0
- package/dist/debug-overlay.js +25 -0
- package/dist/debug-overlay.js.map +1 -0
- package/dist/design-language.d.ts.map +1 -1
- package/dist/design-language.js +4 -5
- package/dist/design-language.js.map +1 -1
- package/dist/driver.d.ts +102 -0
- package/dist/driver.d.ts.map +1 -1
- package/dist/driver.js +259 -19
- package/dist/driver.js.map +1 -1
- package/dist/file-picker.d.ts.map +1 -1
- package/dist/file-picker.js +2 -2
- package/dist/file-picker.js.map +1 -1
- package/dist/flex.d.ts.map +1 -1
- package/dist/flex.js +6 -6
- package/dist/flex.js.map +1 -1
- package/dist/focus-area.d.ts +9 -1
- package/dist/focus-area.d.ts.map +1 -1
- package/dist/focus-area.js +52 -11
- package/dist/focus-area.js.map +1 -1
- package/dist/grid.d.ts +2 -2
- package/dist/grid.d.ts.map +1 -1
- package/dist/grid.js +11 -148
- package/dist/grid.js.map +1 -1
- package/dist/help.d.ts +2 -0
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +6 -4
- package/dist/help.js.map +1 -1
- package/dist/icon-presentation.d.ts +10 -0
- package/dist/icon-presentation.d.ts.map +1 -0
- package/dist/icon-presentation.js +12 -0
- package/dist/icon-presentation.js.map +1 -0
- package/dist/index.d.ts +14 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/layout-preset.d.ts +1 -1
- package/dist/layout-preset.d.ts.map +1 -1
- package/dist/motion/reconciler.d.ts +6 -2
- package/dist/motion/reconciler.d.ts.map +1 -1
- package/dist/motion/reconciler.js +40 -10
- package/dist/motion/reconciler.js.map +1 -1
- package/dist/motion/types.d.ts +3 -1
- package/dist/motion/types.d.ts.map +1 -1
- package/dist/navigable-table.d.ts +9 -10
- package/dist/navigable-table.d.ts.map +1 -1
- package/dist/navigable-table.js +33 -14
- package/dist/navigable-table.js.map +1 -1
- package/dist/notification.d.ts +15 -0
- package/dist/notification.d.ts.map +1 -1
- package/dist/notification.js +98 -29
- package/dist/notification.js.map +1 -1
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +40 -14
- package/dist/overlay.js.map +1 -1
- package/dist/pager.d.ts +3 -1
- package/dist/pager.d.ts.map +1 -1
- package/dist/pager.js +4 -0
- package/dist/pager.js.map +1 -1
- package/dist/pipeline/middleware/grayscale.d.ts.map +1 -1
- package/dist/pipeline/middleware/grayscale.js +5 -5
- package/dist/pipeline/middleware/grayscale.js.map +1 -1
- package/dist/pipeline/middleware/motion.js +2 -2
- package/dist/pipeline/middleware/motion.js.map +1 -1
- package/dist/pipeline/middleware/surface-shaders.d.ts +37 -0
- package/dist/pipeline/middleware/surface-shaders.d.ts.map +1 -0
- package/dist/pipeline/middleware/surface-shaders.js +164 -0
- package/dist/pipeline/middleware/surface-shaders.js.map +1 -0
- package/dist/pipeline/pipeline.d.ts +29 -1
- package/dist/pipeline/pipeline.d.ts.map +1 -1
- package/dist/pipeline/pipeline.js +86 -23
- package/dist/pipeline/pipeline.js.map +1 -1
- package/dist/runtime.d.ts +19 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +183 -30
- package/dist/runtime.js.map +1 -1
- package/dist/split-pane.d.ts +2 -2
- package/dist/split-pane.d.ts.map +1 -1
- package/dist/split-pane.js +28 -49
- package/dist/split-pane.js.map +1 -1
- package/dist/subapp/mount.d.ts +17 -0
- package/dist/subapp/mount.d.ts.map +1 -1
- package/dist/subapp/mount.js +13 -0
- package/dist/subapp/mount.js.map +1 -1
- package/dist/surface-layout.d.ts +8 -3
- package/dist/surface-layout.d.ts.map +1 -1
- package/dist/surface-layout.js +27 -12
- package/dist/surface-layout.js.map +1 -1
- package/dist/view-output.js +2 -2
- package/dist/view-output.js.map +1 -1
- package/dist/viewport.d.ts +13 -1
- package/dist/viewport.d.ts.map +1 -1
- package/dist/viewport.js +33 -11
- package/dist/viewport.js.map +1 -1
- package/package.json +3 -3
package/dist/app-frame.js
CHANGED
|
@@ -4,28 +4,27 @@
|
|
|
4
4
|
* Provides tabs, pane focus/scroll isolation, shell key handling, help,
|
|
5
5
|
* panel-scoped overlay context, and optional frame-level command palette.
|
|
6
6
|
*/
|
|
7
|
-
import { cloneContextWithResolvedTheme, createResolved, createSurface, setDefaultContext,
|
|
8
|
-
import {
|
|
9
|
-
import { createKeyMap } from './keybindings.js';
|
|
7
|
+
import { cloneContextWithResolvedTheme, createResolved, createSurface, setDefaultContext, resolveClock, resolveSafeCtx, } from '@flyingrobots/bijou';
|
|
8
|
+
import { createKeyMap, formatKeyCombo } from './keybindings.js';
|
|
10
9
|
import { isKeyMsg, isMouseMsg, isResizeMsg } from './types.js';
|
|
11
10
|
import { quit } from './commands.js';
|
|
12
|
-
import {
|
|
11
|
+
import { runWithLifecycleHooks } from './runtime.js';
|
|
12
|
+
import { compositeSurfaceInto, modal } from './overlay.js';
|
|
13
13
|
import { isShellQuitConfirmAccept, isShellQuitConfirmDismiss, isShellQuitRequest, renderShellQuitOverlay, shouldUseShellQuitConfirm, } from './shell-quit.js';
|
|
14
14
|
import { commandPaletteSurface, commandPaletteKeyMap, } from './command-palette.js';
|
|
15
|
-
import { createPagerStateForSurface, pagerSurface, } from './pager.js';
|
|
16
15
|
import { restoreLayoutState } from './layout-preset.js';
|
|
17
|
-
import {
|
|
18
|
-
import { insetLineSurface } from './collection-surface.js';
|
|
19
|
-
import { vstackSurface } from './surface-layout.js';
|
|
16
|
+
import { createNotificationState, dismissNotification, hitTestNotificationStack, notificationsNeedTick, pushNotification, renderNotificationStack, tickNotifications, trimNotificationsToViewport, } from './notification.js';
|
|
20
17
|
import { isFrameScopedMsg, isPageScopedMsg, wrapCmdForPage, emitMsg, emitMsgForPage, wrapFrameMsg, } from './app-frame-types.js';
|
|
21
18
|
import { createFrameKeyMap, frameBodyRect, mergeBindingSources, } from './app-frame-utils.js';
|
|
22
|
-
import {
|
|
19
|
+
import { renderHelpOverlay, isHelpScrollAction, resolveCurrentShellTheme, resolveNextShellTheme, resolveShellThemeForContext, resolveFrameSettings, resolveFrameNotificationCenter, resolveSettingsLayout, resolveNotificationCenterLayout, resolveInputAreas, findInputAreaByPaneId, moveSettingsFocus, scrollSettingsBy, clampSettingsScroll, scrollNotificationCenterBy, cycleNotificationCenterFilter, clampSettingsFocus, renderSettingsDrawer, renderNotificationCenterDrawer, resolveNotificationFooterCue, } from './app-frame-overlays.js';
|
|
20
|
+
import { activeFrameLayer, describeFrameRuntimeViewStack, projectFrameControls, } from './app-frame-layers.js';
|
|
23
21
|
import { applyRuntimeCommandBuffer, bufferRuntimeRouteResult, createRuntimeBuffers, createRuntimeRetainedLayouts, retainRuntimeLayout, routeRuntimeInput, } from './runtime-engine.js';
|
|
24
|
-
import {
|
|
22
|
+
import { frameMessage, frameNotificationFilterLabel, } from './app-frame-i18n.js';
|
|
25
23
|
import { resolveHeaderLine, renderHelpLine, renderPageContent, renderPageContentInto, renderMaximizedPane, renderMaximizedPaneInto, renderTransition, createFramePaneScratchPool, } from './app-frame-render.js';
|
|
26
24
|
import { applyFrameAction, scrollFocusedPane, switchTab, syncPageFrameState, } from './app-frame-actions.js';
|
|
27
25
|
import { handlePaletteKey, openCommandPalette, openSearchPalette, } from './app-frame-palette.js';
|
|
28
|
-
export {
|
|
26
|
+
export { emitFrameAction, notify, } from './app-frame-types.js';
|
|
27
|
+
export { activeFrameLayer, describeFrameLayerStack, describeFrameRuntimeViewStack, projectFrameControls, underlyingFrameLayer, } from './app-frame-layers.js';
|
|
29
28
|
// ---------------------------------------------------------------------------
|
|
30
29
|
// Frame Notification Helpers
|
|
31
30
|
// ---------------------------------------------------------------------------
|
|
@@ -33,72 +32,79 @@ const FRAME_NOTIFICATION_TICK_MS = 40;
|
|
|
33
32
|
const DEFAULT_FRAME_NOTIFICATION_DURATION_MS = 6_000;
|
|
34
33
|
const SETTINGS_FEEDBACK_TOAST_WIDTH = 40;
|
|
35
34
|
const EMPTY_RUNTIME_LAYOUTS = createRuntimeRetainedLayouts();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
35
|
+
function createQuitHelpKeys(i18n) {
|
|
36
|
+
const t = (id, fallback) => frameMessage(i18n, id, fallback);
|
|
37
|
+
return createKeyMap()
|
|
38
|
+
.group(t('help.group.quit', 'Quit'), (g) => g
|
|
39
|
+
.bind('q', t('help.key.quit', 'Quit'), { type: 'toggle-help' })
|
|
40
|
+
.bind('escape', t('help.key.quit', 'Quit'), { type: 'toggle-help' })
|
|
41
|
+
.bind('ctrl+c', t('help.key.quit', 'Quit'), { type: 'toggle-help' }));
|
|
42
|
+
}
|
|
43
|
+
function createHelpLayerHelpKeys(i18n) {
|
|
44
|
+
const t = (id, fallback) => frameMessage(i18n, id, fallback);
|
|
45
|
+
return createKeyMap()
|
|
46
|
+
.group(t('help.group.help', 'Help'), (g) => g
|
|
47
|
+
.bind('escape', t('help.key.closeHelp', 'Close help'), { type: 'noop' })
|
|
48
|
+
.bind('?', t('help.key.closeHelp', 'Close help'), { type: 'noop' })
|
|
49
|
+
.bind('up', t('key.scrollUp', 'Scroll up'), { type: 'noop' })
|
|
50
|
+
.bind('down', t('key.scrollDown', 'Scroll down'), { type: 'noop' })
|
|
51
|
+
.bind('j', t('key.scrollDown', 'Scroll down'), { type: 'noop' })
|
|
52
|
+
.bind('k', t('key.scrollUp', 'Scroll up'), { type: 'noop' })
|
|
53
|
+
.bind('d', t('key.pageDown', 'Page down'), { type: 'noop' })
|
|
54
|
+
.bind('u', t('key.pageUp', 'Page up'), { type: 'noop' })
|
|
55
|
+
.bind('g', t('key.top', 'Top'), { type: 'noop' })
|
|
56
|
+
.bind('shift+g', t('key.bottom', 'Bottom'), { type: 'noop' }));
|
|
57
|
+
}
|
|
58
|
+
function createSettingsHelpKeys(i18n) {
|
|
59
|
+
const t = (id, fallback) => frameMessage(i18n, id, fallback);
|
|
60
|
+
return createKeyMap()
|
|
61
|
+
.group(t('help.group.settings', 'Settings'), (g) => g
|
|
62
|
+
.bind('escape', t('help.key.closeSettings', 'Close settings'), { type: 'toggle-settings' })
|
|
63
|
+
.bind('f2', t('help.key.closeSettings', 'Close settings'), { type: 'toggle-settings' })
|
|
64
|
+
.bind('up', t('help.key.previousRow', 'Previous row'), { type: 'scroll-up' })
|
|
65
|
+
.bind('down', t('help.key.nextRow', 'Next row'), { type: 'scroll-down' })
|
|
66
|
+
.bind('enter', t('help.key.activateSetting', 'Activate setting'), { type: 'toggle-settings' })
|
|
67
|
+
.bind('space', t('help.key.activateSetting', 'Activate setting'), { type: 'toggle-settings' })
|
|
68
|
+
.bind('j', t('key.scrollDown', 'Scroll down'), { type: 'scroll-down' })
|
|
69
|
+
.bind('k', t('key.scrollUp', 'Scroll up'), { type: 'scroll-up' })
|
|
70
|
+
.bind('d', t('key.pageDown', 'Page down'), { type: 'page-down' })
|
|
71
|
+
.bind('u', t('key.pageUp', 'Page up'), { type: 'page-up' })
|
|
72
|
+
.bind('g', t('key.top', 'Top'), { type: 'top' })
|
|
73
|
+
.bind('shift+g', t('key.bottom', 'Bottom'), { type: 'bottom' })
|
|
74
|
+
.bind('/', t('key.search', 'Search'), { type: 'open-search' })
|
|
75
|
+
.bind('ctrl+p', t('key.openPalette', 'Open command palette'), { type: 'open-palette' })
|
|
76
|
+
.bind(':', t('key.openPalette', 'Open command palette'), { type: 'open-palette' })
|
|
77
|
+
.bind('?', t('key.toggleHelp', 'Toggle help'), { type: 'toggle-help' }));
|
|
78
|
+
}
|
|
79
|
+
function createNotificationCenterHelpKeys(i18n) {
|
|
80
|
+
const t = (id, fallback) => frameMessage(i18n, id, fallback);
|
|
81
|
+
return createKeyMap()
|
|
82
|
+
.group(t('help.group.notifications', 'Notifications'), (g) => g
|
|
83
|
+
.bind('shift+n', t('help.key.closeNotifications', 'Close notification center'), { type: 'noop' })
|
|
84
|
+
.bind('up', t('key.scrollUp', 'Scroll up'), { type: 'noop' })
|
|
85
|
+
.bind('down', t('key.scrollDown', 'Scroll down'), { type: 'noop' })
|
|
86
|
+
.bind('j', t('key.scrollDown', 'Scroll down'), { type: 'noop' })
|
|
87
|
+
.bind('k', t('key.scrollUp', 'Scroll up'), { type: 'noop' })
|
|
88
|
+
.bind('d', t('key.pageDown', 'Page down'), { type: 'noop' })
|
|
89
|
+
.bind('u', t('key.pageUp', 'Page up'), { type: 'noop' })
|
|
90
|
+
.bind('g', t('key.top', 'Top'), { type: 'noop' })
|
|
91
|
+
.bind('shift+g', t('key.bottom', 'Bottom'), { type: 'noop' })
|
|
92
|
+
.bind('f', t('help.key.cycleFilter', 'Cycle filter'), { type: 'noop' })
|
|
93
|
+
.bind('/', t('key.search', 'Search'), { type: 'noop' })
|
|
94
|
+
.bind('ctrl+p', t('key.openPalette', 'Open command palette'), { type: 'noop' })
|
|
95
|
+
.bind(':', t('key.openPalette', 'Open command palette'), { type: 'noop' })
|
|
96
|
+
.bind('?', t('key.toggleHelp', 'Toggle help'), { type: 'noop' }));
|
|
97
|
+
}
|
|
98
|
+
function createQuitConfirmHelpKeys(i18n) {
|
|
99
|
+
const t = (id, fallback) => frameMessage(i18n, id, fallback);
|
|
100
|
+
return createKeyMap()
|
|
101
|
+
.group(t('help.group.quit', 'Quit'), (g) => g
|
|
102
|
+
.bind('y', t('help.key.quit', 'Quit'), { type: 'noop' })
|
|
103
|
+
.bind('enter', t('help.key.quit', 'Quit'), { type: 'noop' })
|
|
104
|
+
.bind('n', t('help.key.stay', 'Stay'), { type: 'noop' })
|
|
105
|
+
.bind('escape', t('help.key.stay', 'Stay'), { type: 'noop' })
|
|
106
|
+
.bind('q', t('help.key.stay', 'Stay'), { type: 'noop' }));
|
|
107
|
+
}
|
|
102
108
|
function resolveFrameNotificationOptions(options) {
|
|
103
109
|
if (options.runtimeNotifications === false) {
|
|
104
110
|
return {
|
|
@@ -137,78 +143,45 @@ function createFrameNotificationTickCmd() {
|
|
|
137
143
|
function cloneShellThemeContext(ctx, resolvedTheme) {
|
|
138
144
|
return cloneContextWithResolvedTheme(ctx, resolvedTheme);
|
|
139
145
|
}
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
if (labels.length === 0)
|
|
143
|
-
return '';
|
|
144
|
-
if (i18n == null)
|
|
145
|
-
return labels.join(', ');
|
|
146
|
-
return i18n.formatList(labels, i18n.locale);
|
|
147
|
-
}
|
|
148
|
-
function resolveCurrentShellTheme(shellThemes, activeShellThemeId) {
|
|
149
|
-
return shellThemes.find((theme) => theme.id === activeShellThemeId) ?? shellThemes[0];
|
|
146
|
+
function readStageDuration(timings, stage) {
|
|
147
|
+
return timings.find((timing) => timing.stage === stage)?.durationMs ?? 0;
|
|
150
148
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return undefined;
|
|
160
|
-
return shellThemes.find((theme) => theme.resolvedTheme.theme === ctx.theme.theme);
|
|
161
|
-
}
|
|
162
|
-
function mergeShellThemeSettings(settings, shellThemes, activeShellThemeId, i18n) {
|
|
163
|
-
if (shellThemes.length < 2)
|
|
164
|
-
return settings;
|
|
165
|
-
const currentTheme = resolveCurrentShellTheme(shellThemes, activeShellThemeId);
|
|
166
|
-
const nextTheme = resolveNextShellTheme(shellThemes, activeShellThemeId);
|
|
167
|
-
if (currentTheme == null || nextTheme == null)
|
|
168
|
-
return settings;
|
|
169
|
-
const row = {
|
|
170
|
-
id: FRAME_SHELL_THEME_ROW_ID,
|
|
171
|
-
label: frameMessage(i18n, 'settings.shellTheme.label', 'Shell theme'),
|
|
172
|
-
description: currentTheme.description ?? frameMessage(i18n, 'settings.shellTheme.description', 'Current theme: {theme}. Options: {options}.', {
|
|
173
|
-
theme: currentTheme.label,
|
|
174
|
-
options: resolveShellThemeOptionsText(shellThemes, i18n),
|
|
175
|
-
}),
|
|
176
|
-
valueLabel: currentTheme.label,
|
|
177
|
-
kind: 'choice',
|
|
178
|
-
feedback: {
|
|
179
|
-
title: frameMessage(i18n, 'settings.title', 'Settings'),
|
|
180
|
-
message: frameMessage(i18n, 'settings.shellTheme.feedback', 'Shell theme set to {theme}.', { theme: nextTheme.label }),
|
|
181
|
-
},
|
|
149
|
+
function summarizeFrameTimings(timings, frameBudgetMs) {
|
|
150
|
+
const frameTimeMs = timings.reduce((total, timing) => total + timing.durationMs, 0);
|
|
151
|
+
return {
|
|
152
|
+
frameTimeMs,
|
|
153
|
+
viewTimeMs: readStageDuration(timings, 'Layout'),
|
|
154
|
+
diffTimeMs: readStageDuration(timings, 'Diff'),
|
|
155
|
+
frameBudgetMs,
|
|
156
|
+
frameOverBudget: frameBudgetMs != null && frameTimeMs > frameBudgetMs,
|
|
182
157
|
};
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (shellSectionIndex >= 0) {
|
|
192
|
-
const shellSection = settings.sections[shellSectionIndex];
|
|
193
|
-
const existingRowIndex = shellSection.rows.findIndex((existingRow) => existingRow.id === FRAME_SHELL_THEME_ROW_ID);
|
|
194
|
-
const nextRows = existingRowIndex >= 0
|
|
195
|
-
? shellSection.rows.map((existingRow, index) => (index === existingRowIndex ? row : existingRow))
|
|
196
|
-
: [...shellSection.rows, row];
|
|
197
|
-
return {
|
|
198
|
-
...settings,
|
|
199
|
-
sections: settings.sections.map((section, index) => (index === shellSectionIndex
|
|
200
|
-
? { ...shellSection, rows: nextRows }
|
|
201
|
-
: section)),
|
|
202
|
-
};
|
|
158
|
+
}
|
|
159
|
+
function applyFrameTimingSnapshot(model, snapshot) {
|
|
160
|
+
if (model.frameTimeMs === snapshot.frameTimeMs
|
|
161
|
+
&& model.viewTimeMs === snapshot.viewTimeMs
|
|
162
|
+
&& model.diffTimeMs === snapshot.diffTimeMs
|
|
163
|
+
&& model.frameBudgetMs === snapshot.frameBudgetMs
|
|
164
|
+
&& model.frameOverBudget === snapshot.frameOverBudget) {
|
|
165
|
+
return model;
|
|
203
166
|
}
|
|
204
167
|
return {
|
|
205
|
-
...
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
168
|
+
...model,
|
|
169
|
+
frameTimeMs: snapshot.frameTimeMs,
|
|
170
|
+
viewTimeMs: snapshot.viewTimeMs,
|
|
171
|
+
diffTimeMs: snapshot.diffTimeMs,
|
|
172
|
+
frameBudgetMs: snapshot.frameBudgetMs,
|
|
173
|
+
frameOverBudget: snapshot.frameOverBudget,
|
|
210
174
|
};
|
|
211
175
|
}
|
|
176
|
+
function resolveFrameBudgetMs(runOptions, fallbackCtx) {
|
|
177
|
+
if (runOptions?.frameBudgetMs != null)
|
|
178
|
+
return runOptions.frameBudgetMs;
|
|
179
|
+
const refreshRate = runOptions?.ctx?.runtime.refreshRate ?? fallbackCtx?.runtime.refreshRate;
|
|
180
|
+
if (refreshRate == null || !Number.isFinite(refreshRate) || refreshRate <= 0) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
return 1_000 / refreshRate;
|
|
184
|
+
}
|
|
212
185
|
// Factory
|
|
213
186
|
// ---------------------------------------------------------------------------
|
|
214
187
|
/**
|
|
@@ -230,28 +203,40 @@ export function createFramedApp(options) {
|
|
|
230
203
|
if (!pagesById.has(defaultPageId)) {
|
|
231
204
|
throw new Error(`createFramedApp: defaultPageId "${defaultPageId}" not found in pages`);
|
|
232
205
|
}
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const resolvedShellThemes = options.shellThemes?.map((theme) => ({
|
|
238
|
-
id: theme.id,
|
|
239
|
-
label: theme.label,
|
|
240
|
-
description: theme.description,
|
|
241
|
-
shellTheme: theme,
|
|
242
|
-
resolvedTheme: createResolved(theme.theme, defaultFrameCtx.theme.noColor, defaultFrameCtx.theme.colorScheme),
|
|
243
|
-
})) ?? [];
|
|
244
|
-
const enableShellThemeSettings = resolvedShellThemes.length > 1;
|
|
245
|
-
const initialShellTheme = resolveShellThemeForContext(resolvedShellThemes, defaultFrameCtx)
|
|
246
|
-
?? resolvedShellThemes[0];
|
|
206
|
+
const shellThemeSpecs = options.shellThemes ?? [];
|
|
207
|
+
let defaultFrameCtx = options.ctx ?? resolveSafeCtx();
|
|
208
|
+
let resolvedShellThemes = [];
|
|
209
|
+
const enableShellThemeSettings = shellThemeSpecs.length > 1;
|
|
247
210
|
const usesAmbientDefaultContext = options.ctx == null && defaultFrameCtx != null;
|
|
248
211
|
let frameCtx = options.ctx;
|
|
249
|
-
let frameCtxShellThemeId
|
|
212
|
+
let frameCtxShellThemeId;
|
|
213
|
+
let useRunScopedFrameCtx = false;
|
|
214
|
+
function ensureResolvedShellThemes(explicitCtx) {
|
|
215
|
+
if (shellThemeSpecs.length === 0 || resolvedShellThemes.length > 0)
|
|
216
|
+
return;
|
|
217
|
+
const baseCtx = explicitCtx ?? frameCtx ?? options.ctx ?? resolveSafeCtx();
|
|
218
|
+
if (baseCtx == null) {
|
|
219
|
+
throw new Error('createFramedApp: shellThemes requires options.ctx, app.run({ ctx }), or a default Bijou context');
|
|
220
|
+
}
|
|
221
|
+
defaultFrameCtx ??= baseCtx;
|
|
222
|
+
resolvedShellThemes = shellThemeSpecs.map((theme) => ({
|
|
223
|
+
id: theme.id,
|
|
224
|
+
label: theme.label,
|
|
225
|
+
description: theme.description,
|
|
226
|
+
shellTheme: theme,
|
|
227
|
+
resolvedTheme: createResolved(theme.theme, defaultFrameCtx.theme.noColor, defaultFrameCtx.theme.colorScheme),
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
if (frameCtx != null) {
|
|
231
|
+
ensureResolvedShellThemes(frameCtx);
|
|
232
|
+
frameCtxShellThemeId = resolveShellThemeForContext(resolvedShellThemes, frameCtx)?.id;
|
|
233
|
+
}
|
|
250
234
|
function resolveFrameCtx() {
|
|
251
235
|
return frameCtx ?? options.ctx ?? resolveSafeCtx();
|
|
252
236
|
}
|
|
253
237
|
function resolveFrameThemeCtx(activeShellThemeId) {
|
|
254
238
|
const baseCtx = resolveFrameCtx();
|
|
239
|
+
ensureResolvedShellThemes(baseCtx);
|
|
255
240
|
if (defaultFrameCtx == null)
|
|
256
241
|
return baseCtx;
|
|
257
242
|
const activeTheme = resolveCurrentShellTheme(resolvedShellThemes, activeShellThemeId);
|
|
@@ -266,11 +251,12 @@ export function createFramedApp(options) {
|
|
|
266
251
|
return cloneShellThemeContext(defaultFrameCtx, activeTheme.resolvedTheme);
|
|
267
252
|
}
|
|
268
253
|
function publishShellThemeContext(nextTheme) {
|
|
254
|
+
ensureResolvedShellThemes(resolveFrameCtx());
|
|
269
255
|
if (defaultFrameCtx == null)
|
|
270
256
|
return resolveFrameCtx();
|
|
271
257
|
frameCtx = cloneShellThemeContext(defaultFrameCtx, nextTheme.resolvedTheme);
|
|
272
258
|
frameCtxShellThemeId = nextTheme.id;
|
|
273
|
-
if (usesAmbientDefaultContext) {
|
|
259
|
+
if (usesAmbientDefaultContext && !useRunScopedFrameCtx) {
|
|
274
260
|
setDefaultContext(frameCtx);
|
|
275
261
|
}
|
|
276
262
|
options.onShellThemeChange?.({
|
|
@@ -284,11 +270,17 @@ export function createFramedApp(options) {
|
|
|
284
270
|
enableNotifications: options.notificationCenter != null || options.runtimeNotifications !== false,
|
|
285
271
|
i18n: options.i18n,
|
|
286
272
|
});
|
|
273
|
+
const quitHelpKeys = createQuitHelpKeys(options.i18n);
|
|
274
|
+
const helpLayerHelpKeys = createHelpLayerHelpKeys(options.i18n);
|
|
275
|
+
const settingsHelpKeys = createSettingsHelpKeys(options.i18n);
|
|
276
|
+
const notificationCenterHelpKeys = createNotificationCenterHelpKeys(options.i18n);
|
|
277
|
+
const quitConfirmHelpKeys = createQuitConfirmHelpKeys(options.i18n);
|
|
287
278
|
const frameNotificationOptions = resolveFrameNotificationOptions(options);
|
|
288
279
|
let composedFrameScratch = null;
|
|
289
280
|
let headerScratch;
|
|
290
281
|
let helpLineScratch;
|
|
291
282
|
const paneScratchPool = createFramePaneScratchPool();
|
|
283
|
+
let workspaceLayoutCache;
|
|
292
284
|
const paletteKeys = commandPaletteKeyMap({
|
|
293
285
|
focusNext: { type: 'cp-next' },
|
|
294
286
|
focusPrev: { type: 'cp-prev' },
|
|
@@ -297,6 +289,49 @@ export function createFramedApp(options) {
|
|
|
297
289
|
select: { type: 'cp-select' },
|
|
298
290
|
close: { type: 'cp-close' },
|
|
299
291
|
});
|
|
292
|
+
function bindingComboKey(binding) {
|
|
293
|
+
const combo = binding.combo;
|
|
294
|
+
return `${combo.key}|${combo.ctrl ? 1 : 0}|${combo.alt ? 1 : 0}|${combo.shift ? 1 : 0}`;
|
|
295
|
+
}
|
|
296
|
+
function findBindingForMessage(bindings, msg) {
|
|
297
|
+
const comboKey = `${msg.key}|${msg.ctrl ? 1 : 0}|${msg.alt ? 1 : 0}|${msg.shift ? 1 : 0}`;
|
|
298
|
+
return bindings.find((binding) => binding.enabled && bindingComboKey(binding) === comboKey);
|
|
299
|
+
}
|
|
300
|
+
function queueFrameKeyCollisionWarning(model, msg, teaCmds) {
|
|
301
|
+
if (!frameNotificationOptions.enabled)
|
|
302
|
+
return model;
|
|
303
|
+
if ((options.keyPriority ?? 'frame-first') !== 'frame-first')
|
|
304
|
+
return model;
|
|
305
|
+
if (model.warnedFrameKeyCollisionPages[model.activePageId])
|
|
306
|
+
return model;
|
|
307
|
+
const activePage = pagesById.get(model.activePageId);
|
|
308
|
+
if (activePage?.keyMap == null)
|
|
309
|
+
return model;
|
|
310
|
+
const pageBinding = findBindingForMessage(activePage.keyMap.bindings(), msg);
|
|
311
|
+
const frameBinding = findBindingForMessage(frameKeys.bindings(), msg);
|
|
312
|
+
if (pageBinding == null || frameBinding == null)
|
|
313
|
+
return model;
|
|
314
|
+
const warningCmd = async () => wrapFrameMsg({
|
|
315
|
+
type: 'runtime-issue',
|
|
316
|
+
issue: {
|
|
317
|
+
level: 'warning',
|
|
318
|
+
source: 'runtime',
|
|
319
|
+
message: `Page "${model.activePageId}" key binding ${formatKeyCombo(pageBinding.combo)} `
|
|
320
|
+
+ `("${pageBinding.description}") is shadowed by the frame binding `
|
|
321
|
+
+ `"${frameBinding.description}" under keyPriority="frame-first". `
|
|
322
|
+
+ `Use keyPriority: 'page-first' or choose a different page binding.`,
|
|
323
|
+
atMs: resolveClock(resolveFrameCtx()).now(),
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
teaCmds.push(warningCmd);
|
|
327
|
+
return {
|
|
328
|
+
...model,
|
|
329
|
+
warnedFrameKeyCollisionPages: {
|
|
330
|
+
...model.warnedFrameKeyCollisionPages,
|
|
331
|
+
[model.activePageId]: true,
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
300
335
|
function getComposedFrameScratch(width, height) {
|
|
301
336
|
if (composedFrameScratch == null
|
|
302
337
|
|| composedFrameScratch.width !== width
|
|
@@ -402,11 +437,18 @@ export function createFramedApp(options) {
|
|
|
402
437
|
teaCmds.push(...cmds);
|
|
403
438
|
return nextModel;
|
|
404
439
|
},
|
|
440
|
+
'warn-frame-key-collision': (model, cmd, teaCmds) => {
|
|
441
|
+
const c = cmd;
|
|
442
|
+
return queueFrameKeyCollisionWarning(model, c.msg, teaCmds);
|
|
443
|
+
},
|
|
405
444
|
// --- help ---
|
|
406
445
|
'help-scroll': (model, cmd) => {
|
|
407
446
|
const c = cmd;
|
|
408
|
-
const
|
|
409
|
-
|
|
447
|
+
const helpSource = resolvePresentedLayerContext(model).controlProjection.helpSource;
|
|
448
|
+
if (helpSource == null) {
|
|
449
|
+
return model;
|
|
450
|
+
}
|
|
451
|
+
const overlay = renderHelpOverlay(model, helpSource, options.i18n);
|
|
410
452
|
const viewportHeight = Math.max(1, overlay.body.height - 1);
|
|
411
453
|
const delta = c.action === 'down' ? 3
|
|
412
454
|
: c.action === 'up' ? -3
|
|
@@ -706,7 +748,9 @@ export function createFramedApp(options) {
|
|
|
706
748
|
}
|
|
707
749
|
// frame-first (default)
|
|
708
750
|
if (frameAction !== undefined) {
|
|
709
|
-
return
|
|
751
|
+
return pageAction !== undefined
|
|
752
|
+
? [...resolveFrameActionCommands(msg, frameAction, 'frame'), { type: 'warn-frame-key-collision', msg }]
|
|
753
|
+
: resolveFrameActionCommands(msg, frameAction, 'frame');
|
|
710
754
|
}
|
|
711
755
|
if (paneAction !== undefined) {
|
|
712
756
|
return [{ type: 'observed-key', msg, route: 'page' }, { type: 'emit-page-msg', pageId: model.activePageId, msg: paneAction }];
|
|
@@ -779,27 +823,15 @@ export function createFramedApp(options) {
|
|
|
779
823
|
children: children ?? [],
|
|
780
824
|
};
|
|
781
825
|
}
|
|
782
|
-
function
|
|
783
|
-
const
|
|
784
|
-
const
|
|
785
|
-
const maximizedPaneId = maxState?.maximizedPaneId;
|
|
786
|
-
const themedFrameCtx = resolveFrameThemeCtx(model.activeShellThemeId);
|
|
787
|
-
const renderResult = maximizedPaneId
|
|
788
|
-
? renderMaximizedPane(model.activePageId, model, bodyRect, pagesById, maximizedPaneId, paneScratchPool, themedFrameCtx)
|
|
789
|
-
: renderPageContent(model.activePageId, model, bodyRect, pagesById, themedFrameCtx);
|
|
790
|
-
return renderResult.paneRects;
|
|
791
|
-
}
|
|
792
|
-
function buildWorkspaceLayoutTree(model) {
|
|
793
|
-
const header = resolveHeaderLine(model, options, pagesById, headerScratch, resolveFrameThemeCtx(model.activeShellThemeId));
|
|
794
|
-
headerScratch = header.surface;
|
|
795
|
-
const tabChildren = header.tabTargets.map((target) => createShellRetainedLayoutNode(`tab:${target.pageId}`, {
|
|
826
|
+
function buildWorkspaceLayoutTreeFromPaneRects(model, paneRects, tabTargets) {
|
|
827
|
+
const resolvedTabTargets = tabTargets ?? resolveHeaderLine(model, options, pagesById, headerScratch, resolveFrameThemeCtx(model.activeShellThemeId)).tabTargets;
|
|
828
|
+
const tabChildren = resolvedTabTargets.map((target) => createShellRetainedLayoutNode(`tab:${target.pageId}`, {
|
|
796
829
|
row: 0,
|
|
797
830
|
col: target.startCol,
|
|
798
831
|
width: target.endCol - target.startCol + 1,
|
|
799
832
|
height: 1,
|
|
800
833
|
}));
|
|
801
834
|
const bodyRect = resolveBodyRect(model, options);
|
|
802
|
-
const paneRects = resolveWorkspacePaneRects(model);
|
|
803
835
|
const paneChildren = [];
|
|
804
836
|
for (const [paneId, rect] of paneRects.entries()) {
|
|
805
837
|
paneChildren.push(createShellRetainedLayoutNode(`pane:${paneId}`, rect));
|
|
@@ -809,6 +841,55 @@ export function createFramedApp(options) {
|
|
|
809
841
|
createShellRetainedLayoutNode('workspace-body', bodyRect, paneChildren),
|
|
810
842
|
]);
|
|
811
843
|
}
|
|
844
|
+
function rememberWorkspaceLayout(model, paneRects, tabTargets) {
|
|
845
|
+
const activePageId = model.activePageId;
|
|
846
|
+
const next = {
|
|
847
|
+
activePageId,
|
|
848
|
+
activePageModel: model.pageModels[activePageId],
|
|
849
|
+
columns: model.columns,
|
|
850
|
+
rows: model.rows,
|
|
851
|
+
visibilityState: model.minimizedByPage[activePageId],
|
|
852
|
+
dockState: model.dockStateByPage[activePageId],
|
|
853
|
+
splitRatioOverrides: model.splitRatioOverrides,
|
|
854
|
+
maximizedPaneId: model.maximizedPaneByPage[activePageId]?.maximizedPaneId,
|
|
855
|
+
paneRects,
|
|
856
|
+
tree: buildWorkspaceLayoutTreeFromPaneRects(model, paneRects, tabTargets),
|
|
857
|
+
};
|
|
858
|
+
workspaceLayoutCache = next;
|
|
859
|
+
return next;
|
|
860
|
+
}
|
|
861
|
+
function matchesWorkspaceLayoutCache(model) {
|
|
862
|
+
if (workspaceLayoutCache == null)
|
|
863
|
+
return false;
|
|
864
|
+
const activePageId = model.activePageId;
|
|
865
|
+
return workspaceLayoutCache.activePageId === activePageId
|
|
866
|
+
&& workspaceLayoutCache.activePageModel === model.pageModels[activePageId]
|
|
867
|
+
&& workspaceLayoutCache.columns === model.columns
|
|
868
|
+
&& workspaceLayoutCache.rows === model.rows
|
|
869
|
+
&& workspaceLayoutCache.visibilityState === model.minimizedByPage[activePageId]
|
|
870
|
+
&& workspaceLayoutCache.dockState === model.dockStateByPage[activePageId]
|
|
871
|
+
&& workspaceLayoutCache.splitRatioOverrides === model.splitRatioOverrides
|
|
872
|
+
&& workspaceLayoutCache.maximizedPaneId === model.maximizedPaneByPage[activePageId]?.maximizedPaneId;
|
|
873
|
+
}
|
|
874
|
+
function resolveWorkspaceLayout(model) {
|
|
875
|
+
if (matchesWorkspaceLayoutCache(model)) {
|
|
876
|
+
return workspaceLayoutCache;
|
|
877
|
+
}
|
|
878
|
+
const bodyRect = resolveBodyRect(model, options);
|
|
879
|
+
const maxState = model.maximizedPaneByPage[model.activePageId];
|
|
880
|
+
const maximizedPaneId = maxState?.maximizedPaneId;
|
|
881
|
+
const themedFrameCtx = resolveFrameThemeCtx(model.activeShellThemeId);
|
|
882
|
+
const renderResult = maximizedPaneId
|
|
883
|
+
? renderMaximizedPane(model.activePageId, model, bodyRect, pagesById, maximizedPaneId, paneScratchPool, themedFrameCtx)
|
|
884
|
+
: renderPageContent(model.activePageId, model, bodyRect, pagesById, themedFrameCtx);
|
|
885
|
+
return rememberWorkspaceLayout(model, renderResult.paneRects);
|
|
886
|
+
}
|
|
887
|
+
function resolveWorkspacePaneRects(model) {
|
|
888
|
+
return resolveWorkspaceLayout(model).paneRects;
|
|
889
|
+
}
|
|
890
|
+
function buildWorkspaceLayoutTree(model) {
|
|
891
|
+
return resolveWorkspaceLayout(model).tree;
|
|
892
|
+
}
|
|
812
893
|
function buildSettingsRowChildren(model, layout) {
|
|
813
894
|
const scrollY = clampSettingsScroll(model, layout);
|
|
814
895
|
const viewportTop = 1;
|
|
@@ -1008,7 +1089,7 @@ export function createFramedApp(options) {
|
|
|
1008
1089
|
}
|
|
1009
1090
|
return helpLineOverride ?? mergeBindingSources(frameKeys, options.globalKeys, activeInputArea?.helpSource ?? activeInputArea?.keyMap, activePage.helpSource ?? activePage.keyMap);
|
|
1010
1091
|
}
|
|
1011
|
-
function resolveLayerMetadata(model, activePage, activeInputArea, modalKeyMap) {
|
|
1092
|
+
function resolveLayerMetadata(model, activePage, activePageModel, activeInputArea, modalKeyMap) {
|
|
1012
1093
|
const settings = resolveFrameSettings(model, options, pagesById, resolvedShellThemes);
|
|
1013
1094
|
const notificationCenter = resolveFrameNotificationCenter(model, options, pagesById);
|
|
1014
1095
|
const workspaceHintSource = resolveWorkspaceHintSource(model, activePage, activeInputArea);
|
|
@@ -1026,17 +1107,22 @@ export function createFramedApp(options) {
|
|
|
1026
1107
|
const notificationsTitle = notificationCenter == null
|
|
1027
1108
|
? frameMessage(options.i18n, 'notifications.title', 'Notifications')
|
|
1028
1109
|
: `${notificationCenter.title} • ${frameNotificationFilterLabel(options.i18n, notificationCenter.activeFilter)}`;
|
|
1110
|
+
const pageLayers = activePage.layers?.(activePageModel);
|
|
1111
|
+
const workspaceLayer = {
|
|
1112
|
+
title: activePage.title,
|
|
1113
|
+
hintSource: workspaceHintSource,
|
|
1114
|
+
helpSource: workspaceHelpSource,
|
|
1115
|
+
...pageLayers?.workspace,
|
|
1116
|
+
};
|
|
1117
|
+
const pageModalLayer = {
|
|
1118
|
+
title: activePage.title,
|
|
1119
|
+
hintSource: modalKeyMap ?? activePage.helpSource ?? activePage.keyMap,
|
|
1120
|
+
helpSource: mergeBindingSources(quitHelpKeys, modalKeyMap, activePage.helpSource ?? activePage.keyMap),
|
|
1121
|
+
...pageLayers?.['page-modal'],
|
|
1122
|
+
};
|
|
1029
1123
|
return {
|
|
1030
|
-
workspace:
|
|
1031
|
-
|
|
1032
|
-
hintSource: workspaceHintSource,
|
|
1033
|
-
helpSource: workspaceHelpSource,
|
|
1034
|
-
},
|
|
1035
|
-
'page-modal': {
|
|
1036
|
-
title: activePage.title,
|
|
1037
|
-
hintSource: modalKeyMap ?? activePage.helpSource ?? activePage.keyMap,
|
|
1038
|
-
helpSource: mergeBindingSources(quitHelpKeys, modalKeyMap, activePage.helpSource ?? activePage.keyMap),
|
|
1039
|
-
},
|
|
1124
|
+
workspace: workspaceLayer,
|
|
1125
|
+
'page-modal': pageModalLayer,
|
|
1040
1126
|
settings: {
|
|
1041
1127
|
title: settings?.title ?? frameMessage(options.i18n, 'settings.title', 'Settings'),
|
|
1042
1128
|
hintSource: settingsHint,
|
|
@@ -1071,12 +1157,11 @@ export function createFramedApp(options) {
|
|
|
1071
1157
|
}
|
|
1072
1158
|
function resolvePresentedLayerContext(model) {
|
|
1073
1159
|
const { activePage, activePageModel, inputAreas, activeInputArea, modalKeyMap, pageModalOpen, } = resolveLayerContext(model);
|
|
1074
|
-
const
|
|
1160
|
+
const layerMetadata = resolveLayerMetadata(model, activePage, activePageModel, activeInputArea, modalKeyMap);
|
|
1161
|
+
const controlProjection = projectFrameControls(model, {
|
|
1075
1162
|
pageModalOpen,
|
|
1076
|
-
layers:
|
|
1163
|
+
layers: layerMetadata,
|
|
1077
1164
|
});
|
|
1078
|
-
const activeLayer = layerStack[layerStack.length - 1];
|
|
1079
|
-
const underlyingLayer = layerStack.length > 1 ? layerStack[layerStack.length - 2] : undefined;
|
|
1080
1165
|
return {
|
|
1081
1166
|
activePage,
|
|
1082
1167
|
activePageModel,
|
|
@@ -1084,9 +1169,11 @@ export function createFramedApp(options) {
|
|
|
1084
1169
|
activeInputArea,
|
|
1085
1170
|
modalKeyMap,
|
|
1086
1171
|
pageModalOpen,
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1172
|
+
layerMetadata,
|
|
1173
|
+
controlProjection,
|
|
1174
|
+
layerStack: controlProjection.layerStack,
|
|
1175
|
+
activeLayer: controlProjection.activeLayer,
|
|
1176
|
+
underlyingLayer: controlProjection.underlyingLayer,
|
|
1090
1177
|
};
|
|
1091
1178
|
}
|
|
1092
1179
|
function updateTargetPage(model, targetPageId, targetMsg) {
|
|
@@ -1173,10 +1260,16 @@ export function createFramedApp(options) {
|
|
|
1173
1260
|
activePageId: defaultPageId,
|
|
1174
1261
|
pageOrder,
|
|
1175
1262
|
pageModels,
|
|
1263
|
+
warnedFrameKeyCollisionPages: {},
|
|
1176
1264
|
focusedPaneByPage: {},
|
|
1177
1265
|
scrollByPage: {},
|
|
1178
1266
|
columns: Math.max(1, options.initialColumns ?? 80),
|
|
1179
1267
|
rows: Math.max(1, options.initialRows ?? 24),
|
|
1268
|
+
frameTimeMs: 0,
|
|
1269
|
+
viewTimeMs: 0,
|
|
1270
|
+
diffTimeMs: 0,
|
|
1271
|
+
frameBudgetMs: undefined,
|
|
1272
|
+
frameOverBudget: false,
|
|
1180
1273
|
helpOpen: false,
|
|
1181
1274
|
helpScrollY: 0,
|
|
1182
1275
|
commandPaletteKind: undefined,
|
|
@@ -1196,6 +1289,13 @@ export function createFramedApp(options) {
|
|
|
1196
1289
|
runtimeNotifications: createNotificationState(),
|
|
1197
1290
|
runtimeNotificationHistoryFilter: 'ALL',
|
|
1198
1291
|
runtimeNotificationLoopActive: false,
|
|
1292
|
+
activeShellThemeId: undefined,
|
|
1293
|
+
};
|
|
1294
|
+
ensureResolvedShellThemes(resolveFrameCtx());
|
|
1295
|
+
const initialShellTheme = resolveShellThemeForContext(resolvedShellThemes, resolveFrameCtx())
|
|
1296
|
+
?? resolvedShellThemes[0];
|
|
1297
|
+
model = {
|
|
1298
|
+
...model,
|
|
1199
1299
|
activeShellThemeId: initialShellTheme?.id,
|
|
1200
1300
|
};
|
|
1201
1301
|
if (initialShellTheme != null) {
|
|
@@ -1236,6 +1336,20 @@ export function createFramedApp(options) {
|
|
|
1236
1336
|
}, action.issue.atMs);
|
|
1237
1337
|
return applyFrameNotificationState(model, notifications, action.issue.atMs);
|
|
1238
1338
|
}
|
|
1339
|
+
if (action.type === 'push-notification') {
|
|
1340
|
+
if (!frameNotificationOptions.enabled)
|
|
1341
|
+
return [model, []];
|
|
1342
|
+
const nowMs = resolveClock(resolveFrameCtx()).now();
|
|
1343
|
+
const notifications = pushNotification(model.runtimeNotifications, {
|
|
1344
|
+
...action.notification,
|
|
1345
|
+
placement: action.notification.placement ?? frameNotificationOptions.placement,
|
|
1346
|
+
durationMs: action.notification.durationMs === undefined
|
|
1347
|
+
? frameNotificationOptions.durationMs
|
|
1348
|
+
: action.notification.durationMs,
|
|
1349
|
+
overflow: action.notification.overflow ?? frameNotificationOptions.overflow,
|
|
1350
|
+
}, nowMs);
|
|
1351
|
+
return applyFrameNotificationState(model, notifications, nowMs);
|
|
1352
|
+
}
|
|
1239
1353
|
if (action.type === 'notification-tick') {
|
|
1240
1354
|
const notifications = tickNotifications(model.runtimeNotifications, action.atMs);
|
|
1241
1355
|
return applyFrameNotificationState(model, notifications, action.atMs, true);
|
|
@@ -1316,7 +1430,7 @@ export function createFramedApp(options) {
|
|
|
1316
1430
|
},
|
|
1317
1431
|
view(model) {
|
|
1318
1432
|
const themedFrameCtx = resolveFrameThemeCtx(model.activeShellThemeId);
|
|
1319
|
-
const {
|
|
1433
|
+
const { controlProjection, layerStack, activeLayer, } = resolvePresentedLayerContext(model);
|
|
1320
1434
|
const headerResult = resolveHeaderLine(model, options, pagesById, headerScratch, themedFrameCtx);
|
|
1321
1435
|
headerScratch = headerResult.surface;
|
|
1322
1436
|
const header = headerResult.surface;
|
|
@@ -1354,6 +1468,7 @@ export function createFramedApp(options) {
|
|
|
1354
1468
|
? renderMaximizedPaneInto(model.activePageId, model, bodyRect, pagesById, maximizedPaneId, frameSurface, bodyRect.row, bodyRect.col, paneScratchPool, themedFrameCtx)
|
|
1355
1469
|
: renderPageContentInto(model.activePageId, model, bodyRect, pagesById, frameSurface, bodyRect.row, bodyRect.col, paneScratchPool, themedFrameCtx);
|
|
1356
1470
|
}
|
|
1471
|
+
rememberWorkspaceLayout(model, activeResult.paneRects, headerResult.tabTargets);
|
|
1357
1472
|
const overlays = [];
|
|
1358
1473
|
if (options.overlayFactory != null) {
|
|
1359
1474
|
overlays.push(...options.overlayFactory({
|
|
@@ -1388,7 +1503,11 @@ export function createFramedApp(options) {
|
|
|
1388
1503
|
}
|
|
1389
1504
|
}
|
|
1390
1505
|
if (model.helpOpen) {
|
|
1391
|
-
const
|
|
1506
|
+
const helpSource = controlProjection.helpSource;
|
|
1507
|
+
if (helpSource == null) {
|
|
1508
|
+
throw new Error('createFramedApp: help layer projection is missing a help source');
|
|
1509
|
+
}
|
|
1510
|
+
const helpOverlay = renderHelpOverlay(model, helpSource, options.i18n);
|
|
1392
1511
|
overlays.push(modal({
|
|
1393
1512
|
title: activeLayer.kind === 'help'
|
|
1394
1513
|
? (activeLayer.title ?? frameMessage(options.i18n, 'help.title', 'Keyboard Help'))
|
|
@@ -1443,7 +1562,67 @@ export function createFramedApp(options) {
|
|
|
1443
1562
|
return wrapFrameMsg({ type: 'runtime-issue', issue });
|
|
1444
1563
|
},
|
|
1445
1564
|
};
|
|
1446
|
-
|
|
1565
|
+
const framedApp = app;
|
|
1566
|
+
let hostedRunActive = false;
|
|
1567
|
+
framedApp.run = async (runOptions) => {
|
|
1568
|
+
if (hostedRunActive) {
|
|
1569
|
+
throw new Error('createFramedApp: concurrent app.run() calls on the same framed app are not supported');
|
|
1570
|
+
}
|
|
1571
|
+
hostedRunActive = true;
|
|
1572
|
+
const previousFrameCtx = frameCtx;
|
|
1573
|
+
const previousFrameCtxShellThemeId = frameCtxShellThemeId;
|
|
1574
|
+
const previousDefaultFrameCtx = defaultFrameCtx;
|
|
1575
|
+
const previousResolvedShellThemes = resolvedShellThemes;
|
|
1576
|
+
const runtimeCtx = runOptions?.ctx;
|
|
1577
|
+
if (runtimeCtx != null && options.ctx == null) {
|
|
1578
|
+
useRunScopedFrameCtx = true;
|
|
1579
|
+
frameCtx = runtimeCtx;
|
|
1580
|
+
defaultFrameCtx = runtimeCtx;
|
|
1581
|
+
resolvedShellThemes = [];
|
|
1582
|
+
ensureResolvedShellThemes(runtimeCtx);
|
|
1583
|
+
frameCtxShellThemeId = resolveShellThemeForContext(resolvedShellThemes, runtimeCtx)?.id;
|
|
1584
|
+
}
|
|
1585
|
+
const frameBudgetMs = resolveFrameBudgetMs(runOptions, resolveFrameCtx());
|
|
1586
|
+
let pendingTimingSnapshot;
|
|
1587
|
+
let needsTimingHydrationRender = true;
|
|
1588
|
+
try {
|
|
1589
|
+
await runWithLifecycleHooks(framedApp, {
|
|
1590
|
+
...runOptions,
|
|
1591
|
+
mouse: runOptions?.mouse ?? true,
|
|
1592
|
+
}, {
|
|
1593
|
+
beforeRender(model) {
|
|
1594
|
+
if (pendingTimingSnapshot == null)
|
|
1595
|
+
return model;
|
|
1596
|
+
return applyFrameTimingSnapshot(model, pendingTimingSnapshot);
|
|
1597
|
+
},
|
|
1598
|
+
afterRender({ timings }) {
|
|
1599
|
+
pendingTimingSnapshot = summarizeFrameTimings(timings, frameBudgetMs);
|
|
1600
|
+
if (!needsTimingHydrationRender)
|
|
1601
|
+
return;
|
|
1602
|
+
needsTimingHydrationRender = false;
|
|
1603
|
+
return { requestRender: true };
|
|
1604
|
+
},
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
finally {
|
|
1608
|
+
hostedRunActive = false;
|
|
1609
|
+
useRunScopedFrameCtx = false;
|
|
1610
|
+
frameCtx = previousFrameCtx;
|
|
1611
|
+
frameCtxShellThemeId = previousFrameCtxShellThemeId;
|
|
1612
|
+
defaultFrameCtx = previousDefaultFrameCtx;
|
|
1613
|
+
resolvedShellThemes = previousResolvedShellThemes;
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
return framedApp;
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Create and immediately run a batteries-included framed shell.
|
|
1620
|
+
*
|
|
1621
|
+
* This is the one-call hosted path for users who want the frame to own the
|
|
1622
|
+
* runtime pump while `run(app)` remains the low-level TEA contract.
|
|
1623
|
+
*/
|
|
1624
|
+
export async function runFramedApp(options, runOptions) {
|
|
1625
|
+
await createFramedApp(options).run(runOptions);
|
|
1447
1626
|
}
|
|
1448
1627
|
function focusPane(model, paneId) {
|
|
1449
1628
|
if (model.focusedPaneByPage[model.activePageId] === paneId)
|
|
@@ -1459,431 +1638,4 @@ function focusPane(model, paneId) {
|
|
|
1459
1638
|
function resolveBodyRect(model, options) {
|
|
1460
1639
|
return frameBodyRect(model.columns, model.rows, options.bodyTopRows ?? 1, options.bodyBottomRows ?? 1);
|
|
1461
1640
|
}
|
|
1462
|
-
function renderHelpOverlay(model, activePage, frameKeys, paletteKeys, options, pagesById, shellThemes) {
|
|
1463
|
-
const activePageModel = model.pageModels[model.activePageId];
|
|
1464
|
-
const activeInputArea = findInputAreaByPaneId(resolveInputAreas(activePage, activePageModel), model.focusedPaneByPage[model.activePageId]);
|
|
1465
|
-
const modalKeyMap = activePage.modalKeyMap?.(activePageModel);
|
|
1466
|
-
const settings = resolveFrameSettings(model, options, pagesById, shellThemes);
|
|
1467
|
-
const notificationCenter = resolveFrameNotificationCenter(model, options, pagesById);
|
|
1468
|
-
const workspaceHintSource = options.helpLineSource?.({
|
|
1469
|
-
model,
|
|
1470
|
-
activePage,
|
|
1471
|
-
frameKeys,
|
|
1472
|
-
globalKeys: options.globalKeys,
|
|
1473
|
-
});
|
|
1474
|
-
const workspaceHelpSource = mergeBindingSources(frameKeys, quitHelpKeys, options.globalKeys, activeInputArea?.helpSource ?? activeInputArea?.keyMap, activePage.helpSource ?? activePage.keyMap);
|
|
1475
|
-
const layerStack = describeFrameLayerStack(model, {
|
|
1476
|
-
pageModalOpen: modalKeyMap != null,
|
|
1477
|
-
layers: {
|
|
1478
|
-
workspace: {
|
|
1479
|
-
title: activePage.title,
|
|
1480
|
-
hintSource: typeof workspaceHintSource === 'string'
|
|
1481
|
-
? workspaceHintSource
|
|
1482
|
-
: workspaceHintSource ?? mergeBindingSources(frameKeys, options.globalKeys, activeInputArea?.helpSource ?? activeInputArea?.keyMap, activePage.helpSource ?? activePage.keyMap),
|
|
1483
|
-
helpSource: workspaceHelpSource,
|
|
1484
|
-
},
|
|
1485
|
-
'page-modal': {
|
|
1486
|
-
title: activePage.title,
|
|
1487
|
-
hintSource: modalKeyMap ?? activePage.helpSource ?? activePage.keyMap,
|
|
1488
|
-
helpSource: mergeBindingSources(quitHelpKeys, modalKeyMap, activePage.helpSource ?? activePage.keyMap),
|
|
1489
|
-
},
|
|
1490
|
-
settings: {
|
|
1491
|
-
title: settings?.title ?? frameMessage(options.i18n, 'settings.title', 'Settings'),
|
|
1492
|
-
hintSource: frameMessage(options.i18n, 'settings.footer', 'F2/Esc close • ↑/↓ rows • Enter toggle • / search • q quit'),
|
|
1493
|
-
helpSource: mergeBindingSources(settingsHelpKeys, quitHelpKeys),
|
|
1494
|
-
},
|
|
1495
|
-
help: {
|
|
1496
|
-
title: frameMessage(options.i18n, 'help.title', 'Keyboard Help'),
|
|
1497
|
-
hintSource: frameMessage(options.i18n, 'help.hint', 'j/k scroll • d/u page • g/G top/bottom • mouse wheel • ?/Esc close'),
|
|
1498
|
-
helpSource: helpLayerHelpKeys,
|
|
1499
|
-
},
|
|
1500
|
-
'notification-center': {
|
|
1501
|
-
title: notificationCenter == null
|
|
1502
|
-
? frameMessage(options.i18n, 'notifications.title', 'Notifications')
|
|
1503
|
-
: `${notificationCenter.title} • ${frameNotificationFilterLabel(options.i18n, notificationCenter.activeFilter)}`,
|
|
1504
|
-
hintSource: frameMessage(options.i18n, 'notifications.footer', 'Shift+N close • f filter • j/k scroll • q quit'),
|
|
1505
|
-
helpSource: mergeBindingSources(notificationCenterHelpKeys, quitHelpKeys),
|
|
1506
|
-
},
|
|
1507
|
-
search: {
|
|
1508
|
-
title: model.commandPaletteTitle ?? activePage.searchTitle ?? frameMessage(options.i18n, 'search.title', 'Search'),
|
|
1509
|
-
hintSource: frameMessage(options.i18n, 'palette.hint', 'Enter select • Esc close'),
|
|
1510
|
-
helpSource: mergeBindingSources(paletteKeys, quitHelpKeys),
|
|
1511
|
-
},
|
|
1512
|
-
'command-palette': {
|
|
1513
|
-
title: model.commandPaletteTitle ?? frameMessage(options.i18n, 'palette.title', 'Command Palette'),
|
|
1514
|
-
hintSource: frameMessage(options.i18n, 'palette.hint', 'Enter select • Esc close'),
|
|
1515
|
-
helpSource: mergeBindingSources(paletteKeys, quitHelpKeys),
|
|
1516
|
-
},
|
|
1517
|
-
'quit-confirm': {
|
|
1518
|
-
title: frameMessage(options.i18n, 'quit.title', 'Quit?'),
|
|
1519
|
-
hintSource: frameMessage(options.i18n, 'quit.footer', 'Y quit • N stay'),
|
|
1520
|
-
helpSource: quitConfirmHelpKeys,
|
|
1521
|
-
},
|
|
1522
|
-
},
|
|
1523
|
-
});
|
|
1524
|
-
const beneathHelpLayer = layerStack.length > 1 ? layerStack[layerStack.length - 2] : undefined;
|
|
1525
|
-
const source = beneathHelpLayer?.helpSource ?? workspaceHelpSource;
|
|
1526
|
-
const maxDialogWidth = Math.max(28, Math.min(model.columns - 4, 88));
|
|
1527
|
-
const bodyWidth = Math.max(20, maxDialogWidth - 4);
|
|
1528
|
-
const helpSurface = helpViewSurface(source, {
|
|
1529
|
-
title: undefined,
|
|
1530
|
-
width: bodyWidth,
|
|
1531
|
-
});
|
|
1532
|
-
const pagerHeight = Math.max(4, Math.min(helpSurface.height + 1, Math.max(4, model.rows - 8)));
|
|
1533
|
-
const pagerState = createPagerStateForSurface(helpSurface, {
|
|
1534
|
-
width: bodyWidth,
|
|
1535
|
-
height: pagerHeight,
|
|
1536
|
-
});
|
|
1537
|
-
const scrollY = Math.max(0, Math.min(model.helpScrollY, pagerState.scroll.maxY));
|
|
1538
|
-
const scrolledState = {
|
|
1539
|
-
...pagerState,
|
|
1540
|
-
scroll: {
|
|
1541
|
-
...pagerState.scroll,
|
|
1542
|
-
y: scrollY,
|
|
1543
|
-
},
|
|
1544
|
-
};
|
|
1545
|
-
return {
|
|
1546
|
-
body: pagerSurface(helpSurface, scrolledState, { showScrollbar: true, showStatus: true }),
|
|
1547
|
-
maxScrollY: pagerState.scroll.maxY,
|
|
1548
|
-
scrollY,
|
|
1549
|
-
};
|
|
1550
|
-
}
|
|
1551
|
-
function isHelpScrollAction(action) {
|
|
1552
|
-
return action.type === 'scroll-up'
|
|
1553
|
-
|| action.type === 'scroll-down'
|
|
1554
|
-
|| action.type === 'page-up'
|
|
1555
|
-
|| action.type === 'page-down'
|
|
1556
|
-
|| action.type === 'top'
|
|
1557
|
-
|| action.type === 'bottom';
|
|
1558
|
-
}
|
|
1559
|
-
const FRAME_SHELL_THEME_ROW_ID = '__frame-shell-theme__';
|
|
1560
|
-
function resolveFrameSettings(model, options, pagesById, shellThemes) {
|
|
1561
|
-
const activePage = pagesById.get(model.activePageId);
|
|
1562
|
-
const provided = options.settings?.({
|
|
1563
|
-
model,
|
|
1564
|
-
activePage,
|
|
1565
|
-
pageModel: model.pageModels[model.activePageId],
|
|
1566
|
-
});
|
|
1567
|
-
return mergeShellThemeSettings(provided, shellThemes, model.activeShellThemeId, options.i18n);
|
|
1568
|
-
}
|
|
1569
|
-
function resolveFrameNotificationCenter(model, options, pagesById) {
|
|
1570
|
-
const activePage = pagesById.get(model.activePageId);
|
|
1571
|
-
const pageModel = model.pageModels[model.activePageId];
|
|
1572
|
-
const provided = options.notificationCenter?.({
|
|
1573
|
-
model,
|
|
1574
|
-
activePage,
|
|
1575
|
-
pageModel,
|
|
1576
|
-
runtimeNotifications: model.runtimeNotifications,
|
|
1577
|
-
});
|
|
1578
|
-
if (provided != null) {
|
|
1579
|
-
const filters = provided.filters != null && provided.filters.length > 0
|
|
1580
|
-
? provided.filters
|
|
1581
|
-
: DEFAULT_NOTIFICATION_CENTER_FILTERS;
|
|
1582
|
-
const activeFilter = filters.includes(provided.activeFilter ?? 'ALL')
|
|
1583
|
-
? (provided.activeFilter ?? 'ALL')
|
|
1584
|
-
: filters[0];
|
|
1585
|
-
return {
|
|
1586
|
-
title: provided.title ?? frameMessage(options.i18n, 'notifications.title', 'Notifications'),
|
|
1587
|
-
state: provided.state,
|
|
1588
|
-
filters,
|
|
1589
|
-
activeFilter,
|
|
1590
|
-
onFilterChange: provided.onFilterChange,
|
|
1591
|
-
};
|
|
1592
|
-
}
|
|
1593
|
-
if (options.runtimeNotifications === false)
|
|
1594
|
-
return undefined;
|
|
1595
|
-
return {
|
|
1596
|
-
title: frameMessage(options.i18n, 'notifications.title', 'Notifications'),
|
|
1597
|
-
state: model.runtimeNotifications,
|
|
1598
|
-
filters: DEFAULT_NOTIFICATION_CENTER_FILTERS,
|
|
1599
|
-
activeFilter: model.runtimeNotificationHistoryFilter,
|
|
1600
|
-
};
|
|
1601
|
-
}
|
|
1602
|
-
function resolveSettingsLayout(model, options, pagesById, shellThemes) {
|
|
1603
|
-
const settings = resolveFrameSettings(model, options, pagesById, shellThemes);
|
|
1604
|
-
if (settings == null)
|
|
1605
|
-
return undefined;
|
|
1606
|
-
const sections = settings.sections.filter((section) => section.rows.length > 0);
|
|
1607
|
-
if (sections.length === 0)
|
|
1608
|
-
return undefined;
|
|
1609
|
-
const drawerWidth = resolveSettingsDrawerWidth(model.columns);
|
|
1610
|
-
const anchor = frameStartAnchor(options.i18n);
|
|
1611
|
-
const startCol = anchor === 'left' ? 0 : Math.max(0, model.columns - drawerWidth);
|
|
1612
|
-
const contentWidth = Math.max(16, drawerWidth - 4);
|
|
1613
|
-
const preferenceSections = preparePreferenceSections(toPreferenceSections(sections));
|
|
1614
|
-
const rows = [];
|
|
1615
|
-
let line = 0;
|
|
1616
|
-
for (let sectionIndex = 0; sectionIndex < preferenceSections.length; sectionIndex++) {
|
|
1617
|
-
const section = preferenceSections[sectionIndex];
|
|
1618
|
-
if (sectionIndex > 0) {
|
|
1619
|
-
line += 1;
|
|
1620
|
-
}
|
|
1621
|
-
line += 1;
|
|
1622
|
-
line += 1;
|
|
1623
|
-
for (let rowIndex = 0; rowIndex < section.rows.length; rowIndex++) {
|
|
1624
|
-
const preparedRow = section.rows[rowIndex];
|
|
1625
|
-
const row = sections[sectionIndex].rows[rowIndex];
|
|
1626
|
-
const rowLayout = resolvePreferenceRowLayout(preparedRow, contentWidth);
|
|
1627
|
-
rows.push({
|
|
1628
|
-
index: rows.length,
|
|
1629
|
-
line,
|
|
1630
|
-
height: rowLayout.height,
|
|
1631
|
-
row,
|
|
1632
|
-
behavior: row.id === FRAME_SHELL_THEME_ROW_ID ? 'cycle-shell-theme' : undefined,
|
|
1633
|
-
});
|
|
1634
|
-
line += rowLayout.height;
|
|
1635
|
-
if (rowIndex < section.rows.length - 1) {
|
|
1636
|
-
line += 1;
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
const contentHeight = Math.max(1, model.rows - 2);
|
|
1641
|
-
const totalLines = Math.max(1, line);
|
|
1642
|
-
const maxScrollY = Math.max(0, totalLines - contentHeight);
|
|
1643
|
-
return {
|
|
1644
|
-
settings: {
|
|
1645
|
-
...settings,
|
|
1646
|
-
sections,
|
|
1647
|
-
},
|
|
1648
|
-
preferenceSections,
|
|
1649
|
-
rows,
|
|
1650
|
-
anchor,
|
|
1651
|
-
startCol,
|
|
1652
|
-
drawerWidth,
|
|
1653
|
-
contentWidth,
|
|
1654
|
-
contentHeight,
|
|
1655
|
-
totalLines,
|
|
1656
|
-
maxScrollY,
|
|
1657
|
-
};
|
|
1658
|
-
}
|
|
1659
|
-
function resolveNotificationCenterDrawerWidth(columns) {
|
|
1660
|
-
const boundedColumns = Math.max(28, columns);
|
|
1661
|
-
return Math.min(Math.max(32, Math.floor(boundedColumns * 0.34)), Math.max(32, boundedColumns - 4), 52);
|
|
1662
|
-
}
|
|
1663
|
-
function resolveNotificationCenterLayout(model, options, pagesById, ctx) {
|
|
1664
|
-
const center = resolveFrameNotificationCenter(model, options, pagesById);
|
|
1665
|
-
if (center == null)
|
|
1666
|
-
return undefined;
|
|
1667
|
-
const drawerWidth = resolveNotificationCenterDrawerWidth(model.columns);
|
|
1668
|
-
const anchor = frameEndAnchor(options.i18n);
|
|
1669
|
-
const startCol = anchor === 'left' ? 0 : Math.max(0, model.columns - drawerWidth);
|
|
1670
|
-
const contentWidth = Math.max(18, drawerWidth - 4);
|
|
1671
|
-
const content = renderNotificationCenterSurface(center, contentWidth, options.i18n, ctx);
|
|
1672
|
-
const contentHeight = Math.max(1, model.rows - 2);
|
|
1673
|
-
const pagerState = createPagerStateForSurface(content, {
|
|
1674
|
-
width: contentWidth,
|
|
1675
|
-
height: contentHeight,
|
|
1676
|
-
});
|
|
1677
|
-
return {
|
|
1678
|
-
center,
|
|
1679
|
-
anchor,
|
|
1680
|
-
startCol,
|
|
1681
|
-
drawerWidth,
|
|
1682
|
-
contentWidth,
|
|
1683
|
-
contentHeight,
|
|
1684
|
-
content,
|
|
1685
|
-
maxScrollY: pagerState.scroll.maxY,
|
|
1686
|
-
};
|
|
1687
|
-
}
|
|
1688
|
-
function resolveSettingsDrawerWidth(columns) {
|
|
1689
|
-
const boundedColumns = Math.max(24, columns);
|
|
1690
|
-
return Math.min(Math.max(28, Math.floor(boundedColumns * 0.3)), Math.max(28, boundedColumns - 4), 42);
|
|
1691
|
-
}
|
|
1692
|
-
function clampSettingsFocus(model, layout) {
|
|
1693
|
-
if (layout.rows.length === 0)
|
|
1694
|
-
return 0;
|
|
1695
|
-
return Math.max(0, Math.min(model.settingsFocusIndex, layout.rows.length - 1));
|
|
1696
|
-
}
|
|
1697
|
-
function clampSettingsScroll(model, layout) {
|
|
1698
|
-
return Math.max(0, Math.min(model.settingsScrollY, layout.maxScrollY));
|
|
1699
|
-
}
|
|
1700
|
-
function resolveInputAreas(page, pageModel) {
|
|
1701
|
-
return page.inputAreas?.(pageModel) ?? [];
|
|
1702
|
-
}
|
|
1703
|
-
function findInputAreaByPaneId(inputAreas, paneId) {
|
|
1704
|
-
if (paneId == null)
|
|
1705
|
-
return undefined;
|
|
1706
|
-
return inputAreas.find((area) => area.paneId === paneId);
|
|
1707
|
-
}
|
|
1708
|
-
function ensureSettingsRangeVisible(startLine, height, scrollY, visibleLines, maxScrollY) {
|
|
1709
|
-
let next = scrollY;
|
|
1710
|
-
const endLine = startLine + Math.max(1, height) - 1;
|
|
1711
|
-
if (startLine < next) {
|
|
1712
|
-
next = startLine;
|
|
1713
|
-
}
|
|
1714
|
-
else if (endLine >= next + visibleLines) {
|
|
1715
|
-
next = endLine - visibleLines + 1;
|
|
1716
|
-
}
|
|
1717
|
-
return Math.max(0, Math.min(next, maxScrollY));
|
|
1718
|
-
}
|
|
1719
|
-
function moveSettingsFocus(model, layout, delta) {
|
|
1720
|
-
if (layout.rows.length === 0)
|
|
1721
|
-
return model;
|
|
1722
|
-
const nextFocus = Math.max(0, Math.min(clampSettingsFocus(model, layout) + delta, layout.rows.length - 1));
|
|
1723
|
-
const focusedRow = layout.rows[nextFocus];
|
|
1724
|
-
return {
|
|
1725
|
-
...model,
|
|
1726
|
-
settingsFocusIndex: nextFocus,
|
|
1727
|
-
settingsScrollY: ensureSettingsRangeVisible(focusedRow.line, focusedRow.height, clampSettingsScroll(model, layout), layout.contentHeight, layout.maxScrollY),
|
|
1728
|
-
};
|
|
1729
|
-
}
|
|
1730
|
-
function scrollSettingsBy(model, layout, delta) {
|
|
1731
|
-
return {
|
|
1732
|
-
...model,
|
|
1733
|
-
settingsScrollY: Math.max(0, Math.min(clampSettingsScroll(model, layout) + delta, layout.maxScrollY)),
|
|
1734
|
-
};
|
|
1735
|
-
}
|
|
1736
|
-
function scrollNotificationCenterBy(model, layout, delta) {
|
|
1737
|
-
return {
|
|
1738
|
-
...model,
|
|
1739
|
-
notificationCenterScrollY: Math.max(0, Math.min(model.notificationCenterScrollY + delta, layout.maxScrollY)),
|
|
1740
|
-
};
|
|
1741
|
-
}
|
|
1742
|
-
function cycleNotificationCenterFilter(model, layout) {
|
|
1743
|
-
const filters = layout.center.filters;
|
|
1744
|
-
if (filters.length < 2)
|
|
1745
|
-
return [model, []];
|
|
1746
|
-
const currentIndex = Math.max(0, filters.indexOf(layout.center.activeFilter));
|
|
1747
|
-
const nextFilter = filters[(currentIndex + 1) % filters.length];
|
|
1748
|
-
if (layout.center.onFilterChange != null) {
|
|
1749
|
-
const action = layout.center.onFilterChange(nextFilter);
|
|
1750
|
-
return [{
|
|
1751
|
-
...model,
|
|
1752
|
-
notificationCenterScrollY: 0,
|
|
1753
|
-
}, action === undefined ? [] : [emitMsgForPage(model.activePageId, action)]];
|
|
1754
|
-
}
|
|
1755
|
-
return [{
|
|
1756
|
-
...model,
|
|
1757
|
-
runtimeNotificationHistoryFilter: nextFilter,
|
|
1758
|
-
notificationCenterScrollY: 0,
|
|
1759
|
-
}, []];
|
|
1760
|
-
}
|
|
1761
|
-
function renderSettingsDrawer(model, options, pagesById, shellThemes, titleOverride, ctx) {
|
|
1762
|
-
const layout = resolveSettingsLayout(model, options, pagesById, shellThemes);
|
|
1763
|
-
if (layout == null)
|
|
1764
|
-
return undefined;
|
|
1765
|
-
const scrollY = clampSettingsScroll(model, layout);
|
|
1766
|
-
const content = renderSettingsSurface(layout, model, ctx);
|
|
1767
|
-
const pagerState = createPagerStateForSurface(content, {
|
|
1768
|
-
width: layout.contentWidth,
|
|
1769
|
-
height: layout.contentHeight,
|
|
1770
|
-
});
|
|
1771
|
-
const scrolledState = {
|
|
1772
|
-
...pagerState,
|
|
1773
|
-
scroll: {
|
|
1774
|
-
...pagerState.scroll,
|
|
1775
|
-
y: scrollY,
|
|
1776
|
-
},
|
|
1777
|
-
};
|
|
1778
|
-
const body = pagerSurface(content, scrolledState, {
|
|
1779
|
-
showScrollbar: layout.maxScrollY > 0,
|
|
1780
|
-
showStatus: false,
|
|
1781
|
-
});
|
|
1782
|
-
return drawer({
|
|
1783
|
-
anchor: layout.anchor,
|
|
1784
|
-
title: titleOverride ?? layout.settings.title ?? frameMessage(options.i18n, 'settings.title', 'Settings'),
|
|
1785
|
-
content: body,
|
|
1786
|
-
borderToken: layout.settings.borderToken,
|
|
1787
|
-
bgToken: layout.settings.bgToken,
|
|
1788
|
-
ctx,
|
|
1789
|
-
width: layout.drawerWidth,
|
|
1790
|
-
screenWidth: model.columns,
|
|
1791
|
-
screenHeight: model.rows,
|
|
1792
|
-
});
|
|
1793
|
-
}
|
|
1794
|
-
function renderNotificationCenterDrawer(model, options, pagesById, titleOverride, ctx) {
|
|
1795
|
-
const layout = resolveNotificationCenterLayout(model, options, pagesById, ctx);
|
|
1796
|
-
if (layout == null)
|
|
1797
|
-
return undefined;
|
|
1798
|
-
const pagerState = createPagerStateForSurface(layout.content, {
|
|
1799
|
-
width: layout.contentWidth,
|
|
1800
|
-
height: layout.contentHeight,
|
|
1801
|
-
});
|
|
1802
|
-
const scrolledState = {
|
|
1803
|
-
...pagerState,
|
|
1804
|
-
scroll: {
|
|
1805
|
-
...pagerState.scroll,
|
|
1806
|
-
y: Math.max(0, Math.min(model.notificationCenterScrollY, layout.maxScrollY)),
|
|
1807
|
-
},
|
|
1808
|
-
};
|
|
1809
|
-
const body = pagerSurface(layout.content, scrolledState, {
|
|
1810
|
-
showScrollbar: layout.maxScrollY > 0,
|
|
1811
|
-
showStatus: false,
|
|
1812
|
-
});
|
|
1813
|
-
return drawer({
|
|
1814
|
-
anchor: layout.anchor,
|
|
1815
|
-
title: titleOverride ?? `${layout.center.title} • ${frameNotificationFilterLabel(options.i18n, layout.center.activeFilter)}`,
|
|
1816
|
-
content: body,
|
|
1817
|
-
borderToken: ctx?.border('primary'),
|
|
1818
|
-
bgToken: ctx?.surface('elevated'),
|
|
1819
|
-
ctx,
|
|
1820
|
-
width: layout.drawerWidth,
|
|
1821
|
-
screenWidth: model.columns,
|
|
1822
|
-
screenHeight: model.rows,
|
|
1823
|
-
});
|
|
1824
|
-
}
|
|
1825
|
-
function renderSettingsSurface(layout, model, ctx) {
|
|
1826
|
-
const focusedIndex = clampSettingsFocus(model, layout);
|
|
1827
|
-
return preferenceListSurface(layout.preferenceSections, {
|
|
1828
|
-
width: layout.contentWidth,
|
|
1829
|
-
selectedRowId: layout.rows[focusedIndex]?.row.id,
|
|
1830
|
-
ctx,
|
|
1831
|
-
theme: layout.settings.listTheme,
|
|
1832
|
-
});
|
|
1833
|
-
}
|
|
1834
|
-
function toPreferenceSections(sections) {
|
|
1835
|
-
return sections.map((section) => ({
|
|
1836
|
-
id: section.id,
|
|
1837
|
-
title: section.title,
|
|
1838
|
-
rows: section.rows.map((row) => toPreferenceRow(row)),
|
|
1839
|
-
}));
|
|
1840
|
-
}
|
|
1841
|
-
function toPreferenceRow(row) {
|
|
1842
|
-
return {
|
|
1843
|
-
id: row.id,
|
|
1844
|
-
label: row.label,
|
|
1845
|
-
description: row.description,
|
|
1846
|
-
valueLabel: row.valueLabel,
|
|
1847
|
-
kind: row.kind,
|
|
1848
|
-
checked: row.checked,
|
|
1849
|
-
enabled: row.enabled,
|
|
1850
|
-
};
|
|
1851
|
-
}
|
|
1852
|
-
function resolveNotificationFooterCue(model, options, pagesById) {
|
|
1853
|
-
const center = resolveFrameNotificationCenter(model, options, pagesById);
|
|
1854
|
-
if (center == null)
|
|
1855
|
-
return undefined;
|
|
1856
|
-
const liveCount = center.state.items.length;
|
|
1857
|
-
const archivedCount = countNotificationHistory(center.state, center.activeFilter);
|
|
1858
|
-
return frameNotificationCue(options.i18n, liveCount, archivedCount);
|
|
1859
|
-
}
|
|
1860
|
-
function renderNotificationCenterSurface(center, width, i18n, ctx) {
|
|
1861
|
-
const rows = [
|
|
1862
|
-
insetLineSurface(`Live: ${center.state.items.length} • Archived: ${center.state.history.length}`, width),
|
|
1863
|
-
insetLineSurface(`Filter: ${frameNotificationFilterLabel(i18n, center.activeFilter)}`, width),
|
|
1864
|
-
];
|
|
1865
|
-
const liveItems = [...center.state.items].sort((left, right) => right.updatedAtMs - left.updatedAtMs || right.id - left.id);
|
|
1866
|
-
if (liveItems.length > 0) {
|
|
1867
|
-
rows.push(createSurface(width, 1));
|
|
1868
|
-
rows.push(insetLineSurface(ctx == null ? 'Current stack' : ctx.style.bold('Current stack'), width));
|
|
1869
|
-
rows.push(createSurface(width, 1));
|
|
1870
|
-
for (let index = 0; index < liveItems.length; index++) {
|
|
1871
|
-
rows.push(renderNotificationReviewEntrySurface(liveItems[index], {
|
|
1872
|
-
width,
|
|
1873
|
-
ctx,
|
|
1874
|
-
metaLabel: `${liveItems[index].variant} • live`,
|
|
1875
|
-
}));
|
|
1876
|
-
if (index < liveItems.length - 1)
|
|
1877
|
-
rows.push(createSurface(width, 1));
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
rows.push(createSurface(width, 1));
|
|
1881
|
-
rows.push(renderNotificationHistorySurface(center.state, {
|
|
1882
|
-
width,
|
|
1883
|
-
height: Number.MAX_SAFE_INTEGER,
|
|
1884
|
-
filter: center.activeFilter,
|
|
1885
|
-
ctx,
|
|
1886
|
-
}));
|
|
1887
|
-
return vstackSurface(...rows);
|
|
1888
|
-
}
|
|
1889
1641
|
//# sourceMappingURL=app-frame.js.map
|