@flyingrobots/bijou-tui 4.4.0 → 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 +102 -600
- package/dist/app-frame-actions.d.ts.map +1 -1
- package/dist/app-frame-actions.js +6 -1
- 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 +53 -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.d.ts +8 -8
- package/dist/app-frame-render.d.ts.map +1 -1
- package/dist/app-frame-render.js +84 -25
- package/dist/app-frame-render.js.map +1 -1
- package/dist/app-frame-types.d.ts +122 -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 +43 -83
- package/dist/app-frame.d.ts.map +1 -1
- package/dist/app-frame.js +499 -575
- 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/command-palette.d.ts +5 -3
- package/dist/command-palette.d.ts.map +1 -1
- package/dist/command-palette.js +5 -3
- package/dist/command-palette.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 +69 -40
- 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 +50 -19
- 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/shell-quit.d.ts +1 -1
- package/dist/shell-quit.d.ts.map +1 -1
- package/dist/shell-quit.js +14 -3
- package/dist/shell-quit.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 {
|
|
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
|
-
import {
|
|
15
|
-
import { createPagerStateForSurface, pagerSurface, } from './pager.js';
|
|
14
|
+
import { commandPaletteSurface, commandPaletteKeyMap, } from './command-palette.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 {
|
|
@@ -134,6 +140,48 @@ function createFrameNotificationTickCmd() {
|
|
|
134
140
|
});
|
|
135
141
|
};
|
|
136
142
|
}
|
|
143
|
+
function cloneShellThemeContext(ctx, resolvedTheme) {
|
|
144
|
+
return cloneContextWithResolvedTheme(ctx, resolvedTheme);
|
|
145
|
+
}
|
|
146
|
+
function readStageDuration(timings, stage) {
|
|
147
|
+
return timings.find((timing) => timing.stage === stage)?.durationMs ?? 0;
|
|
148
|
+
}
|
|
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,
|
|
157
|
+
};
|
|
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;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
...model,
|
|
169
|
+
frameTimeMs: snapshot.frameTimeMs,
|
|
170
|
+
viewTimeMs: snapshot.viewTimeMs,
|
|
171
|
+
diffTimeMs: snapshot.diffTimeMs,
|
|
172
|
+
frameBudgetMs: snapshot.frameBudgetMs,
|
|
173
|
+
frameOverBudget: snapshot.frameOverBudget,
|
|
174
|
+
};
|
|
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
|
+
}
|
|
137
185
|
// Factory
|
|
138
186
|
// ---------------------------------------------------------------------------
|
|
139
187
|
/**
|
|
@@ -155,16 +203,84 @@ export function createFramedApp(options) {
|
|
|
155
203
|
if (!pagesById.has(defaultPageId)) {
|
|
156
204
|
throw new Error(`createFramedApp: defaultPageId "${defaultPageId}" not found in pages`);
|
|
157
205
|
}
|
|
206
|
+
const shellThemeSpecs = options.shellThemes ?? [];
|
|
207
|
+
let defaultFrameCtx = options.ctx ?? resolveSafeCtx();
|
|
208
|
+
let resolvedShellThemes = [];
|
|
209
|
+
const enableShellThemeSettings = shellThemeSpecs.length > 1;
|
|
210
|
+
const usesAmbientDefaultContext = options.ctx == null && defaultFrameCtx != null;
|
|
211
|
+
let frameCtx = options.ctx;
|
|
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
|
+
}
|
|
234
|
+
function resolveFrameCtx() {
|
|
235
|
+
return frameCtx ?? options.ctx ?? resolveSafeCtx();
|
|
236
|
+
}
|
|
237
|
+
function resolveFrameThemeCtx(activeShellThemeId) {
|
|
238
|
+
const baseCtx = resolveFrameCtx();
|
|
239
|
+
ensureResolvedShellThemes(baseCtx);
|
|
240
|
+
if (defaultFrameCtx == null)
|
|
241
|
+
return baseCtx;
|
|
242
|
+
const activeTheme = resolveCurrentShellTheme(resolvedShellThemes, activeShellThemeId);
|
|
243
|
+
if (activeTheme == null)
|
|
244
|
+
return baseCtx;
|
|
245
|
+
if (frameCtx != null && frameCtxShellThemeId === activeTheme.id) {
|
|
246
|
+
return frameCtx;
|
|
247
|
+
}
|
|
248
|
+
if (resolveShellThemeForContext(resolvedShellThemes, baseCtx)?.id === activeTheme.id) {
|
|
249
|
+
return baseCtx;
|
|
250
|
+
}
|
|
251
|
+
return cloneShellThemeContext(defaultFrameCtx, activeTheme.resolvedTheme);
|
|
252
|
+
}
|
|
253
|
+
function publishShellThemeContext(nextTheme) {
|
|
254
|
+
ensureResolvedShellThemes(resolveFrameCtx());
|
|
255
|
+
if (defaultFrameCtx == null)
|
|
256
|
+
return resolveFrameCtx();
|
|
257
|
+
frameCtx = cloneShellThemeContext(defaultFrameCtx, nextTheme.resolvedTheme);
|
|
258
|
+
frameCtxShellThemeId = nextTheme.id;
|
|
259
|
+
if (usesAmbientDefaultContext && !useRunScopedFrameCtx) {
|
|
260
|
+
setDefaultContext(frameCtx);
|
|
261
|
+
}
|
|
262
|
+
options.onShellThemeChange?.({
|
|
263
|
+
shellTheme: nextTheme.shellTheme,
|
|
264
|
+
ctx: frameCtx,
|
|
265
|
+
});
|
|
266
|
+
return frameCtx;
|
|
267
|
+
}
|
|
158
268
|
const frameKeys = createFrameKeyMap({
|
|
159
|
-
enableSettings: options.settings != null,
|
|
269
|
+
enableSettings: options.settings != null || enableShellThemeSettings,
|
|
160
270
|
enableNotifications: options.notificationCenter != null || options.runtimeNotifications !== false,
|
|
161
271
|
i18n: options.i18n,
|
|
162
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);
|
|
163
278
|
const frameNotificationOptions = resolveFrameNotificationOptions(options);
|
|
164
279
|
let composedFrameScratch = null;
|
|
165
280
|
let headerScratch;
|
|
166
281
|
let helpLineScratch;
|
|
167
282
|
const paneScratchPool = createFramePaneScratchPool();
|
|
283
|
+
let workspaceLayoutCache;
|
|
168
284
|
const paletteKeys = commandPaletteKeyMap({
|
|
169
285
|
focusNext: { type: 'cp-next' },
|
|
170
286
|
focusPrev: { type: 'cp-prev' },
|
|
@@ -173,6 +289,49 @@ export function createFramedApp(options) {
|
|
|
173
289
|
select: { type: 'cp-select' },
|
|
174
290
|
close: { type: 'cp-close' },
|
|
175
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
|
+
}
|
|
176
335
|
function getComposedFrameScratch(width, height) {
|
|
177
336
|
if (composedFrameScratch == null
|
|
178
337
|
|| composedFrameScratch.width !== width
|
|
@@ -221,30 +380,35 @@ export function createFramedApp(options) {
|
|
|
221
380
|
// --- settings ---
|
|
222
381
|
'settings-focus-move': (model, cmd) => {
|
|
223
382
|
const c = cmd;
|
|
224
|
-
const layout = resolveSettingsLayout(model, options, pagesById);
|
|
383
|
+
const layout = resolveSettingsLayout(model, options, pagesById, resolvedShellThemes);
|
|
225
384
|
return layout != null ? moveSettingsFocus(model, layout, c.delta) : model;
|
|
226
385
|
},
|
|
227
386
|
'settings-scroll': (model, cmd) => {
|
|
228
387
|
const c = cmd;
|
|
229
|
-
const layout = resolveSettingsLayout(model, options, pagesById);
|
|
388
|
+
const layout = resolveSettingsLayout(model, options, pagesById, resolvedShellThemes);
|
|
230
389
|
return layout != null ? scrollSettingsBy(model, layout, c.delta) : model;
|
|
231
390
|
},
|
|
232
391
|
'settings-scroll-to': (model, cmd) => {
|
|
233
392
|
const c = cmd;
|
|
234
|
-
const layout = resolveSettingsLayout(model, options, pagesById);
|
|
393
|
+
const layout = resolveSettingsLayout(model, options, pagesById, resolvedShellThemes);
|
|
235
394
|
if (layout == null)
|
|
236
395
|
return model;
|
|
237
396
|
return { ...model, settingsScrollY: c.position === 'top' ? 0 : layout.maxScrollY };
|
|
238
397
|
},
|
|
239
398
|
'activate-settings-row': (model, cmd, teaCmds) => {
|
|
240
399
|
const c = cmd;
|
|
241
|
-
const layout = resolveSettingsLayout(model, options, pagesById);
|
|
400
|
+
const layout = resolveSettingsLayout(model, options, pagesById, resolvedShellThemes);
|
|
242
401
|
if (layout == null)
|
|
243
402
|
return model;
|
|
244
403
|
const hitRow = layout.rows.find((r) => r.index === c.rowIndex);
|
|
245
404
|
if (hitRow == null)
|
|
246
405
|
return model;
|
|
247
406
|
const focusedModel = { ...model, settingsFocusIndex: hitRow.index };
|
|
407
|
+
if (hitRow.behavior === 'cycle-shell-theme') {
|
|
408
|
+
const [nextModel, cmds] = cycleShellThemeSetting(focusedModel, hitRow.row);
|
|
409
|
+
teaCmds.push(...cmds);
|
|
410
|
+
return nextModel;
|
|
411
|
+
}
|
|
248
412
|
if (hitRow.row.action === undefined || hitRow.row.enabled === false || hitRow.row.kind === 'info') {
|
|
249
413
|
return focusedModel;
|
|
250
414
|
}
|
|
@@ -255,29 +419,36 @@ export function createFramedApp(options) {
|
|
|
255
419
|
// --- notification center ---
|
|
256
420
|
'notification-center-scroll': (model, cmd) => {
|
|
257
421
|
const c = cmd;
|
|
258
|
-
const layout = resolveNotificationCenterLayout(model, options, pagesById);
|
|
422
|
+
const layout = resolveNotificationCenterLayout(model, options, pagesById, resolveFrameThemeCtx(model.activeShellThemeId));
|
|
259
423
|
return layout != null ? scrollNotificationCenterBy(model, layout, c.delta) : model;
|
|
260
424
|
},
|
|
261
425
|
'notification-center-scroll-to': (model, cmd) => {
|
|
262
426
|
const c = cmd;
|
|
263
|
-
const layout = resolveNotificationCenterLayout(model, options, pagesById);
|
|
427
|
+
const layout = resolveNotificationCenterLayout(model, options, pagesById, resolveFrameThemeCtx(model.activeShellThemeId));
|
|
264
428
|
if (layout == null)
|
|
265
429
|
return model;
|
|
266
430
|
return { ...model, notificationCenterScrollY: c.position === 'top' ? 0 : layout.maxScrollY };
|
|
267
431
|
},
|
|
268
432
|
'cycle-notification-filter': (model, _cmd, teaCmds) => {
|
|
269
|
-
const layout = resolveNotificationCenterLayout(model, options, pagesById);
|
|
433
|
+
const layout = resolveNotificationCenterLayout(model, options, pagesById, resolveFrameThemeCtx(model.activeShellThemeId));
|
|
270
434
|
if (layout == null)
|
|
271
435
|
return model;
|
|
272
436
|
const [nextModel, cmds] = cycleNotificationCenterFilter(model, layout);
|
|
273
437
|
teaCmds.push(...cmds);
|
|
274
438
|
return nextModel;
|
|
275
439
|
},
|
|
440
|
+
'warn-frame-key-collision': (model, cmd, teaCmds) => {
|
|
441
|
+
const c = cmd;
|
|
442
|
+
return queueFrameKeyCollisionWarning(model, c.msg, teaCmds);
|
|
443
|
+
},
|
|
276
444
|
// --- help ---
|
|
277
445
|
'help-scroll': (model, cmd) => {
|
|
278
446
|
const c = cmd;
|
|
279
|
-
const
|
|
280
|
-
|
|
447
|
+
const helpSource = resolvePresentedLayerContext(model).controlProjection.helpSource;
|
|
448
|
+
if (helpSource == null) {
|
|
449
|
+
return model;
|
|
450
|
+
}
|
|
451
|
+
const overlay = renderHelpOverlay(model, helpSource, options.i18n);
|
|
281
452
|
const viewportHeight = Math.max(1, overlay.body.height - 1);
|
|
282
453
|
const delta = c.action === 'down' ? 3
|
|
283
454
|
: c.action === 'up' ? -3
|
|
@@ -337,7 +508,7 @@ export function createFramedApp(options) {
|
|
|
337
508
|
const c = cmd;
|
|
338
509
|
if (!frameNotificationOptions.enabled)
|
|
339
510
|
return model;
|
|
340
|
-
const nowMs = resolveClock(
|
|
511
|
+
const nowMs = resolveClock(resolveFrameCtx()).now();
|
|
341
512
|
const [nextModel, cmds] = applyFrameNotificationState(model, dismissNotification(model.runtimeNotifications, c.notificationId, nowMs), nowMs);
|
|
342
513
|
teaCmds.push(...cmds);
|
|
343
514
|
return nextModel;
|
|
@@ -436,7 +607,7 @@ export function createFramedApp(options) {
|
|
|
436
607
|
return [obs];
|
|
437
608
|
}
|
|
438
609
|
function handleSettingsLayerKeyCommands(msg, model) {
|
|
439
|
-
const layout = resolveSettingsLayout(model, options, pagesById);
|
|
610
|
+
const layout = resolveSettingsLayout(model, options, pagesById, resolvedShellThemes);
|
|
440
611
|
if (layout == null)
|
|
441
612
|
return undefined;
|
|
442
613
|
const obs = { type: 'observed-key', msg, route: 'frame' };
|
|
@@ -489,7 +660,10 @@ export function createFramedApp(options) {
|
|
|
489
660
|
if (!msg.ctrl && !msg.alt && (msg.key === 'enter' || msg.key === 'space')) {
|
|
490
661
|
const rowIndex = clampSettingsFocus(model, layout);
|
|
491
662
|
const row = layout.rows[rowIndex];
|
|
492
|
-
if (row
|
|
663
|
+
if (row != null
|
|
664
|
+
&& row.row.enabled !== false
|
|
665
|
+
&& row.row.kind !== 'info'
|
|
666
|
+
&& (row.behavior === 'cycle-shell-theme' || row.row.action !== undefined)) {
|
|
493
667
|
return [obs, { type: 'activate-settings-row', rowIndex: row.index }];
|
|
494
668
|
}
|
|
495
669
|
return [obs];
|
|
@@ -497,7 +671,7 @@ export function createFramedApp(options) {
|
|
|
497
671
|
return [obs];
|
|
498
672
|
}
|
|
499
673
|
function handleNotificationCenterLayerKeyCommands(msg, model) {
|
|
500
|
-
const layout = resolveNotificationCenterLayout(model, options, pagesById);
|
|
674
|
+
const layout = resolveNotificationCenterLayout(model, options, pagesById, resolveFrameThemeCtx(model.activeShellThemeId));
|
|
501
675
|
if (layout == null)
|
|
502
676
|
return undefined;
|
|
503
677
|
const obs = { type: 'observed-key', msg, route: 'frame' };
|
|
@@ -574,7 +748,9 @@ export function createFramedApp(options) {
|
|
|
574
748
|
}
|
|
575
749
|
// frame-first (default)
|
|
576
750
|
if (frameAction !== undefined) {
|
|
577
|
-
return
|
|
751
|
+
return pageAction !== undefined
|
|
752
|
+
? [...resolveFrameActionCommands(msg, frameAction, 'frame'), { type: 'warn-frame-key-collision', msg }]
|
|
753
|
+
: resolveFrameActionCommands(msg, frameAction, 'frame');
|
|
578
754
|
}
|
|
579
755
|
if (paneAction !== undefined) {
|
|
580
756
|
return [{ type: 'observed-key', msg, route: 'page' }, { type: 'emit-page-msg', pageId: model.activePageId, msg: paneAction }];
|
|
@@ -647,26 +823,15 @@ export function createFramedApp(options) {
|
|
|
647
823
|
children: children ?? [],
|
|
648
824
|
};
|
|
649
825
|
}
|
|
650
|
-
function
|
|
651
|
-
const
|
|
652
|
-
const
|
|
653
|
-
const maximizedPaneId = maxState?.maximizedPaneId;
|
|
654
|
-
const renderResult = maximizedPaneId
|
|
655
|
-
? renderMaximizedPane(model.activePageId, model, bodyRect, pagesById, maximizedPaneId, paneScratchPool)
|
|
656
|
-
: renderPageContent(model.activePageId, model, bodyRect, pagesById);
|
|
657
|
-
return renderResult.paneRects;
|
|
658
|
-
}
|
|
659
|
-
function buildWorkspaceLayoutTree(model) {
|
|
660
|
-
const header = resolveHeaderLine(model, options, pagesById, headerScratch);
|
|
661
|
-
headerScratch = header.surface;
|
|
662
|
-
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}`, {
|
|
663
829
|
row: 0,
|
|
664
830
|
col: target.startCol,
|
|
665
831
|
width: target.endCol - target.startCol + 1,
|
|
666
832
|
height: 1,
|
|
667
833
|
}));
|
|
668
834
|
const bodyRect = resolveBodyRect(model, options);
|
|
669
|
-
const paneRects = resolveWorkspacePaneRects(model);
|
|
670
835
|
const paneChildren = [];
|
|
671
836
|
for (const [paneId, rect] of paneRects.entries()) {
|
|
672
837
|
paneChildren.push(createShellRetainedLayoutNode(`pane:${paneId}`, rect));
|
|
@@ -676,6 +841,55 @@ export function createFramedApp(options) {
|
|
|
676
841
|
createShellRetainedLayoutNode('workspace-body', bodyRect, paneChildren),
|
|
677
842
|
]);
|
|
678
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
|
+
}
|
|
679
893
|
function buildSettingsRowChildren(model, layout) {
|
|
680
894
|
const scrollY = clampSettingsScroll(model, layout);
|
|
681
895
|
const viewportTop = 1;
|
|
@@ -698,7 +912,9 @@ export function createFramedApp(options) {
|
|
|
698
912
|
}
|
|
699
913
|
function resolveFrameMouseRuntimeLayouts(model) {
|
|
700
914
|
let layouts = EMPTY_RUNTIME_LAYOUTS;
|
|
701
|
-
const settingsLayout = model.settingsOpen
|
|
915
|
+
const settingsLayout = model.settingsOpen
|
|
916
|
+
? resolveSettingsLayout(model, options, pagesById, resolvedShellThemes)
|
|
917
|
+
: undefined;
|
|
702
918
|
if (settingsLayout != null) {
|
|
703
919
|
layouts = retainRuntimeLayout(layouts, {
|
|
704
920
|
viewId: 'settings',
|
|
@@ -710,7 +926,9 @@ export function createFramedApp(options) {
|
|
|
710
926
|
}, buildSettingsRowChildren(model, settingsLayout)),
|
|
711
927
|
});
|
|
712
928
|
}
|
|
713
|
-
const notificationCenterLayout = model.notificationCenterOpen
|
|
929
|
+
const notificationCenterLayout = model.notificationCenterOpen
|
|
930
|
+
? resolveNotificationCenterLayout(model, options, pagesById, resolveFrameThemeCtx(model.activeShellThemeId))
|
|
931
|
+
: undefined;
|
|
714
932
|
if (notificationCenterLayout != null) {
|
|
715
933
|
layouts = retainRuntimeLayout(layouts, {
|
|
716
934
|
viewId: 'notification-center',
|
|
@@ -790,7 +1008,7 @@ export function createFramedApp(options) {
|
|
|
790
1008
|
screenHeight: model.rows,
|
|
791
1009
|
margin: frameNotificationOptions.margin,
|
|
792
1010
|
gap: frameNotificationOptions.gap,
|
|
793
|
-
ctx:
|
|
1011
|
+
ctx: resolveFrameThemeCtx(model.activeShellThemeId) ?? undefined,
|
|
794
1012
|
}, msg.col, msg.row);
|
|
795
1013
|
if (notificationTarget?.kind === 'dismiss') {
|
|
796
1014
|
cmds.push({ type: 'dismiss-notification', notificationId: notificationTarget.item.id });
|
|
@@ -871,8 +1089,8 @@ export function createFramedApp(options) {
|
|
|
871
1089
|
}
|
|
872
1090
|
return helpLineOverride ?? mergeBindingSources(frameKeys, options.globalKeys, activeInputArea?.helpSource ?? activeInputArea?.keyMap, activePage.helpSource ?? activePage.keyMap);
|
|
873
1091
|
}
|
|
874
|
-
function resolveLayerMetadata(model, activePage, activeInputArea, modalKeyMap) {
|
|
875
|
-
const settings = resolveFrameSettings(model, options, pagesById);
|
|
1092
|
+
function resolveLayerMetadata(model, activePage, activePageModel, activeInputArea, modalKeyMap) {
|
|
1093
|
+
const settings = resolveFrameSettings(model, options, pagesById, resolvedShellThemes);
|
|
876
1094
|
const notificationCenter = resolveFrameNotificationCenter(model, options, pagesById);
|
|
877
1095
|
const workspaceHintSource = resolveWorkspaceHintSource(model, activePage, activeInputArea);
|
|
878
1096
|
const workspaceHelpSource = resolveWorkspaceHelpSource(activePage, activeInputArea);
|
|
@@ -889,17 +1107,22 @@ export function createFramedApp(options) {
|
|
|
889
1107
|
const notificationsTitle = notificationCenter == null
|
|
890
1108
|
? frameMessage(options.i18n, 'notifications.title', 'Notifications')
|
|
891
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
|
+
};
|
|
892
1123
|
return {
|
|
893
|
-
workspace:
|
|
894
|
-
|
|
895
|
-
hintSource: workspaceHintSource,
|
|
896
|
-
helpSource: workspaceHelpSource,
|
|
897
|
-
},
|
|
898
|
-
'page-modal': {
|
|
899
|
-
title: activePage.title,
|
|
900
|
-
hintSource: modalKeyMap ?? activePage.helpSource ?? activePage.keyMap,
|
|
901
|
-
helpSource: mergeBindingSources(quitHelpKeys, modalKeyMap, activePage.helpSource ?? activePage.keyMap),
|
|
902
|
-
},
|
|
1124
|
+
workspace: workspaceLayer,
|
|
1125
|
+
'page-modal': pageModalLayer,
|
|
903
1126
|
settings: {
|
|
904
1127
|
title: settings?.title ?? frameMessage(options.i18n, 'settings.title', 'Settings'),
|
|
905
1128
|
hintSource: settingsHint,
|
|
@@ -934,12 +1157,11 @@ export function createFramedApp(options) {
|
|
|
934
1157
|
}
|
|
935
1158
|
function resolvePresentedLayerContext(model) {
|
|
936
1159
|
const { activePage, activePageModel, inputAreas, activeInputArea, modalKeyMap, pageModalOpen, } = resolveLayerContext(model);
|
|
937
|
-
const
|
|
1160
|
+
const layerMetadata = resolveLayerMetadata(model, activePage, activePageModel, activeInputArea, modalKeyMap);
|
|
1161
|
+
const controlProjection = projectFrameControls(model, {
|
|
938
1162
|
pageModalOpen,
|
|
939
|
-
layers:
|
|
1163
|
+
layers: layerMetadata,
|
|
940
1164
|
});
|
|
941
|
-
const activeLayer = layerStack[layerStack.length - 1];
|
|
942
|
-
const underlyingLayer = layerStack.length > 1 ? layerStack[layerStack.length - 2] : undefined;
|
|
943
1165
|
return {
|
|
944
1166
|
activePage,
|
|
945
1167
|
activePageModel,
|
|
@@ -947,9 +1169,11 @@ export function createFramedApp(options) {
|
|
|
947
1169
|
activeInputArea,
|
|
948
1170
|
modalKeyMap,
|
|
949
1171
|
pageModalOpen,
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1172
|
+
layerMetadata,
|
|
1173
|
+
controlProjection,
|
|
1174
|
+
layerStack: controlProjection.layerStack,
|
|
1175
|
+
activeLayer: controlProjection.activeLayer,
|
|
1176
|
+
underlyingLayer: controlProjection.underlyingLayer,
|
|
953
1177
|
};
|
|
954
1178
|
}
|
|
955
1179
|
function updateTargetPage(model, targetPageId, targetMsg) {
|
|
@@ -982,19 +1206,15 @@ export function createFramedApp(options) {
|
|
|
982
1206
|
}
|
|
983
1207
|
return [nextModel, []];
|
|
984
1208
|
}
|
|
985
|
-
function
|
|
986
|
-
if (row.action === undefined || row.enabled === false || row.kind === 'info') {
|
|
987
|
-
return [model, []];
|
|
988
|
-
}
|
|
989
|
-
const cmds = [emitMsgForPage(model.activePageId, row.action)];
|
|
1209
|
+
function pushSettingsFeedback(model, row) {
|
|
990
1210
|
if (!frameNotificationOptions.enabled) {
|
|
991
|
-
return [model,
|
|
1211
|
+
return [model, []];
|
|
992
1212
|
}
|
|
993
1213
|
const feedback = row.feedback ?? {
|
|
994
1214
|
title: 'Setting updated',
|
|
995
1215
|
message: `${row.label} updated.`,
|
|
996
1216
|
};
|
|
997
|
-
const nowMs = resolveClock(
|
|
1217
|
+
const nowMs = resolveClock(resolveFrameCtx()).now();
|
|
998
1218
|
const notifications = pushNotification(model.runtimeNotifications, {
|
|
999
1219
|
title: feedback.title ?? 'Setting updated',
|
|
1000
1220
|
message: feedback.message,
|
|
@@ -1005,9 +1225,28 @@ export function createFramedApp(options) {
|
|
|
1005
1225
|
durationMs: feedback.durationMs ?? 2_500,
|
|
1006
1226
|
overflow: frameNotificationOptions.overflow,
|
|
1007
1227
|
}, nowMs);
|
|
1008
|
-
|
|
1228
|
+
return applyFrameNotificationState(model, notifications, nowMs);
|
|
1229
|
+
}
|
|
1230
|
+
function activateSettingsRow(model, row) {
|
|
1231
|
+
if (row.action === undefined || row.enabled === false || row.kind === 'info') {
|
|
1232
|
+
return [model, []];
|
|
1233
|
+
}
|
|
1234
|
+
const cmds = [emitMsgForPage(model.activePageId, row.action)];
|
|
1235
|
+
const [nextModel, notificationCmds] = pushSettingsFeedback(model, row);
|
|
1009
1236
|
return [nextModel, [...cmds, ...notificationCmds]];
|
|
1010
1237
|
}
|
|
1238
|
+
function cycleShellThemeSetting(model, row) {
|
|
1239
|
+
const nextTheme = resolveNextShellTheme(resolvedShellThemes, model.activeShellThemeId);
|
|
1240
|
+
if (nextTheme == null) {
|
|
1241
|
+
return [model, []];
|
|
1242
|
+
}
|
|
1243
|
+
publishShellThemeContext(nextTheme);
|
|
1244
|
+
const [nextModel, notificationCmds] = pushSettingsFeedback({
|
|
1245
|
+
...model,
|
|
1246
|
+
activeShellThemeId: nextTheme.id,
|
|
1247
|
+
}, row);
|
|
1248
|
+
return [nextModel, notificationCmds];
|
|
1249
|
+
}
|
|
1011
1250
|
const app = {
|
|
1012
1251
|
init() {
|
|
1013
1252
|
const pageModels = {};
|
|
@@ -1021,10 +1260,16 @@ export function createFramedApp(options) {
|
|
|
1021
1260
|
activePageId: defaultPageId,
|
|
1022
1261
|
pageOrder,
|
|
1023
1262
|
pageModels,
|
|
1263
|
+
warnedFrameKeyCollisionPages: {},
|
|
1024
1264
|
focusedPaneByPage: {},
|
|
1025
1265
|
scrollByPage: {},
|
|
1026
1266
|
columns: Math.max(1, options.initialColumns ?? 80),
|
|
1027
1267
|
rows: Math.max(1, options.initialRows ?? 24),
|
|
1268
|
+
frameTimeMs: 0,
|
|
1269
|
+
viewTimeMs: 0,
|
|
1270
|
+
diffTimeMs: 0,
|
|
1271
|
+
frameBudgetMs: undefined,
|
|
1272
|
+
frameOverBudget: false,
|
|
1028
1273
|
helpOpen: false,
|
|
1029
1274
|
helpScrollY: 0,
|
|
1030
1275
|
commandPaletteKind: undefined,
|
|
@@ -1044,7 +1289,18 @@ export function createFramedApp(options) {
|
|
|
1044
1289
|
runtimeNotifications: createNotificationState(),
|
|
1045
1290
|
runtimeNotificationHistoryFilter: 'ALL',
|
|
1046
1291
|
runtimeNotificationLoopActive: false,
|
|
1292
|
+
activeShellThemeId: undefined,
|
|
1047
1293
|
};
|
|
1294
|
+
ensureResolvedShellThemes(resolveFrameCtx());
|
|
1295
|
+
const initialShellTheme = resolveShellThemeForContext(resolvedShellThemes, resolveFrameCtx())
|
|
1296
|
+
?? resolvedShellThemes[0];
|
|
1297
|
+
model = {
|
|
1298
|
+
...model,
|
|
1299
|
+
activeShellThemeId: initialShellTheme?.id,
|
|
1300
|
+
};
|
|
1301
|
+
if (initialShellTheme != null) {
|
|
1302
|
+
publishShellThemeContext(initialShellTheme);
|
|
1303
|
+
}
|
|
1048
1304
|
for (const pageId of pageOrder) {
|
|
1049
1305
|
model = syncPageFrameState(model, pageId, pagesById);
|
|
1050
1306
|
}
|
|
@@ -1080,6 +1336,20 @@ export function createFramedApp(options) {
|
|
|
1080
1336
|
}, action.issue.atMs);
|
|
1081
1337
|
return applyFrameNotificationState(model, notifications, action.issue.atMs);
|
|
1082
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
|
+
}
|
|
1083
1353
|
if (action.type === 'notification-tick') {
|
|
1084
1354
|
const notifications = tickNotifications(model.runtimeNotifications, action.atMs);
|
|
1085
1355
|
return applyFrameNotificationState(model, notifications, action.atMs, true);
|
|
@@ -1159,11 +1429,12 @@ export function createFramedApp(options) {
|
|
|
1159
1429
|
return updateTargetPage(model, model.activePageId, msg);
|
|
1160
1430
|
},
|
|
1161
1431
|
view(model) {
|
|
1162
|
-
const
|
|
1163
|
-
const
|
|
1432
|
+
const themedFrameCtx = resolveFrameThemeCtx(model.activeShellThemeId);
|
|
1433
|
+
const { controlProjection, layerStack, activeLayer, } = resolvePresentedLayerContext(model);
|
|
1434
|
+
const headerResult = resolveHeaderLine(model, options, pagesById, headerScratch, themedFrameCtx);
|
|
1164
1435
|
headerScratch = headerResult.surface;
|
|
1165
1436
|
const header = headerResult.surface;
|
|
1166
|
-
helpLineScratch = renderHelpLine(model, activeLayer, options.i18n, resolveNotificationFooterCue(model, options, pagesById), helpLineScratch);
|
|
1437
|
+
helpLineScratch = renderHelpLine(model, activeLayer, options.i18n, resolveNotificationFooterCue(model, options, pagesById), helpLineScratch, themedFrameCtx);
|
|
1167
1438
|
const helpLine = helpLineScratch;
|
|
1168
1439
|
const bodyRect = resolveBodyRect(model, options);
|
|
1169
1440
|
// Check for maximized pane — if set, render only that pane at full body rect
|
|
@@ -1182,21 +1453,22 @@ export function createFramedApp(options) {
|
|
|
1182
1453
|
const activeTransition = model.activeTransition ?? options.transition;
|
|
1183
1454
|
if (model.previousPageId != null && model.transitionProgress < 1 && activeTransition && activeTransition !== 'none') {
|
|
1184
1455
|
const activeBodyResult = maximizedPaneId
|
|
1185
|
-
? renderMaximizedPane(model.activePageId, model, bodyRect, pagesById, maximizedPaneId, paneScratchPool)
|
|
1186
|
-
: renderPageContent(model.activePageId, model, bodyRect, pagesById);
|
|
1456
|
+
? renderMaximizedPane(model.activePageId, model, bodyRect, pagesById, maximizedPaneId, paneScratchPool, themedFrameCtx)
|
|
1457
|
+
: renderPageContent(model.activePageId, model, bodyRect, pagesById, themedFrameCtx);
|
|
1187
1458
|
activeResult = activeBodyResult;
|
|
1188
1459
|
bodySurface = activeBodyResult.surface;
|
|
1189
|
-
const ctx =
|
|
1460
|
+
const ctx = themedFrameCtx;
|
|
1190
1461
|
if (ctx) {
|
|
1191
|
-
const prevResult = renderPageContent(model.previousPageId, model, bodyRect, pagesById);
|
|
1462
|
+
const prevResult = renderPageContent(model.previousPageId, model, bodyRect, pagesById, themedFrameCtx);
|
|
1192
1463
|
bodySurface = renderTransition(prevResult.surface, activeBodyResult.surface, activeTransition, model.transitionProgress, bodyRect.width, bodyRect.height, ctx, model.transitionFrame);
|
|
1193
1464
|
}
|
|
1194
1465
|
}
|
|
1195
1466
|
else {
|
|
1196
1467
|
activeResult = maximizedPaneId
|
|
1197
|
-
? renderMaximizedPaneInto(model.activePageId, model, bodyRect, pagesById, maximizedPaneId, frameSurface, bodyRect.row, bodyRect.col, paneScratchPool)
|
|
1198
|
-
: renderPageContentInto(model.activePageId, model, bodyRect, pagesById, frameSurface, bodyRect.row, bodyRect.col, paneScratchPool);
|
|
1468
|
+
? renderMaximizedPaneInto(model.activePageId, model, bodyRect, pagesById, maximizedPaneId, frameSurface, bodyRect.row, bodyRect.col, paneScratchPool, themedFrameCtx)
|
|
1469
|
+
: renderPageContentInto(model.activePageId, model, bodyRect, pagesById, frameSurface, bodyRect.row, bodyRect.col, paneScratchPool, themedFrameCtx);
|
|
1199
1470
|
}
|
|
1471
|
+
rememberWorkspaceLayout(model, activeResult.paneRects, headerResult.tabTargets);
|
|
1200
1472
|
const overlays = [];
|
|
1201
1473
|
if (options.overlayFactory != null) {
|
|
1202
1474
|
overlays.push(...options.overlayFactory({
|
|
@@ -1207,7 +1479,7 @@ export function createFramedApp(options) {
|
|
|
1207
1479
|
}));
|
|
1208
1480
|
}
|
|
1209
1481
|
if (frameNotificationOptions.enabled) {
|
|
1210
|
-
const ctx =
|
|
1482
|
+
const ctx = themedFrameCtx;
|
|
1211
1483
|
overlays.push(...renderNotificationStack(model.runtimeNotifications, {
|
|
1212
1484
|
screenWidth: model.columns,
|
|
1213
1485
|
screenHeight: model.rows,
|
|
@@ -1218,20 +1490,24 @@ export function createFramedApp(options) {
|
|
|
1218
1490
|
}
|
|
1219
1491
|
if (model.settingsOpen) {
|
|
1220
1492
|
const settingsLayer = layerStack.find((layer) => layer.kind === 'settings');
|
|
1221
|
-
const settingsOverlay = renderSettingsDrawer(model, options, pagesById, settingsLayer?.title);
|
|
1493
|
+
const settingsOverlay = renderSettingsDrawer(model, options, pagesById, resolvedShellThemes, settingsLayer?.title, themedFrameCtx);
|
|
1222
1494
|
if (settingsOverlay != null) {
|
|
1223
1495
|
overlays.push(settingsOverlay);
|
|
1224
1496
|
}
|
|
1225
1497
|
}
|
|
1226
1498
|
if (model.notificationCenterOpen) {
|
|
1227
1499
|
const notificationLayer = layerStack.find((layer) => layer.kind === 'notification-center');
|
|
1228
|
-
const notificationCenterOverlay = renderNotificationCenterDrawer(model, options, pagesById, notificationLayer?.title);
|
|
1500
|
+
const notificationCenterOverlay = renderNotificationCenterDrawer(model, options, pagesById, notificationLayer?.title, themedFrameCtx);
|
|
1229
1501
|
if (notificationCenterOverlay != null) {
|
|
1230
1502
|
overlays.push(notificationCenterOverlay);
|
|
1231
1503
|
}
|
|
1232
1504
|
}
|
|
1233
1505
|
if (model.helpOpen) {
|
|
1234
|
-
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);
|
|
1235
1511
|
overlays.push(modal({
|
|
1236
1512
|
title: activeLayer.kind === 'help'
|
|
1237
1513
|
? (activeLayer.title ?? frameMessage(options.i18n, 'help.title', 'Keyboard Help'))
|
|
@@ -1240,6 +1516,9 @@ export function createFramedApp(options) {
|
|
|
1240
1516
|
hint: typeof activeLayer.hintSource === 'string'
|
|
1241
1517
|
? activeLayer.hintSource
|
|
1242
1518
|
: frameMessage(options.i18n, 'help.hint', 'j/k scroll • d/u page • g/G top/bottom • mouse wheel • ?/Esc close'),
|
|
1519
|
+
borderToken: themedFrameCtx?.border('primary'),
|
|
1520
|
+
bgToken: themedFrameCtx?.surface('elevated'),
|
|
1521
|
+
ctx: themedFrameCtx,
|
|
1243
1522
|
width: helpOverlay.body.width + 4,
|
|
1244
1523
|
screenWidth: model.columns,
|
|
1245
1524
|
screenHeight: model.rows,
|
|
@@ -1247,7 +1526,11 @@ export function createFramedApp(options) {
|
|
|
1247
1526
|
}
|
|
1248
1527
|
if (model.commandPalette != null) {
|
|
1249
1528
|
const paletteWidth = Math.max(20, Math.min(80, model.columns - 4));
|
|
1250
|
-
const paletteBody =
|
|
1529
|
+
const paletteBody = commandPaletteSurface(model.commandPalette, {
|
|
1530
|
+
width: Math.max(16, paletteWidth - 4),
|
|
1531
|
+
ctx: themedFrameCtx,
|
|
1532
|
+
showScrollbar: false,
|
|
1533
|
+
});
|
|
1251
1534
|
const paletteLayer = activeLayer.kind === 'search' || activeLayer.kind === 'command-palette'
|
|
1252
1535
|
? activeLayer
|
|
1253
1536
|
: undefined;
|
|
@@ -1257,13 +1540,16 @@ export function createFramedApp(options) {
|
|
|
1257
1540
|
hint: typeof paletteLayer?.hintSource === 'string'
|
|
1258
1541
|
? paletteLayer.hintSource
|
|
1259
1542
|
: frameMessage(options.i18n, 'palette.hint', 'Enter select • Esc close'),
|
|
1543
|
+
borderToken: themedFrameCtx?.border('primary'),
|
|
1544
|
+
bgToken: themedFrameCtx?.surface('elevated'),
|
|
1545
|
+
ctx: themedFrameCtx,
|
|
1260
1546
|
width: paletteWidth,
|
|
1261
1547
|
screenWidth: model.columns,
|
|
1262
1548
|
screenHeight: model.rows,
|
|
1263
1549
|
}));
|
|
1264
1550
|
}
|
|
1265
1551
|
if (model.quitConfirmOpen) {
|
|
1266
|
-
overlays.push(renderShellQuitOverlay(model.columns, model.rows, options.i18n));
|
|
1552
|
+
overlays.push(renderShellQuitOverlay(model.columns, model.rows, options.i18n, themedFrameCtx));
|
|
1267
1553
|
}
|
|
1268
1554
|
if (bodySurface != null && bodyRect.width > 0 && bodyRect.height > 0) {
|
|
1269
1555
|
frameSurface.blit(bodySurface, bodyRect.col, bodyRect.row);
|
|
@@ -1276,7 +1562,67 @@ export function createFramedApp(options) {
|
|
|
1276
1562
|
return wrapFrameMsg({ type: 'runtime-issue', issue });
|
|
1277
1563
|
},
|
|
1278
1564
|
};
|
|
1279
|
-
|
|
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);
|
|
1280
1626
|
}
|
|
1281
1627
|
function focusPane(model, paneId) {
|
|
1282
1628
|
if (model.focusedPaneByPage[model.activePageId] === paneId)
|
|
@@ -1292,426 +1638,4 @@ function focusPane(model, paneId) {
|
|
|
1292
1638
|
function resolveBodyRect(model, options) {
|
|
1293
1639
|
return frameBodyRect(model.columns, model.rows, options.bodyTopRows ?? 1, options.bodyBottomRows ?? 1);
|
|
1294
1640
|
}
|
|
1295
|
-
function renderHelpOverlay(model, activePage, frameKeys, paletteKeys, options, pagesById) {
|
|
1296
|
-
const activePageModel = model.pageModels[model.activePageId];
|
|
1297
|
-
const activeInputArea = findInputAreaByPaneId(resolveInputAreas(activePage, activePageModel), model.focusedPaneByPage[model.activePageId]);
|
|
1298
|
-
const modalKeyMap = activePage.modalKeyMap?.(activePageModel);
|
|
1299
|
-
const settings = resolveFrameSettings(model, options, pagesById);
|
|
1300
|
-
const notificationCenter = resolveFrameNotificationCenter(model, options, pagesById);
|
|
1301
|
-
const workspaceHintSource = options.helpLineSource?.({
|
|
1302
|
-
model,
|
|
1303
|
-
activePage,
|
|
1304
|
-
frameKeys,
|
|
1305
|
-
globalKeys: options.globalKeys,
|
|
1306
|
-
});
|
|
1307
|
-
const workspaceHelpSource = mergeBindingSources(frameKeys, quitHelpKeys, options.globalKeys, activeInputArea?.helpSource ?? activeInputArea?.keyMap, activePage.helpSource ?? activePage.keyMap);
|
|
1308
|
-
const layerStack = describeFrameLayerStack(model, {
|
|
1309
|
-
pageModalOpen: modalKeyMap != null,
|
|
1310
|
-
layers: {
|
|
1311
|
-
workspace: {
|
|
1312
|
-
title: activePage.title,
|
|
1313
|
-
hintSource: typeof workspaceHintSource === 'string'
|
|
1314
|
-
? workspaceHintSource
|
|
1315
|
-
: workspaceHintSource ?? mergeBindingSources(frameKeys, options.globalKeys, activeInputArea?.helpSource ?? activeInputArea?.keyMap, activePage.helpSource ?? activePage.keyMap),
|
|
1316
|
-
helpSource: workspaceHelpSource,
|
|
1317
|
-
},
|
|
1318
|
-
'page-modal': {
|
|
1319
|
-
title: activePage.title,
|
|
1320
|
-
hintSource: modalKeyMap ?? activePage.helpSource ?? activePage.keyMap,
|
|
1321
|
-
helpSource: mergeBindingSources(quitHelpKeys, modalKeyMap, activePage.helpSource ?? activePage.keyMap),
|
|
1322
|
-
},
|
|
1323
|
-
settings: {
|
|
1324
|
-
title: settings?.title ?? frameMessage(options.i18n, 'settings.title', 'Settings'),
|
|
1325
|
-
hintSource: frameMessage(options.i18n, 'settings.footer', 'F2/Esc close • ↑/↓ rows • Enter toggle • / search • q quit'),
|
|
1326
|
-
helpSource: mergeBindingSources(settingsHelpKeys, quitHelpKeys),
|
|
1327
|
-
},
|
|
1328
|
-
help: {
|
|
1329
|
-
title: frameMessage(options.i18n, 'help.title', 'Keyboard Help'),
|
|
1330
|
-
hintSource: frameMessage(options.i18n, 'help.hint', 'j/k scroll • d/u page • g/G top/bottom • mouse wheel • ?/Esc close'),
|
|
1331
|
-
helpSource: helpLayerHelpKeys,
|
|
1332
|
-
},
|
|
1333
|
-
'notification-center': {
|
|
1334
|
-
title: notificationCenter == null
|
|
1335
|
-
? frameMessage(options.i18n, 'notifications.title', 'Notifications')
|
|
1336
|
-
: `${notificationCenter.title} • ${frameNotificationFilterLabel(options.i18n, notificationCenter.activeFilter)}`,
|
|
1337
|
-
hintSource: frameMessage(options.i18n, 'notifications.footer', 'Shift+N close • f filter • j/k scroll • q quit'),
|
|
1338
|
-
helpSource: mergeBindingSources(notificationCenterHelpKeys, quitHelpKeys),
|
|
1339
|
-
},
|
|
1340
|
-
search: {
|
|
1341
|
-
title: model.commandPaletteTitle ?? activePage.searchTitle ?? frameMessage(options.i18n, 'search.title', 'Search'),
|
|
1342
|
-
hintSource: frameMessage(options.i18n, 'palette.hint', 'Enter select • Esc close'),
|
|
1343
|
-
helpSource: mergeBindingSources(paletteKeys, quitHelpKeys),
|
|
1344
|
-
},
|
|
1345
|
-
'command-palette': {
|
|
1346
|
-
title: model.commandPaletteTitle ?? frameMessage(options.i18n, 'palette.title', 'Command Palette'),
|
|
1347
|
-
hintSource: frameMessage(options.i18n, 'palette.hint', 'Enter select • Esc close'),
|
|
1348
|
-
helpSource: mergeBindingSources(paletteKeys, quitHelpKeys),
|
|
1349
|
-
},
|
|
1350
|
-
'quit-confirm': {
|
|
1351
|
-
title: frameMessage(options.i18n, 'quit.title', 'Quit?'),
|
|
1352
|
-
hintSource: frameMessage(options.i18n, 'quit.footer', 'Y quit • N stay'),
|
|
1353
|
-
helpSource: quitConfirmHelpKeys,
|
|
1354
|
-
},
|
|
1355
|
-
},
|
|
1356
|
-
});
|
|
1357
|
-
const beneathHelpLayer = layerStack.length > 1 ? layerStack[layerStack.length - 2] : undefined;
|
|
1358
|
-
const source = beneathHelpLayer?.helpSource ?? workspaceHelpSource;
|
|
1359
|
-
const maxDialogWidth = Math.max(28, Math.min(model.columns - 4, 88));
|
|
1360
|
-
const bodyWidth = Math.max(20, maxDialogWidth - 4);
|
|
1361
|
-
const helpSurface = helpViewSurface(source, {
|
|
1362
|
-
title: undefined,
|
|
1363
|
-
width: bodyWidth,
|
|
1364
|
-
});
|
|
1365
|
-
const pagerHeight = Math.max(4, Math.min(helpSurface.height + 1, Math.max(4, model.rows - 8)));
|
|
1366
|
-
const pagerState = createPagerStateForSurface(helpSurface, {
|
|
1367
|
-
width: bodyWidth,
|
|
1368
|
-
height: pagerHeight,
|
|
1369
|
-
});
|
|
1370
|
-
const scrollY = Math.max(0, Math.min(model.helpScrollY, pagerState.scroll.maxY));
|
|
1371
|
-
const scrolledState = {
|
|
1372
|
-
...pagerState,
|
|
1373
|
-
scroll: {
|
|
1374
|
-
...pagerState.scroll,
|
|
1375
|
-
y: scrollY,
|
|
1376
|
-
},
|
|
1377
|
-
};
|
|
1378
|
-
return {
|
|
1379
|
-
body: pagerSurface(helpSurface, scrolledState, { showScrollbar: true, showStatus: true }),
|
|
1380
|
-
maxScrollY: pagerState.scroll.maxY,
|
|
1381
|
-
scrollY,
|
|
1382
|
-
};
|
|
1383
|
-
}
|
|
1384
|
-
function isHelpScrollAction(action) {
|
|
1385
|
-
return action.type === 'scroll-up'
|
|
1386
|
-
|| action.type === 'scroll-down'
|
|
1387
|
-
|| action.type === 'page-up'
|
|
1388
|
-
|| action.type === 'page-down'
|
|
1389
|
-
|| action.type === 'top'
|
|
1390
|
-
|| action.type === 'bottom';
|
|
1391
|
-
}
|
|
1392
|
-
function resolveFrameSettings(model, options, pagesById) {
|
|
1393
|
-
const activePage = pagesById.get(model.activePageId);
|
|
1394
|
-
return options.settings?.({
|
|
1395
|
-
model,
|
|
1396
|
-
activePage,
|
|
1397
|
-
pageModel: model.pageModels[model.activePageId],
|
|
1398
|
-
});
|
|
1399
|
-
}
|
|
1400
|
-
function resolveFrameNotificationCenter(model, options, pagesById) {
|
|
1401
|
-
const activePage = pagesById.get(model.activePageId);
|
|
1402
|
-
const pageModel = model.pageModels[model.activePageId];
|
|
1403
|
-
const provided = options.notificationCenter?.({
|
|
1404
|
-
model,
|
|
1405
|
-
activePage,
|
|
1406
|
-
pageModel,
|
|
1407
|
-
runtimeNotifications: model.runtimeNotifications,
|
|
1408
|
-
});
|
|
1409
|
-
if (provided != null) {
|
|
1410
|
-
const filters = provided.filters != null && provided.filters.length > 0
|
|
1411
|
-
? provided.filters
|
|
1412
|
-
: DEFAULT_NOTIFICATION_CENTER_FILTERS;
|
|
1413
|
-
const activeFilter = filters.includes(provided.activeFilter ?? 'ALL')
|
|
1414
|
-
? (provided.activeFilter ?? 'ALL')
|
|
1415
|
-
: filters[0];
|
|
1416
|
-
return {
|
|
1417
|
-
title: provided.title ?? frameMessage(options.i18n, 'notifications.title', 'Notifications'),
|
|
1418
|
-
state: provided.state,
|
|
1419
|
-
filters,
|
|
1420
|
-
activeFilter,
|
|
1421
|
-
onFilterChange: provided.onFilterChange,
|
|
1422
|
-
};
|
|
1423
|
-
}
|
|
1424
|
-
if (options.runtimeNotifications === false)
|
|
1425
|
-
return undefined;
|
|
1426
|
-
return {
|
|
1427
|
-
title: frameMessage(options.i18n, 'notifications.title', 'Notifications'),
|
|
1428
|
-
state: model.runtimeNotifications,
|
|
1429
|
-
filters: DEFAULT_NOTIFICATION_CENTER_FILTERS,
|
|
1430
|
-
activeFilter: model.runtimeNotificationHistoryFilter,
|
|
1431
|
-
};
|
|
1432
|
-
}
|
|
1433
|
-
function resolveSettingsLayout(model, options, pagesById) {
|
|
1434
|
-
const settings = resolveFrameSettings(model, options, pagesById);
|
|
1435
|
-
if (settings == null)
|
|
1436
|
-
return undefined;
|
|
1437
|
-
const sections = settings.sections.filter((section) => section.rows.length > 0);
|
|
1438
|
-
if (sections.length === 0)
|
|
1439
|
-
return undefined;
|
|
1440
|
-
const drawerWidth = resolveSettingsDrawerWidth(model.columns);
|
|
1441
|
-
const anchor = frameStartAnchor(options.i18n);
|
|
1442
|
-
const startCol = anchor === 'left' ? 0 : Math.max(0, model.columns - drawerWidth);
|
|
1443
|
-
const contentWidth = Math.max(16, drawerWidth - 4);
|
|
1444
|
-
const preferenceSections = preparePreferenceSections(toPreferenceSections(sections));
|
|
1445
|
-
const rows = [];
|
|
1446
|
-
let line = 0;
|
|
1447
|
-
for (let sectionIndex = 0; sectionIndex < preferenceSections.length; sectionIndex++) {
|
|
1448
|
-
const section = preferenceSections[sectionIndex];
|
|
1449
|
-
if (sectionIndex > 0) {
|
|
1450
|
-
line += 1;
|
|
1451
|
-
}
|
|
1452
|
-
line += 1;
|
|
1453
|
-
line += 1;
|
|
1454
|
-
for (let rowIndex = 0; rowIndex < section.rows.length; rowIndex++) {
|
|
1455
|
-
const preparedRow = section.rows[rowIndex];
|
|
1456
|
-
const row = sections[sectionIndex].rows[rowIndex];
|
|
1457
|
-
const rowLayout = resolvePreferenceRowLayout(preparedRow, contentWidth);
|
|
1458
|
-
rows.push({
|
|
1459
|
-
index: rows.length,
|
|
1460
|
-
line,
|
|
1461
|
-
height: rowLayout.height,
|
|
1462
|
-
row,
|
|
1463
|
-
});
|
|
1464
|
-
line += rowLayout.height;
|
|
1465
|
-
if (rowIndex < section.rows.length - 1) {
|
|
1466
|
-
line += 1;
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
const contentHeight = Math.max(1, model.rows - 2);
|
|
1471
|
-
const totalLines = Math.max(1, line);
|
|
1472
|
-
const maxScrollY = Math.max(0, totalLines - contentHeight);
|
|
1473
|
-
return {
|
|
1474
|
-
settings: {
|
|
1475
|
-
...settings,
|
|
1476
|
-
sections,
|
|
1477
|
-
},
|
|
1478
|
-
preferenceSections,
|
|
1479
|
-
rows,
|
|
1480
|
-
anchor,
|
|
1481
|
-
startCol,
|
|
1482
|
-
drawerWidth,
|
|
1483
|
-
contentWidth,
|
|
1484
|
-
contentHeight,
|
|
1485
|
-
totalLines,
|
|
1486
|
-
maxScrollY,
|
|
1487
|
-
};
|
|
1488
|
-
}
|
|
1489
|
-
function resolveNotificationCenterDrawerWidth(columns) {
|
|
1490
|
-
const boundedColumns = Math.max(28, columns);
|
|
1491
|
-
return Math.min(Math.max(32, Math.floor(boundedColumns * 0.34)), Math.max(32, boundedColumns - 4), 52);
|
|
1492
|
-
}
|
|
1493
|
-
function resolveNotificationCenterLayout(model, options, pagesById) {
|
|
1494
|
-
const center = resolveFrameNotificationCenter(model, options, pagesById);
|
|
1495
|
-
if (center == null)
|
|
1496
|
-
return undefined;
|
|
1497
|
-
const drawerWidth = resolveNotificationCenterDrawerWidth(model.columns);
|
|
1498
|
-
const anchor = frameEndAnchor(options.i18n);
|
|
1499
|
-
const startCol = anchor === 'left' ? 0 : Math.max(0, model.columns - drawerWidth);
|
|
1500
|
-
const contentWidth = Math.max(18, drawerWidth - 4);
|
|
1501
|
-
const content = renderNotificationCenterSurface(center, contentWidth, options.i18n);
|
|
1502
|
-
const contentHeight = Math.max(1, model.rows - 2);
|
|
1503
|
-
const pagerState = createPagerStateForSurface(content, {
|
|
1504
|
-
width: contentWidth,
|
|
1505
|
-
height: contentHeight,
|
|
1506
|
-
});
|
|
1507
|
-
return {
|
|
1508
|
-
center,
|
|
1509
|
-
anchor,
|
|
1510
|
-
startCol,
|
|
1511
|
-
drawerWidth,
|
|
1512
|
-
contentWidth,
|
|
1513
|
-
contentHeight,
|
|
1514
|
-
content,
|
|
1515
|
-
maxScrollY: pagerState.scroll.maxY,
|
|
1516
|
-
};
|
|
1517
|
-
}
|
|
1518
|
-
function resolveSettingsDrawerWidth(columns) {
|
|
1519
|
-
const boundedColumns = Math.max(24, columns);
|
|
1520
|
-
return Math.min(Math.max(28, Math.floor(boundedColumns * 0.3)), Math.max(28, boundedColumns - 4), 42);
|
|
1521
|
-
}
|
|
1522
|
-
function clampSettingsFocus(model, layout) {
|
|
1523
|
-
if (layout.rows.length === 0)
|
|
1524
|
-
return 0;
|
|
1525
|
-
return Math.max(0, Math.min(model.settingsFocusIndex, layout.rows.length - 1));
|
|
1526
|
-
}
|
|
1527
|
-
function clampSettingsScroll(model, layout) {
|
|
1528
|
-
return Math.max(0, Math.min(model.settingsScrollY, layout.maxScrollY));
|
|
1529
|
-
}
|
|
1530
|
-
function resolveInputAreas(page, pageModel) {
|
|
1531
|
-
return page.inputAreas?.(pageModel) ?? [];
|
|
1532
|
-
}
|
|
1533
|
-
function findInputAreaByPaneId(inputAreas, paneId) {
|
|
1534
|
-
if (paneId == null)
|
|
1535
|
-
return undefined;
|
|
1536
|
-
return inputAreas.find((area) => area.paneId === paneId);
|
|
1537
|
-
}
|
|
1538
|
-
function ensureSettingsRangeVisible(startLine, height, scrollY, visibleLines, maxScrollY) {
|
|
1539
|
-
let next = scrollY;
|
|
1540
|
-
const endLine = startLine + Math.max(1, height) - 1;
|
|
1541
|
-
if (startLine < next) {
|
|
1542
|
-
next = startLine;
|
|
1543
|
-
}
|
|
1544
|
-
else if (endLine >= next + visibleLines) {
|
|
1545
|
-
next = endLine - visibleLines + 1;
|
|
1546
|
-
}
|
|
1547
|
-
return Math.max(0, Math.min(next, maxScrollY));
|
|
1548
|
-
}
|
|
1549
|
-
function moveSettingsFocus(model, layout, delta) {
|
|
1550
|
-
if (layout.rows.length === 0)
|
|
1551
|
-
return model;
|
|
1552
|
-
const nextFocus = Math.max(0, Math.min(clampSettingsFocus(model, layout) + delta, layout.rows.length - 1));
|
|
1553
|
-
const focusedRow = layout.rows[nextFocus];
|
|
1554
|
-
return {
|
|
1555
|
-
...model,
|
|
1556
|
-
settingsFocusIndex: nextFocus,
|
|
1557
|
-
settingsScrollY: ensureSettingsRangeVisible(focusedRow.line, focusedRow.height, clampSettingsScroll(model, layout), layout.contentHeight, layout.maxScrollY),
|
|
1558
|
-
};
|
|
1559
|
-
}
|
|
1560
|
-
function scrollSettingsBy(model, layout, delta) {
|
|
1561
|
-
return {
|
|
1562
|
-
...model,
|
|
1563
|
-
settingsScrollY: Math.max(0, Math.min(clampSettingsScroll(model, layout) + delta, layout.maxScrollY)),
|
|
1564
|
-
};
|
|
1565
|
-
}
|
|
1566
|
-
function scrollNotificationCenterBy(model, layout, delta) {
|
|
1567
|
-
return {
|
|
1568
|
-
...model,
|
|
1569
|
-
notificationCenterScrollY: Math.max(0, Math.min(model.notificationCenterScrollY + delta, layout.maxScrollY)),
|
|
1570
|
-
};
|
|
1571
|
-
}
|
|
1572
|
-
function cycleNotificationCenterFilter(model, layout) {
|
|
1573
|
-
const filters = layout.center.filters;
|
|
1574
|
-
if (filters.length < 2)
|
|
1575
|
-
return [model, []];
|
|
1576
|
-
const currentIndex = Math.max(0, filters.indexOf(layout.center.activeFilter));
|
|
1577
|
-
const nextFilter = filters[(currentIndex + 1) % filters.length];
|
|
1578
|
-
if (layout.center.onFilterChange != null) {
|
|
1579
|
-
const action = layout.center.onFilterChange(nextFilter);
|
|
1580
|
-
return [{
|
|
1581
|
-
...model,
|
|
1582
|
-
notificationCenterScrollY: 0,
|
|
1583
|
-
}, action === undefined ? [] : [emitMsgForPage(model.activePageId, action)]];
|
|
1584
|
-
}
|
|
1585
|
-
return [{
|
|
1586
|
-
...model,
|
|
1587
|
-
runtimeNotificationHistoryFilter: nextFilter,
|
|
1588
|
-
notificationCenterScrollY: 0,
|
|
1589
|
-
}, []];
|
|
1590
|
-
}
|
|
1591
|
-
function renderSettingsDrawer(model, options, pagesById, titleOverride) {
|
|
1592
|
-
const layout = resolveSettingsLayout(model, options, pagesById);
|
|
1593
|
-
if (layout == null)
|
|
1594
|
-
return undefined;
|
|
1595
|
-
const scrollY = clampSettingsScroll(model, layout);
|
|
1596
|
-
const content = renderSettingsSurface(layout, model);
|
|
1597
|
-
const pagerState = createPagerStateForSurface(content, {
|
|
1598
|
-
width: layout.contentWidth,
|
|
1599
|
-
height: layout.contentHeight,
|
|
1600
|
-
});
|
|
1601
|
-
const scrolledState = {
|
|
1602
|
-
...pagerState,
|
|
1603
|
-
scroll: {
|
|
1604
|
-
...pagerState.scroll,
|
|
1605
|
-
y: scrollY,
|
|
1606
|
-
},
|
|
1607
|
-
};
|
|
1608
|
-
const body = pagerSurface(content, scrolledState, {
|
|
1609
|
-
showScrollbar: layout.maxScrollY > 0,
|
|
1610
|
-
showStatus: false,
|
|
1611
|
-
});
|
|
1612
|
-
return drawer({
|
|
1613
|
-
anchor: layout.anchor,
|
|
1614
|
-
title: titleOverride ?? layout.settings.title ?? frameMessage(options.i18n, 'settings.title', 'Settings'),
|
|
1615
|
-
content: body,
|
|
1616
|
-
borderToken: layout.settings.borderToken,
|
|
1617
|
-
bgToken: layout.settings.bgToken,
|
|
1618
|
-
ctx: resolveSafeCtx() ?? undefined,
|
|
1619
|
-
width: layout.drawerWidth,
|
|
1620
|
-
screenWidth: model.columns,
|
|
1621
|
-
screenHeight: model.rows,
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
1624
|
-
function renderNotificationCenterDrawer(model, options, pagesById, titleOverride) {
|
|
1625
|
-
const layout = resolveNotificationCenterLayout(model, options, pagesById);
|
|
1626
|
-
if (layout == null)
|
|
1627
|
-
return undefined;
|
|
1628
|
-
const pagerState = createPagerStateForSurface(layout.content, {
|
|
1629
|
-
width: layout.contentWidth,
|
|
1630
|
-
height: layout.contentHeight,
|
|
1631
|
-
});
|
|
1632
|
-
const scrolledState = {
|
|
1633
|
-
...pagerState,
|
|
1634
|
-
scroll: {
|
|
1635
|
-
...pagerState.scroll,
|
|
1636
|
-
y: Math.max(0, Math.min(model.notificationCenterScrollY, layout.maxScrollY)),
|
|
1637
|
-
},
|
|
1638
|
-
};
|
|
1639
|
-
const body = pagerSurface(layout.content, scrolledState, {
|
|
1640
|
-
showScrollbar: layout.maxScrollY > 0,
|
|
1641
|
-
showStatus: false,
|
|
1642
|
-
});
|
|
1643
|
-
return drawer({
|
|
1644
|
-
anchor: layout.anchor,
|
|
1645
|
-
title: titleOverride ?? `${layout.center.title} • ${frameNotificationFilterLabel(options.i18n, layout.center.activeFilter)}`,
|
|
1646
|
-
content: body,
|
|
1647
|
-
width: layout.drawerWidth,
|
|
1648
|
-
screenWidth: model.columns,
|
|
1649
|
-
screenHeight: model.rows,
|
|
1650
|
-
});
|
|
1651
|
-
}
|
|
1652
|
-
function renderSettingsSurface(layout, model) {
|
|
1653
|
-
const focusedIndex = clampSettingsFocus(model, layout);
|
|
1654
|
-
return preferenceListSurface(layout.preferenceSections, {
|
|
1655
|
-
width: layout.contentWidth,
|
|
1656
|
-
selectedRowId: layout.rows[focusedIndex]?.row.id,
|
|
1657
|
-
ctx: resolveSafeCtx() ?? undefined,
|
|
1658
|
-
theme: layout.settings.listTheme,
|
|
1659
|
-
});
|
|
1660
|
-
}
|
|
1661
|
-
function toPreferenceSections(sections) {
|
|
1662
|
-
return sections.map((section) => ({
|
|
1663
|
-
id: section.id,
|
|
1664
|
-
title: section.title,
|
|
1665
|
-
rows: section.rows.map((row) => toPreferenceRow(row)),
|
|
1666
|
-
}));
|
|
1667
|
-
}
|
|
1668
|
-
function toPreferenceRow(row) {
|
|
1669
|
-
return {
|
|
1670
|
-
id: row.id,
|
|
1671
|
-
label: row.label,
|
|
1672
|
-
description: row.description,
|
|
1673
|
-
valueLabel: row.valueLabel,
|
|
1674
|
-
kind: row.kind,
|
|
1675
|
-
checked: row.checked,
|
|
1676
|
-
enabled: row.enabled,
|
|
1677
|
-
};
|
|
1678
|
-
}
|
|
1679
|
-
function resolveNotificationFooterCue(model, options, pagesById) {
|
|
1680
|
-
const center = resolveFrameNotificationCenter(model, options, pagesById);
|
|
1681
|
-
if (center == null)
|
|
1682
|
-
return undefined;
|
|
1683
|
-
const liveCount = center.state.items.length;
|
|
1684
|
-
const archivedCount = countNotificationHistory(center.state, center.activeFilter);
|
|
1685
|
-
return frameNotificationCue(options.i18n, liveCount, archivedCount);
|
|
1686
|
-
}
|
|
1687
|
-
function renderNotificationCenterSurface(center, width, i18n) {
|
|
1688
|
-
const ctx = resolveSafeCtx() ?? undefined;
|
|
1689
|
-
const rows = [
|
|
1690
|
-
insetLineSurface(`Live: ${center.state.items.length} • Archived: ${center.state.history.length}`, width),
|
|
1691
|
-
insetLineSurface(`Filter: ${frameNotificationFilterLabel(i18n, center.activeFilter)}`, width),
|
|
1692
|
-
];
|
|
1693
|
-
const liveItems = [...center.state.items].sort((left, right) => right.updatedAtMs - left.updatedAtMs || right.id - left.id);
|
|
1694
|
-
if (liveItems.length > 0) {
|
|
1695
|
-
rows.push(createSurface(width, 1));
|
|
1696
|
-
rows.push(insetLineSurface(ctx == null ? 'Current stack' : ctx.style.bold('Current stack'), width));
|
|
1697
|
-
rows.push(createSurface(width, 1));
|
|
1698
|
-
for (let index = 0; index < liveItems.length; index++) {
|
|
1699
|
-
rows.push(renderNotificationReviewEntrySurface(liveItems[index], {
|
|
1700
|
-
width,
|
|
1701
|
-
ctx,
|
|
1702
|
-
metaLabel: `${liveItems[index].variant} • live`,
|
|
1703
|
-
}));
|
|
1704
|
-
if (index < liveItems.length - 1)
|
|
1705
|
-
rows.push(createSurface(width, 1));
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
rows.push(createSurface(width, 1));
|
|
1709
|
-
rows.push(renderNotificationHistorySurface(center.state, {
|
|
1710
|
-
width,
|
|
1711
|
-
height: Number.MAX_SAFE_INTEGER,
|
|
1712
|
-
filter: center.activeFilter,
|
|
1713
|
-
ctx,
|
|
1714
|
-
}));
|
|
1715
|
-
return vstackSurface(...rows);
|
|
1716
|
-
}
|
|
1717
1641
|
//# sourceMappingURL=app-frame.js.map
|