@flyingrobots/bijou-tui 4.4.1 → 7.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 +244 -21
- package/dist/animate.d.ts +4 -0
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +47 -20
- package/dist/animate.js.map +1 -1
- package/dist/app-frame-actions.d.ts.map +1 -1
- package/dist/app-frame-actions.js +55 -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 +51 -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-palette.d.ts.map +1 -1
- package/dist/app-frame-palette.js +36 -16
- package/dist/app-frame-palette.js.map +1 -1
- package/dist/app-frame-render.d.ts.map +1 -1
- package/dist/app-frame-render.js +16 -7
- package/dist/app-frame-render.js.map +1 -1
- package/dist/app-frame-types.d.ts +141 -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 +3 -1
- package/dist/app-frame-utils.d.ts.map +1 -1
- package/dist/app-frame-utils.js +11 -5
- package/dist/app-frame-utils.js.map +1 -1
- package/dist/app-frame.d.ts +22 -86
- package/dist/app-frame.d.ts.map +1 -1
- package/dist/app-frame.js +475 -629
- 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 +11 -3
- package/dist/canvas.d.ts.map +1 -1
- package/dist/canvas.js +146 -13
- package/dist/canvas.js.map +1 -1
- package/dist/cell-glyph-fit.d.ts +20 -0
- package/dist/cell-glyph-fit.d.ts.map +1 -0
- package/dist/cell-glyph-fit.js +82 -0
- package/dist/cell-glyph-fit.js.map +1 -0
- 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 +2 -0
- package/dist/command-palette.d.ts.map +1 -1
- package/dist/command-palette.js +36 -19
- 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/eventbus.d.ts +20 -0
- package/dist/eventbus.d.ts.map +1 -1
- package/dist/eventbus.js +47 -0
- package/dist/eventbus.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 +13 -4
- package/dist/focus-area.d.ts.map +1 -1
- package/dist/focus-area.js +56 -14
- package/dist/focus-area.js.map +1 -1
- package/dist/focus-map.d.ts +40 -0
- package/dist/focus-map.d.ts.map +1 -0
- package/dist/focus-map.js +133 -0
- package/dist/focus-map.js.map +1 -0
- 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 +32 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -5
- package/dist/index.js.map +1 -1
- package/dist/input-map.d.ts +68 -0
- package/dist/input-map.d.ts.map +1 -0
- package/dist/input-map.js +162 -0
- package/dist/input-map.js.map +1 -0
- package/dist/input-routing-inspector.d.ts +24 -0
- package/dist/input-routing-inspector.d.ts.map +1 -0
- package/dist/input-routing-inspector.js +76 -0
- package/dist/input-routing-inspector.js.map +1 -0
- package/dist/keybindings.d.ts.map +1 -1
- package/dist/keybindings.js +3 -0
- package/dist/keybindings.js.map +1 -1
- package/dist/layout-inspector.d.ts +31 -0
- package/dist/layout-inspector.d.ts.map +1 -0
- package/dist/layout-inspector.js +133 -0
- package/dist/layout-inspector.js.map +1 -0
- 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 +136 -34
- 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.d.ts.map +1 -1
- package/dist/pipeline/middleware/motion.js +4 -3
- package/dist/pipeline/middleware/motion.js.map +1 -1
- package/dist/pipeline/middleware/paint.d.ts.map +1 -1
- package/dist/pipeline/middleware/paint.js +2 -1
- package/dist/pipeline/middleware/paint.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 +37 -2
- package/dist/pipeline/pipeline.d.ts.map +1 -1
- package/dist/pipeline/pipeline.js +121 -22
- package/dist/pipeline/pipeline.js.map +1 -1
- package/dist/raytrace.d.ts +59 -0
- package/dist/raytrace.d.ts.map +1 -0
- package/dist/raytrace.js +121 -0
- package/dist/raytrace.js.map +1 -0
- package/dist/runtime-binding.d.ts +59 -0
- package/dist/runtime-binding.d.ts.map +1 -0
- package/dist/runtime-binding.js +271 -0
- package/dist/runtime-binding.js.map +1 -0
- package/dist/runtime.d.ts +19 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +222 -31
- 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-budget.d.ts +27 -0
- package/dist/surface-budget.d.ts.map +1 -0
- package/dist/surface-budget.js +61 -0
- package/dist/surface-budget.js.map +1 -0
- package/dist/surface-diff.d.ts +36 -0
- package/dist/surface-diff.d.ts.map +1 -0
- package/dist/surface-diff.js +202 -0
- package/dist/surface-diff.js.map +1 -0
- package/dist/surface-layout.d.ts +19 -3
- package/dist/surface-layout.d.ts.map +1 -1
- package/dist/surface-layout.js +42 -12
- package/dist/surface-layout.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.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 +4 -4
package/dist/app-frame.js
CHANGED
|
@@ -4,101 +4,109 @@
|
|
|
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,
|
|
8
|
-
import {
|
|
9
|
-
import { createKeyMap } from './keybindings.js';
|
|
7
|
+
import { cloneContextWithResolvedTheme, createResolved, createSurface, perfOverlaySurface, 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
|
-
import { createFrameKeyMap, frameBodyRect, mergeBindingSources, } from './app-frame-utils.js';
|
|
22
|
-
import {
|
|
18
|
+
import { createFrameKeyMap, frameBodyRect, mergeBindingSources, resolveFramePageText, } from './app-frame-utils.js';
|
|
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
|
-
|
|
26
|
+
import { createInputActionMap, createInputGestureRecognizer, } from './input-map.js';
|
|
27
|
+
export { emitFrameAction, notify, } from './app-frame-types.js';
|
|
28
|
+
export { activeFrameLayer, describeFrameLayerStack, describeFrameRuntimeViewStack, projectFrameControls, underlyingFrameLayer, } from './app-frame-layers.js';
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
// Frame Notification Helpers
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
32
32
|
const FRAME_NOTIFICATION_TICK_MS = 40;
|
|
33
33
|
const DEFAULT_FRAME_NOTIFICATION_DURATION_MS = 6_000;
|
|
34
34
|
const SETTINGS_FEEDBACK_TOAST_WIDTH = 40;
|
|
35
|
+
const FRAME_FOOTER_DOUBLE_TAB_MS = 300;
|
|
35
36
|
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
|
-
|
|
37
|
+
function createQuitHelpKeys(i18n) {
|
|
38
|
+
const t = (id, fallback) => frameMessage(i18n, id, fallback);
|
|
39
|
+
return createKeyMap()
|
|
40
|
+
.group(t('help.group.quit', 'Quit'), (g) => g
|
|
41
|
+
.bind('q', t('help.key.quit', 'Quit'), { type: 'toggle-help' })
|
|
42
|
+
.bind('escape', t('help.key.quit', 'Quit'), { type: 'toggle-help' })
|
|
43
|
+
.bind('ctrl+c', t('help.key.quit', 'Quit'), { type: 'toggle-help' }));
|
|
44
|
+
}
|
|
45
|
+
function createHelpLayerHelpKeys(i18n) {
|
|
46
|
+
const t = (id, fallback) => frameMessage(i18n, id, fallback);
|
|
47
|
+
return createKeyMap()
|
|
48
|
+
.group(t('help.group.help', 'Help'), (g) => g
|
|
49
|
+
.bind('escape', t('help.key.closeHelp', 'Close help'), { type: 'noop' })
|
|
50
|
+
.bind('?', t('help.key.closeHelp', 'Close help'), { type: 'noop' })
|
|
51
|
+
.bind('up', t('key.scrollUp', 'Scroll up'), { type: 'noop' })
|
|
52
|
+
.bind('down', t('key.scrollDown', 'Scroll down'), { type: 'noop' })
|
|
53
|
+
.bind('j', t('key.scrollDown', 'Scroll down'), { type: 'noop' })
|
|
54
|
+
.bind('k', t('key.scrollUp', 'Scroll up'), { type: 'noop' })
|
|
55
|
+
.bind('d', t('key.pageDown', 'Page down'), { type: 'noop' })
|
|
56
|
+
.bind('u', t('key.pageUp', 'Page up'), { type: 'noop' })
|
|
57
|
+
.bind('g', t('key.top', 'Top'), { type: 'noop' })
|
|
58
|
+
.bind('shift+g', t('key.bottom', 'Bottom'), { type: 'noop' }));
|
|
59
|
+
}
|
|
60
|
+
function createSettingsHelpKeys(i18n) {
|
|
61
|
+
const t = (id, fallback) => frameMessage(i18n, id, fallback);
|
|
62
|
+
return createKeyMap()
|
|
63
|
+
.group(t('help.group.settings', 'Settings'), (g) => g
|
|
64
|
+
.bind('escape', t('help.key.closeSettings', 'Close settings'), { type: 'toggle-settings' })
|
|
65
|
+
.bind('f2', t('help.key.closeSettings', 'Close settings'), { type: 'toggle-settings' })
|
|
66
|
+
.bind('up', t('help.key.previousRow', 'Previous row'), { type: 'scroll-up' })
|
|
67
|
+
.bind('down', t('help.key.nextRow', 'Next row'), { type: 'scroll-down' })
|
|
68
|
+
.bind('enter', t('help.key.activateSetting', 'Activate setting'), { type: 'toggle-settings' })
|
|
69
|
+
.bind('space', t('help.key.activateSetting', 'Activate setting'), { type: 'toggle-settings' })
|
|
70
|
+
.bind('j', t('key.scrollDown', 'Scroll down'), { type: 'scroll-down' })
|
|
71
|
+
.bind('k', t('key.scrollUp', 'Scroll up'), { type: 'scroll-up' })
|
|
72
|
+
.bind('d', t('key.pageDown', 'Page down'), { type: 'page-down' })
|
|
73
|
+
.bind('u', t('key.pageUp', 'Page up'), { type: 'page-up' })
|
|
74
|
+
.bind('g', t('key.top', 'Top'), { type: 'top' })
|
|
75
|
+
.bind('shift+g', t('key.bottom', 'Bottom'), { type: 'bottom' })
|
|
76
|
+
.bind('/', t('key.search', 'Search'), { type: 'open-search' })
|
|
77
|
+
.bind('ctrl+p', t('key.openPalette', 'Open command palette'), { type: 'open-palette' })
|
|
78
|
+
.bind(':', t('key.openPalette', 'Open command palette'), { type: 'open-palette' })
|
|
79
|
+
.bind('?', t('key.toggleHelp', 'Toggle help'), { type: 'toggle-help' }));
|
|
80
|
+
}
|
|
81
|
+
function createNotificationCenterHelpKeys(i18n) {
|
|
82
|
+
const t = (id, fallback) => frameMessage(i18n, id, fallback);
|
|
83
|
+
return createKeyMap()
|
|
84
|
+
.group(t('help.group.notifications', 'Notifications'), (g) => g
|
|
85
|
+
.bind('shift+n', t('help.key.closeNotifications', 'Close notification center'), { type: 'noop' })
|
|
86
|
+
.bind('up', t('key.scrollUp', 'Scroll up'), { type: 'noop' })
|
|
87
|
+
.bind('down', t('key.scrollDown', 'Scroll down'), { type: 'noop' })
|
|
88
|
+
.bind('j', t('key.scrollDown', 'Scroll down'), { type: 'noop' })
|
|
89
|
+
.bind('k', t('key.scrollUp', 'Scroll up'), { type: 'noop' })
|
|
90
|
+
.bind('d', t('key.pageDown', 'Page down'), { type: 'noop' })
|
|
91
|
+
.bind('u', t('key.pageUp', 'Page up'), { type: 'noop' })
|
|
92
|
+
.bind('g', t('key.top', 'Top'), { type: 'noop' })
|
|
93
|
+
.bind('shift+g', t('key.bottom', 'Bottom'), { type: 'noop' })
|
|
94
|
+
.bind('f', t('help.key.cycleFilter', 'Cycle filter'), { type: 'noop' })
|
|
95
|
+
.bind('/', t('key.search', 'Search'), { type: 'noop' })
|
|
96
|
+
.bind('ctrl+p', t('key.openPalette', 'Open command palette'), { type: 'noop' })
|
|
97
|
+
.bind(':', t('key.openPalette', 'Open command palette'), { type: 'noop' })
|
|
98
|
+
.bind('?', t('key.toggleHelp', 'Toggle help'), { type: 'noop' }));
|
|
99
|
+
}
|
|
100
|
+
function createQuitConfirmHelpKeys(i18n) {
|
|
101
|
+
const t = (id, fallback) => frameMessage(i18n, id, fallback);
|
|
102
|
+
return createKeyMap()
|
|
103
|
+
.group(t('help.group.quit', 'Quit'), (g) => g
|
|
104
|
+
.bind('y', t('help.key.quit', 'Quit'), { type: 'noop' })
|
|
105
|
+
.bind('enter', t('help.key.quit', 'Quit'), { type: 'noop' })
|
|
106
|
+
.bind('n', t('help.key.stay', 'Stay'), { type: 'noop' })
|
|
107
|
+
.bind('escape', t('help.key.stay', 'Stay'), { type: 'noop' })
|
|
108
|
+
.bind('q', t('help.key.stay', 'Stay'), { type: 'noop' }));
|
|
109
|
+
}
|
|
102
110
|
function resolveFrameNotificationOptions(options) {
|
|
103
111
|
if (options.runtimeNotifications === false) {
|
|
104
112
|
return {
|
|
@@ -137,78 +145,45 @@ function createFrameNotificationTickCmd() {
|
|
|
137
145
|
function cloneShellThemeContext(ctx, resolvedTheme) {
|
|
138
146
|
return cloneContextWithResolvedTheme(ctx, resolvedTheme);
|
|
139
147
|
}
|
|
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];
|
|
150
|
-
}
|
|
151
|
-
function resolveNextShellTheme(shellThemes, activeShellThemeId) {
|
|
152
|
-
if (shellThemes.length === 0)
|
|
153
|
-
return undefined;
|
|
154
|
-
const currentIndex = Math.max(0, shellThemes.findIndex((theme) => theme.id === activeShellThemeId));
|
|
155
|
-
return shellThemes[(currentIndex + 1) % shellThemes.length];
|
|
148
|
+
function readStageDuration(timings, stage) {
|
|
149
|
+
return timings.find((timing) => timing.stage === stage)?.durationMs ?? 0;
|
|
156
150
|
}
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
},
|
|
151
|
+
function summarizeFrameTimings(timings, frameBudgetMs) {
|
|
152
|
+
const frameTimeMs = timings.reduce((total, timing) => total + timing.durationMs, 0);
|
|
153
|
+
return {
|
|
154
|
+
frameTimeMs,
|
|
155
|
+
viewTimeMs: readStageDuration(timings, 'Layout'),
|
|
156
|
+
diffTimeMs: readStageDuration(timings, 'Diff'),
|
|
157
|
+
frameBudgetMs,
|
|
158
|
+
frameOverBudget: frameBudgetMs != null && frameTimeMs > frameBudgetMs,
|
|
182
159
|
};
|
|
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
|
-
};
|
|
160
|
+
}
|
|
161
|
+
function applyFrameTimingSnapshot(model, snapshot) {
|
|
162
|
+
if (model.frameTimeMs === snapshot.frameTimeMs
|
|
163
|
+
&& model.viewTimeMs === snapshot.viewTimeMs
|
|
164
|
+
&& model.diffTimeMs === snapshot.diffTimeMs
|
|
165
|
+
&& model.frameBudgetMs === snapshot.frameBudgetMs
|
|
166
|
+
&& model.frameOverBudget === snapshot.frameOverBudget) {
|
|
167
|
+
return model;
|
|
203
168
|
}
|
|
204
169
|
return {
|
|
205
|
-
...
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
170
|
+
...model,
|
|
171
|
+
frameTimeMs: snapshot.frameTimeMs,
|
|
172
|
+
viewTimeMs: snapshot.viewTimeMs,
|
|
173
|
+
diffTimeMs: snapshot.diffTimeMs,
|
|
174
|
+
frameBudgetMs: snapshot.frameBudgetMs,
|
|
175
|
+
frameOverBudget: snapshot.frameOverBudget,
|
|
210
176
|
};
|
|
211
177
|
}
|
|
178
|
+
function resolveFrameBudgetMs(runOptions, fallbackCtx) {
|
|
179
|
+
if (runOptions?.frameBudgetMs != null)
|
|
180
|
+
return runOptions.frameBudgetMs;
|
|
181
|
+
const refreshRate = runOptions?.ctx?.runtime.refreshRate ?? fallbackCtx?.runtime.refreshRate;
|
|
182
|
+
if (refreshRate == null || !Number.isFinite(refreshRate) || refreshRate <= 0) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
return 1_000 / refreshRate;
|
|
186
|
+
}
|
|
212
187
|
// Factory
|
|
213
188
|
// ---------------------------------------------------------------------------
|
|
214
189
|
/**
|
|
@@ -230,28 +205,40 @@ export function createFramedApp(options) {
|
|
|
230
205
|
if (!pagesById.has(defaultPageId)) {
|
|
231
206
|
throw new Error(`createFramedApp: defaultPageId "${defaultPageId}" not found in pages`);
|
|
232
207
|
}
|
|
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];
|
|
208
|
+
const shellThemeSpecs = options.shellThemes ?? [];
|
|
209
|
+
let defaultFrameCtx = options.ctx ?? resolveSafeCtx();
|
|
210
|
+
let resolvedShellThemes = [];
|
|
211
|
+
const enableShellThemeSettings = shellThemeSpecs.length > 1;
|
|
247
212
|
const usesAmbientDefaultContext = options.ctx == null && defaultFrameCtx != null;
|
|
248
213
|
let frameCtx = options.ctx;
|
|
249
|
-
let frameCtxShellThemeId
|
|
214
|
+
let frameCtxShellThemeId;
|
|
215
|
+
let useRunScopedFrameCtx = false;
|
|
216
|
+
function ensureResolvedShellThemes(explicitCtx) {
|
|
217
|
+
if (shellThemeSpecs.length === 0 || resolvedShellThemes.length > 0)
|
|
218
|
+
return;
|
|
219
|
+
const baseCtx = explicitCtx ?? frameCtx ?? options.ctx ?? resolveSafeCtx();
|
|
220
|
+
if (baseCtx == null) {
|
|
221
|
+
throw new Error('createFramedApp: shellThemes requires options.ctx, app.run({ ctx }), or a default Bijou context');
|
|
222
|
+
}
|
|
223
|
+
defaultFrameCtx ??= baseCtx;
|
|
224
|
+
resolvedShellThemes = shellThemeSpecs.map((theme) => ({
|
|
225
|
+
id: theme.id,
|
|
226
|
+
label: theme.label,
|
|
227
|
+
description: theme.description,
|
|
228
|
+
shellTheme: theme,
|
|
229
|
+
resolvedTheme: createResolved(theme.theme, defaultFrameCtx.theme.noColor, defaultFrameCtx.theme.colorScheme),
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
if (frameCtx != null) {
|
|
233
|
+
ensureResolvedShellThemes(frameCtx);
|
|
234
|
+
frameCtxShellThemeId = resolveShellThemeForContext(resolvedShellThemes, frameCtx)?.id;
|
|
235
|
+
}
|
|
250
236
|
function resolveFrameCtx() {
|
|
251
237
|
return frameCtx ?? options.ctx ?? resolveSafeCtx();
|
|
252
238
|
}
|
|
253
239
|
function resolveFrameThemeCtx(activeShellThemeId) {
|
|
254
240
|
const baseCtx = resolveFrameCtx();
|
|
241
|
+
ensureResolvedShellThemes(baseCtx);
|
|
255
242
|
if (defaultFrameCtx == null)
|
|
256
243
|
return baseCtx;
|
|
257
244
|
const activeTheme = resolveCurrentShellTheme(resolvedShellThemes, activeShellThemeId);
|
|
@@ -266,11 +253,12 @@ export function createFramedApp(options) {
|
|
|
266
253
|
return cloneShellThemeContext(defaultFrameCtx, activeTheme.resolvedTheme);
|
|
267
254
|
}
|
|
268
255
|
function publishShellThemeContext(nextTheme) {
|
|
256
|
+
ensureResolvedShellThemes(resolveFrameCtx());
|
|
269
257
|
if (defaultFrameCtx == null)
|
|
270
258
|
return resolveFrameCtx();
|
|
271
259
|
frameCtx = cloneShellThemeContext(defaultFrameCtx, nextTheme.resolvedTheme);
|
|
272
260
|
frameCtxShellThemeId = nextTheme.id;
|
|
273
|
-
if (usesAmbientDefaultContext) {
|
|
261
|
+
if (usesAmbientDefaultContext && !useRunScopedFrameCtx) {
|
|
274
262
|
setDefaultContext(frameCtx);
|
|
275
263
|
}
|
|
276
264
|
options.onShellThemeChange?.({
|
|
@@ -284,11 +272,24 @@ export function createFramedApp(options) {
|
|
|
284
272
|
enableNotifications: options.notificationCenter != null || options.runtimeNotifications !== false,
|
|
285
273
|
i18n: options.i18n,
|
|
286
274
|
});
|
|
275
|
+
const frameInputRecognizer = createInputGestureRecognizer({
|
|
276
|
+
doubleTapMs: FRAME_FOOTER_DOUBLE_TAB_MS,
|
|
277
|
+
});
|
|
278
|
+
const frameInputActions = createInputActionMap()
|
|
279
|
+
.bind('frame.footer.doubleTab', 'Toggle footer', [
|
|
280
|
+
{ deviceId: 'keyboard', featureId: 'key.tab', type: 'double-tap' },
|
|
281
|
+
], { type: 'toggle-footer' });
|
|
282
|
+
const quitHelpKeys = createQuitHelpKeys(options.i18n);
|
|
283
|
+
const helpLayerHelpKeys = createHelpLayerHelpKeys(options.i18n);
|
|
284
|
+
const settingsHelpKeys = createSettingsHelpKeys(options.i18n);
|
|
285
|
+
const notificationCenterHelpKeys = createNotificationCenterHelpKeys(options.i18n);
|
|
286
|
+
const quitConfirmHelpKeys = createQuitConfirmHelpKeys(options.i18n);
|
|
287
287
|
const frameNotificationOptions = resolveFrameNotificationOptions(options);
|
|
288
288
|
let composedFrameScratch = null;
|
|
289
289
|
let headerScratch;
|
|
290
290
|
let helpLineScratch;
|
|
291
291
|
const paneScratchPool = createFramePaneScratchPool();
|
|
292
|
+
let workspaceLayoutCache;
|
|
292
293
|
const paletteKeys = commandPaletteKeyMap({
|
|
293
294
|
focusNext: { type: 'cp-next' },
|
|
294
295
|
focusPrev: { type: 'cp-prev' },
|
|
@@ -297,6 +298,49 @@ export function createFramedApp(options) {
|
|
|
297
298
|
select: { type: 'cp-select' },
|
|
298
299
|
close: { type: 'cp-close' },
|
|
299
300
|
});
|
|
301
|
+
function bindingComboKey(binding) {
|
|
302
|
+
const combo = binding.combo;
|
|
303
|
+
return `${combo.key}|${combo.ctrl ? 1 : 0}|${combo.alt ? 1 : 0}|${combo.shift ? 1 : 0}`;
|
|
304
|
+
}
|
|
305
|
+
function findBindingForMessage(bindings, msg) {
|
|
306
|
+
const comboKey = `${msg.key}|${msg.ctrl ? 1 : 0}|${msg.alt ? 1 : 0}|${msg.shift ? 1 : 0}`;
|
|
307
|
+
return bindings.find((binding) => binding.enabled && bindingComboKey(binding) === comboKey);
|
|
308
|
+
}
|
|
309
|
+
function queueFrameKeyCollisionWarning(model, msg, teaCmds) {
|
|
310
|
+
if (!frameNotificationOptions.enabled)
|
|
311
|
+
return model;
|
|
312
|
+
if ((options.keyPriority ?? 'frame-first') !== 'frame-first')
|
|
313
|
+
return model;
|
|
314
|
+
if (model.warnedFrameKeyCollisionPages[model.activePageId])
|
|
315
|
+
return model;
|
|
316
|
+
const activePage = pagesById.get(model.activePageId);
|
|
317
|
+
if (activePage?.keyMap == null)
|
|
318
|
+
return model;
|
|
319
|
+
const pageBinding = findBindingForMessage(activePage.keyMap.bindings(), msg);
|
|
320
|
+
const frameBinding = findBindingForMessage(frameKeys.bindings(), msg);
|
|
321
|
+
if (pageBinding == null || frameBinding == null)
|
|
322
|
+
return model;
|
|
323
|
+
const warningCmd = async () => wrapFrameMsg({
|
|
324
|
+
type: 'runtime-issue',
|
|
325
|
+
issue: {
|
|
326
|
+
level: 'warning',
|
|
327
|
+
source: 'runtime',
|
|
328
|
+
message: `Page "${model.activePageId}" key binding ${formatKeyCombo(pageBinding.combo)} `
|
|
329
|
+
+ `("${pageBinding.description}") is shadowed by the frame binding `
|
|
330
|
+
+ `"${frameBinding.description}" under keyPriority="frame-first". `
|
|
331
|
+
+ `Use keyPriority: 'page-first' or choose a different page binding.`,
|
|
332
|
+
atMs: resolveClock(resolveFrameCtx()).now(),
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
teaCmds.push(warningCmd);
|
|
336
|
+
return {
|
|
337
|
+
...model,
|
|
338
|
+
warnedFrameKeyCollisionPages: {
|
|
339
|
+
...model.warnedFrameKeyCollisionPages,
|
|
340
|
+
[model.activePageId]: true,
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
300
344
|
function getComposedFrameScratch(width, height) {
|
|
301
345
|
if (composedFrameScratch == null
|
|
302
346
|
|| composedFrameScratch.width !== width
|
|
@@ -305,6 +349,36 @@ export function createFramedApp(options) {
|
|
|
305
349
|
}
|
|
306
350
|
return composedFrameScratch;
|
|
307
351
|
}
|
|
352
|
+
function renderFramePerfHudOverlay(model, ctx) {
|
|
353
|
+
const refreshRate = ctx?.runtime.refreshRate ?? 60;
|
|
354
|
+
const fps = model.frameTimeMs > 0
|
|
355
|
+
? Math.min(refreshRate, 1000 / model.frameTimeMs)
|
|
356
|
+
: refreshRate;
|
|
357
|
+
const width = Math.max(12, Math.min(40, model.columns - 2));
|
|
358
|
+
const surface = perfOverlaySurface({
|
|
359
|
+
fps,
|
|
360
|
+
frameTimeMs: model.frameTimeMs,
|
|
361
|
+
width: model.columns,
|
|
362
|
+
height: model.rows,
|
|
363
|
+
extras: [
|
|
364
|
+
{ label: 'view', value: `${model.viewTimeMs.toFixed(2)} ms` },
|
|
365
|
+
{ label: 'diff', value: `${model.diffTimeMs.toFixed(2)} ms` },
|
|
366
|
+
],
|
|
367
|
+
}, {
|
|
368
|
+
title: frameMessage(options.i18n, 'perfHud.title', 'Perf HUD'),
|
|
369
|
+
width,
|
|
370
|
+
showChart: false,
|
|
371
|
+
borderToken: ctx?.border('primary'),
|
|
372
|
+
bgToken: ctx?.surface('elevated'),
|
|
373
|
+
ctx,
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
content: '',
|
|
377
|
+
surface,
|
|
378
|
+
row: 1,
|
|
379
|
+
col: Math.max(0, model.columns - surface.width - 1),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
308
382
|
function closeCommandPalette(model) {
|
|
309
383
|
return {
|
|
310
384
|
...model,
|
|
@@ -402,11 +476,18 @@ export function createFramedApp(options) {
|
|
|
402
476
|
teaCmds.push(...cmds);
|
|
403
477
|
return nextModel;
|
|
404
478
|
},
|
|
479
|
+
'warn-frame-key-collision': (model, cmd, teaCmds) => {
|
|
480
|
+
const c = cmd;
|
|
481
|
+
return queueFrameKeyCollisionWarning(model, c.msg, teaCmds);
|
|
482
|
+
},
|
|
405
483
|
// --- help ---
|
|
406
484
|
'help-scroll': (model, cmd) => {
|
|
407
485
|
const c = cmd;
|
|
408
|
-
const
|
|
409
|
-
|
|
486
|
+
const helpSource = resolvePresentedLayerContext(model).controlProjection.helpSource;
|
|
487
|
+
if (helpSource == null) {
|
|
488
|
+
return model;
|
|
489
|
+
}
|
|
490
|
+
const overlay = renderHelpOverlay(model, helpSource, options.i18n);
|
|
410
491
|
const viewportHeight = Math.max(1, overlay.body.height - 1);
|
|
411
492
|
const delta = c.action === 'down' ? 3
|
|
412
493
|
: c.action === 'up' ? -3
|
|
@@ -511,6 +592,23 @@ export function createFramedApp(options) {
|
|
|
511
592
|
}
|
|
512
593
|
return [{ type: 'observed-key', msg, route }, { type: 'open-quit-confirm' }];
|
|
513
594
|
}
|
|
595
|
+
function resolveFrameInputActionCommands(msg, route) {
|
|
596
|
+
const inputEvent = frameInputRecognizer.observeKey(msg, resolveClock(resolveFrameCtx()).now());
|
|
597
|
+
const inputAction = frameInputActions.handle(inputEvent);
|
|
598
|
+
if (inputAction == null || msg.ctrl || msg.alt || msg.shift) {
|
|
599
|
+
return undefined;
|
|
600
|
+
}
|
|
601
|
+
return [{ type: 'observed-key', msg, route }, { type: 'apply-frame-action', action: inputAction }];
|
|
602
|
+
}
|
|
603
|
+
function routeForFrameInputAction(layerKind) {
|
|
604
|
+
if (layerKind === 'search' || layerKind === 'command-palette')
|
|
605
|
+
return 'palette';
|
|
606
|
+
if (layerKind === 'help')
|
|
607
|
+
return 'help';
|
|
608
|
+
if (layerKind === 'page-modal')
|
|
609
|
+
return 'page';
|
|
610
|
+
return 'frame';
|
|
611
|
+
}
|
|
514
612
|
function resolveFrameActionCommands(msg, action, route) {
|
|
515
613
|
if (action.type === 'open-search' && options.enableCommandPalette) {
|
|
516
614
|
return [{ type: 'observed-key', msg, route }, { type: 'open-search-palette' }];
|
|
@@ -542,6 +640,9 @@ export function createFramedApp(options) {
|
|
|
542
640
|
if (frameAction?.type === 'toggle-notifications') {
|
|
543
641
|
return [obs, { type: 'close-palette' }, { type: 'apply-frame-action', action: frameAction }];
|
|
544
642
|
}
|
|
643
|
+
if (frameAction?.type === 'toggle-perf-hud') {
|
|
644
|
+
return [obs, { type: 'apply-frame-action', action: frameAction }];
|
|
645
|
+
}
|
|
545
646
|
return [obs, { type: 'palette-key', msg }];
|
|
546
647
|
}
|
|
547
648
|
function handleHelpLayerKeyCommands(msg) {
|
|
@@ -553,6 +654,9 @@ export function createFramedApp(options) {
|
|
|
553
654
|
return quitRequestCommands(msg, 'help');
|
|
554
655
|
}
|
|
555
656
|
const helpAction = frameKeys.handle(msg);
|
|
657
|
+
if (helpAction?.type === 'toggle-perf-hud') {
|
|
658
|
+
return [obs, { type: 'apply-frame-action', action: helpAction }];
|
|
659
|
+
}
|
|
556
660
|
if (helpAction && isHelpScrollAction(helpAction)) {
|
|
557
661
|
const action = helpAction.type === 'scroll-down' ? 'down'
|
|
558
662
|
: helpAction.type === 'scroll-up' ? 'up'
|
|
@@ -591,6 +695,9 @@ export function createFramedApp(options) {
|
|
|
591
695
|
if (settingsFrameAction?.type === 'toggle-notifications') {
|
|
592
696
|
return [obs, { type: 'apply-frame-action', action: settingsFrameAction }];
|
|
593
697
|
}
|
|
698
|
+
if (settingsFrameAction?.type === 'toggle-perf-hud') {
|
|
699
|
+
return [obs, { type: 'apply-frame-action', action: settingsFrameAction }];
|
|
700
|
+
}
|
|
594
701
|
if (!msg.ctrl && !msg.alt && msg.key === 'up') {
|
|
595
702
|
return [obs, { type: 'settings-focus-move', delta: -1 }];
|
|
596
703
|
}
|
|
@@ -643,6 +750,9 @@ export function createFramedApp(options) {
|
|
|
643
750
|
if (centerFrameAction?.type === 'toggle-notifications') {
|
|
644
751
|
return [obs, { type: 'apply-frame-action', action: centerFrameAction }];
|
|
645
752
|
}
|
|
753
|
+
if (centerFrameAction?.type === 'toggle-perf-hud') {
|
|
754
|
+
return [obs, { type: 'apply-frame-action', action: centerFrameAction }];
|
|
755
|
+
}
|
|
646
756
|
if (!msg.ctrl && !msg.alt && msg.key === 'f2') {
|
|
647
757
|
return [obs, { type: 'close-notification-center' }, { type: 'apply-frame-action', action: { type: 'toggle-settings' } }];
|
|
648
758
|
}
|
|
@@ -706,7 +816,9 @@ export function createFramedApp(options) {
|
|
|
706
816
|
}
|
|
707
817
|
// frame-first (default)
|
|
708
818
|
if (frameAction !== undefined) {
|
|
709
|
-
return
|
|
819
|
+
return pageAction !== undefined
|
|
820
|
+
? [...resolveFrameActionCommands(msg, frameAction, 'frame'), { type: 'warn-frame-key-collision', msg }]
|
|
821
|
+
: resolveFrameActionCommands(msg, frameAction, 'frame');
|
|
710
822
|
}
|
|
711
823
|
if (paneAction !== undefined) {
|
|
712
824
|
return [{ type: 'observed-key', msg, route: 'page' }, { type: 'emit-page-msg', pageId: model.activePageId, msg: paneAction }];
|
|
@@ -728,6 +840,10 @@ export function createFramedApp(options) {
|
|
|
728
840
|
const frameLayer = layer.model;
|
|
729
841
|
if (frameLayer == null)
|
|
730
842
|
return undefined;
|
|
843
|
+
const inputActionCommands = resolveFrameInputActionCommands(msg, routeForFrameInputAction(frameLayer.kind));
|
|
844
|
+
if (inputActionCommands != null) {
|
|
845
|
+
return { handled: true, commands: inputActionCommands };
|
|
846
|
+
}
|
|
731
847
|
if (frameLayer.kind === 'search' || frameLayer.kind === 'command-palette') {
|
|
732
848
|
return { handled: true, commands: handlePaletteLayerKeyCommands(msg, frameLayer.kind) };
|
|
733
849
|
}
|
|
@@ -744,6 +860,10 @@ export function createFramedApp(options) {
|
|
|
744
860
|
}
|
|
745
861
|
if (frameLayer.kind === 'quit-confirm') {
|
|
746
862
|
const obs = { type: 'observed-key', msg, route: 'frame' };
|
|
863
|
+
const frameAction = frameKeys.handle(msg);
|
|
864
|
+
if (frameAction?.type === 'toggle-perf-hud') {
|
|
865
|
+
return { handled: true, commands: [obs, { type: 'apply-frame-action', action: frameAction }] };
|
|
866
|
+
}
|
|
747
867
|
if (isShellQuitConfirmAccept(msg)) {
|
|
748
868
|
return { handled: true, commands: [obs, { type: 'close-quit-confirm' }, { type: 'quit' }] };
|
|
749
869
|
}
|
|
@@ -755,6 +875,10 @@ export function createFramedApp(options) {
|
|
|
755
875
|
if (frameLayer.kind === 'page-modal') {
|
|
756
876
|
const { modalKeyMap } = context;
|
|
757
877
|
const obs = { type: 'observed-key', msg, route: 'page' };
|
|
878
|
+
const frameAction = frameKeys.handle(msg);
|
|
879
|
+
if (frameAction?.type === 'toggle-perf-hud') {
|
|
880
|
+
return { handled: true, commands: [obs, { type: 'apply-frame-action', action: frameAction }] };
|
|
881
|
+
}
|
|
758
882
|
if (modalKeyMap != null) {
|
|
759
883
|
const modalAction = modalKeyMap.handle(msg);
|
|
760
884
|
if (modalAction !== undefined) {
|
|
@@ -779,27 +903,15 @@ export function createFramedApp(options) {
|
|
|
779
903
|
children: children ?? [],
|
|
780
904
|
};
|
|
781
905
|
}
|
|
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}`, {
|
|
906
|
+
function buildWorkspaceLayoutTreeFromPaneRects(model, paneRects, tabTargets) {
|
|
907
|
+
const resolvedTabTargets = tabTargets ?? resolveHeaderLine(model, options, pagesById, headerScratch, resolveFrameThemeCtx(model.activeShellThemeId)).tabTargets;
|
|
908
|
+
const tabChildren = resolvedTabTargets.map((target) => createShellRetainedLayoutNode(`tab:${target.pageId}`, {
|
|
796
909
|
row: 0,
|
|
797
910
|
col: target.startCol,
|
|
798
911
|
width: target.endCol - target.startCol + 1,
|
|
799
912
|
height: 1,
|
|
800
913
|
}));
|
|
801
914
|
const bodyRect = resolveBodyRect(model, options);
|
|
802
|
-
const paneRects = resolveWorkspacePaneRects(model);
|
|
803
915
|
const paneChildren = [];
|
|
804
916
|
for (const [paneId, rect] of paneRects.entries()) {
|
|
805
917
|
paneChildren.push(createShellRetainedLayoutNode(`pane:${paneId}`, rect));
|
|
@@ -809,6 +921,55 @@ export function createFramedApp(options) {
|
|
|
809
921
|
createShellRetainedLayoutNode('workspace-body', bodyRect, paneChildren),
|
|
810
922
|
]);
|
|
811
923
|
}
|
|
924
|
+
function rememberWorkspaceLayout(model, paneRects, tabTargets) {
|
|
925
|
+
const activePageId = model.activePageId;
|
|
926
|
+
const next = {
|
|
927
|
+
activePageId,
|
|
928
|
+
activePageModel: model.pageModels[activePageId],
|
|
929
|
+
columns: model.columns,
|
|
930
|
+
rows: model.rows,
|
|
931
|
+
visibilityState: model.minimizedByPage[activePageId],
|
|
932
|
+
dockState: model.dockStateByPage[activePageId],
|
|
933
|
+
splitRatioOverrides: model.splitRatioOverrides,
|
|
934
|
+
maximizedPaneId: model.maximizedPaneByPage[activePageId]?.maximizedPaneId,
|
|
935
|
+
paneRects,
|
|
936
|
+
tree: buildWorkspaceLayoutTreeFromPaneRects(model, paneRects, tabTargets),
|
|
937
|
+
};
|
|
938
|
+
workspaceLayoutCache = next;
|
|
939
|
+
return next;
|
|
940
|
+
}
|
|
941
|
+
function matchesWorkspaceLayoutCache(model) {
|
|
942
|
+
if (workspaceLayoutCache == null)
|
|
943
|
+
return false;
|
|
944
|
+
const activePageId = model.activePageId;
|
|
945
|
+
return workspaceLayoutCache.activePageId === activePageId
|
|
946
|
+
&& workspaceLayoutCache.activePageModel === model.pageModels[activePageId]
|
|
947
|
+
&& workspaceLayoutCache.columns === model.columns
|
|
948
|
+
&& workspaceLayoutCache.rows === model.rows
|
|
949
|
+
&& workspaceLayoutCache.visibilityState === model.minimizedByPage[activePageId]
|
|
950
|
+
&& workspaceLayoutCache.dockState === model.dockStateByPage[activePageId]
|
|
951
|
+
&& workspaceLayoutCache.splitRatioOverrides === model.splitRatioOverrides
|
|
952
|
+
&& workspaceLayoutCache.maximizedPaneId === model.maximizedPaneByPage[activePageId]?.maximizedPaneId;
|
|
953
|
+
}
|
|
954
|
+
function resolveWorkspaceLayout(model) {
|
|
955
|
+
if (matchesWorkspaceLayoutCache(model)) {
|
|
956
|
+
return workspaceLayoutCache;
|
|
957
|
+
}
|
|
958
|
+
const bodyRect = resolveBodyRect(model, options);
|
|
959
|
+
const maxState = model.maximizedPaneByPage[model.activePageId];
|
|
960
|
+
const maximizedPaneId = maxState?.maximizedPaneId;
|
|
961
|
+
const themedFrameCtx = resolveFrameThemeCtx(model.activeShellThemeId);
|
|
962
|
+
const renderResult = maximizedPaneId
|
|
963
|
+
? renderMaximizedPane(model.activePageId, model, bodyRect, pagesById, maximizedPaneId, paneScratchPool, themedFrameCtx)
|
|
964
|
+
: renderPageContent(model.activePageId, model, bodyRect, pagesById, themedFrameCtx);
|
|
965
|
+
return rememberWorkspaceLayout(model, renderResult.paneRects);
|
|
966
|
+
}
|
|
967
|
+
function resolveWorkspacePaneRects(model) {
|
|
968
|
+
return resolveWorkspaceLayout(model).paneRects;
|
|
969
|
+
}
|
|
970
|
+
function buildWorkspaceLayoutTree(model) {
|
|
971
|
+
return resolveWorkspaceLayout(model).tree;
|
|
972
|
+
}
|
|
812
973
|
function buildSettingsRowChildren(model, layout) {
|
|
813
974
|
const scrollY = clampSettingsScroll(model, layout);
|
|
814
975
|
const viewportTop = 1;
|
|
@@ -1008,7 +1169,7 @@ export function createFramedApp(options) {
|
|
|
1008
1169
|
}
|
|
1009
1170
|
return helpLineOverride ?? mergeBindingSources(frameKeys, options.globalKeys, activeInputArea?.helpSource ?? activeInputArea?.keyMap, activePage.helpSource ?? activePage.keyMap);
|
|
1010
1171
|
}
|
|
1011
|
-
function resolveLayerMetadata(model, activePage, activeInputArea, modalKeyMap) {
|
|
1172
|
+
function resolveLayerMetadata(model, activePage, activePageModel, activeInputArea, modalKeyMap) {
|
|
1012
1173
|
const settings = resolveFrameSettings(model, options, pagesById, resolvedShellThemes);
|
|
1013
1174
|
const notificationCenter = resolveFrameNotificationCenter(model, options, pagesById);
|
|
1014
1175
|
const workspaceHintSource = resolveWorkspaceHintSource(model, activePage, activeInputArea);
|
|
@@ -1020,23 +1181,29 @@ export function createFramedApp(options) {
|
|
|
1020
1181
|
const quitHint = frameMessage(options.i18n, 'quit.footer', 'Y quit • N stay');
|
|
1021
1182
|
const paletteTitle = model.commandPaletteTitle
|
|
1022
1183
|
?? frameMessage(options.i18n, 'palette.title', 'Command Palette');
|
|
1184
|
+
const activePageTitle = resolveFramePageText(activePage.title, activePageModel) ?? '';
|
|
1023
1185
|
const searchTitle = model.commandPaletteTitle
|
|
1024
|
-
?? activePage.searchTitle
|
|
1186
|
+
?? resolveFramePageText(activePage.searchTitle, activePageModel)
|
|
1025
1187
|
?? frameMessage(options.i18n, 'search.title', 'Search');
|
|
1026
1188
|
const notificationsTitle = notificationCenter == null
|
|
1027
1189
|
? frameMessage(options.i18n, 'notifications.title', 'Notifications')
|
|
1028
1190
|
: `${notificationCenter.title} • ${frameNotificationFilterLabel(options.i18n, notificationCenter.activeFilter)}`;
|
|
1191
|
+
const pageLayers = activePage.layers?.(activePageModel);
|
|
1192
|
+
const workspaceLayer = {
|
|
1193
|
+
title: activePageTitle,
|
|
1194
|
+
hintSource: workspaceHintSource,
|
|
1195
|
+
helpSource: workspaceHelpSource,
|
|
1196
|
+
...pageLayers?.workspace,
|
|
1197
|
+
};
|
|
1198
|
+
const pageModalLayer = {
|
|
1199
|
+
title: activePageTitle,
|
|
1200
|
+
hintSource: modalKeyMap ?? activePage.helpSource ?? activePage.keyMap,
|
|
1201
|
+
helpSource: mergeBindingSources(quitHelpKeys, modalKeyMap, activePage.helpSource ?? activePage.keyMap),
|
|
1202
|
+
...pageLayers?.['page-modal'],
|
|
1203
|
+
};
|
|
1029
1204
|
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
|
-
},
|
|
1205
|
+
workspace: workspaceLayer,
|
|
1206
|
+
'page-modal': pageModalLayer,
|
|
1040
1207
|
settings: {
|
|
1041
1208
|
title: settings?.title ?? frameMessage(options.i18n, 'settings.title', 'Settings'),
|
|
1042
1209
|
hintSource: settingsHint,
|
|
@@ -1071,12 +1238,11 @@ export function createFramedApp(options) {
|
|
|
1071
1238
|
}
|
|
1072
1239
|
function resolvePresentedLayerContext(model) {
|
|
1073
1240
|
const { activePage, activePageModel, inputAreas, activeInputArea, modalKeyMap, pageModalOpen, } = resolveLayerContext(model);
|
|
1074
|
-
const
|
|
1241
|
+
const layerMetadata = resolveLayerMetadata(model, activePage, activePageModel, activeInputArea, modalKeyMap);
|
|
1242
|
+
const controlProjection = projectFrameControls(model, {
|
|
1075
1243
|
pageModalOpen,
|
|
1076
|
-
layers:
|
|
1244
|
+
layers: layerMetadata,
|
|
1077
1245
|
});
|
|
1078
|
-
const activeLayer = layerStack[layerStack.length - 1];
|
|
1079
|
-
const underlyingLayer = layerStack.length > 1 ? layerStack[layerStack.length - 2] : undefined;
|
|
1080
1246
|
return {
|
|
1081
1247
|
activePage,
|
|
1082
1248
|
activePageModel,
|
|
@@ -1084,9 +1250,11 @@ export function createFramedApp(options) {
|
|
|
1084
1250
|
activeInputArea,
|
|
1085
1251
|
modalKeyMap,
|
|
1086
1252
|
pageModalOpen,
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1253
|
+
layerMetadata,
|
|
1254
|
+
controlProjection,
|
|
1255
|
+
layerStack: controlProjection.layerStack,
|
|
1256
|
+
activeLayer: controlProjection.activeLayer,
|
|
1257
|
+
underlyingLayer: controlProjection.underlyingLayer,
|
|
1090
1258
|
};
|
|
1091
1259
|
}
|
|
1092
1260
|
function updateTargetPage(model, targetPageId, targetMsg) {
|
|
@@ -1173,11 +1341,21 @@ export function createFramedApp(options) {
|
|
|
1173
1341
|
activePageId: defaultPageId,
|
|
1174
1342
|
pageOrder,
|
|
1175
1343
|
pageModels,
|
|
1344
|
+
warnedFrameKeyCollisionPages: {},
|
|
1176
1345
|
focusedPaneByPage: {},
|
|
1177
1346
|
scrollByPage: {},
|
|
1178
1347
|
columns: Math.max(1, options.initialColumns ?? 80),
|
|
1179
1348
|
rows: Math.max(1, options.initialRows ?? 24),
|
|
1349
|
+
frameTimeMs: 0,
|
|
1350
|
+
viewTimeMs: 0,
|
|
1351
|
+
diffTimeMs: 0,
|
|
1352
|
+
frameBudgetMs: undefined,
|
|
1353
|
+
frameOverBudget: false,
|
|
1354
|
+
perfHudOpen: false,
|
|
1180
1355
|
helpOpen: false,
|
|
1356
|
+
footerVisible: true,
|
|
1357
|
+
footerTranslateY: 0,
|
|
1358
|
+
footerAnimationGeneration: 0,
|
|
1181
1359
|
helpScrollY: 0,
|
|
1182
1360
|
commandPaletteKind: undefined,
|
|
1183
1361
|
settingsOpen: false,
|
|
@@ -1196,6 +1374,13 @@ export function createFramedApp(options) {
|
|
|
1196
1374
|
runtimeNotifications: createNotificationState(),
|
|
1197
1375
|
runtimeNotificationHistoryFilter: 'ALL',
|
|
1198
1376
|
runtimeNotificationLoopActive: false,
|
|
1377
|
+
activeShellThemeId: undefined,
|
|
1378
|
+
};
|
|
1379
|
+
ensureResolvedShellThemes(resolveFrameCtx());
|
|
1380
|
+
const initialShellTheme = resolveShellThemeForContext(resolvedShellThemes, resolveFrameCtx())
|
|
1381
|
+
?? resolvedShellThemes[0];
|
|
1382
|
+
model = {
|
|
1383
|
+
...model,
|
|
1199
1384
|
activeShellThemeId: initialShellTheme?.id,
|
|
1200
1385
|
};
|
|
1201
1386
|
if (initialShellTheme != null) {
|
|
@@ -1236,6 +1421,20 @@ export function createFramedApp(options) {
|
|
|
1236
1421
|
}, action.issue.atMs);
|
|
1237
1422
|
return applyFrameNotificationState(model, notifications, action.issue.atMs);
|
|
1238
1423
|
}
|
|
1424
|
+
if (action.type === 'push-notification') {
|
|
1425
|
+
if (!frameNotificationOptions.enabled)
|
|
1426
|
+
return [model, []];
|
|
1427
|
+
const nowMs = resolveClock(resolveFrameCtx()).now();
|
|
1428
|
+
const notifications = pushNotification(model.runtimeNotifications, {
|
|
1429
|
+
...action.notification,
|
|
1430
|
+
placement: action.notification.placement ?? frameNotificationOptions.placement,
|
|
1431
|
+
durationMs: action.notification.durationMs === undefined
|
|
1432
|
+
? frameNotificationOptions.durationMs
|
|
1433
|
+
: action.notification.durationMs,
|
|
1434
|
+
overflow: action.notification.overflow ?? frameNotificationOptions.overflow,
|
|
1435
|
+
}, nowMs);
|
|
1436
|
+
return applyFrameNotificationState(model, notifications, nowMs);
|
|
1437
|
+
}
|
|
1239
1438
|
if (action.type === 'notification-tick') {
|
|
1240
1439
|
const notifications = tickNotifications(model.runtimeNotifications, action.atMs);
|
|
1241
1440
|
return applyFrameNotificationState(model, notifications, action.atMs, true);
|
|
@@ -1316,7 +1515,7 @@ export function createFramedApp(options) {
|
|
|
1316
1515
|
},
|
|
1317
1516
|
view(model) {
|
|
1318
1517
|
const themedFrameCtx = resolveFrameThemeCtx(model.activeShellThemeId);
|
|
1319
|
-
const {
|
|
1518
|
+
const { controlProjection, layerStack, activeLayer, } = resolvePresentedLayerContext(model);
|
|
1320
1519
|
const headerResult = resolveHeaderLine(model, options, pagesById, headerScratch, themedFrameCtx);
|
|
1321
1520
|
headerScratch = headerResult.surface;
|
|
1322
1521
|
const header = headerResult.surface;
|
|
@@ -1332,7 +1531,12 @@ export function createFramedApp(options) {
|
|
|
1332
1531
|
frameSurface.clear();
|
|
1333
1532
|
frameSurface.blit(header, 0, 0);
|
|
1334
1533
|
if (model.rows > 1) {
|
|
1335
|
-
|
|
1534
|
+
const footerVisible = model.footerVisible ?? true;
|
|
1535
|
+
const footerTranslateY = Math.max(0, Math.min(1, model.footerTranslateY ?? (footerVisible ? 0 : 1)));
|
|
1536
|
+
const footerRow = model.rows - helpLine.height + Math.floor(footerTranslateY * helpLine.height);
|
|
1537
|
+
if (footerRow < model.rows) {
|
|
1538
|
+
frameSurface.blit(helpLine, 0, footerRow);
|
|
1539
|
+
}
|
|
1336
1540
|
}
|
|
1337
1541
|
let activeResult;
|
|
1338
1542
|
let bodySurface;
|
|
@@ -1354,6 +1558,7 @@ export function createFramedApp(options) {
|
|
|
1354
1558
|
? renderMaximizedPaneInto(model.activePageId, model, bodyRect, pagesById, maximizedPaneId, frameSurface, bodyRect.row, bodyRect.col, paneScratchPool, themedFrameCtx)
|
|
1355
1559
|
: renderPageContentInto(model.activePageId, model, bodyRect, pagesById, frameSurface, bodyRect.row, bodyRect.col, paneScratchPool, themedFrameCtx);
|
|
1356
1560
|
}
|
|
1561
|
+
rememberWorkspaceLayout(model, activeResult.paneRects, headerResult.tabTargets);
|
|
1357
1562
|
const overlays = [];
|
|
1358
1563
|
if (options.overlayFactory != null) {
|
|
1359
1564
|
overlays.push(...options.overlayFactory({
|
|
@@ -1388,7 +1593,11 @@ export function createFramedApp(options) {
|
|
|
1388
1593
|
}
|
|
1389
1594
|
}
|
|
1390
1595
|
if (model.helpOpen) {
|
|
1391
|
-
const
|
|
1596
|
+
const helpSource = controlProjection.helpSource;
|
|
1597
|
+
if (helpSource == null) {
|
|
1598
|
+
throw new Error('createFramedApp: help layer projection is missing a help source');
|
|
1599
|
+
}
|
|
1600
|
+
const helpOverlay = renderHelpOverlay(model, helpSource, options.i18n);
|
|
1392
1601
|
overlays.push(modal({
|
|
1393
1602
|
title: activeLayer.kind === 'help'
|
|
1394
1603
|
? (activeLayer.title ?? frameMessage(options.i18n, 'help.title', 'Keyboard Help'))
|
|
@@ -1432,10 +1641,14 @@ export function createFramedApp(options) {
|
|
|
1432
1641
|
if (model.quitConfirmOpen) {
|
|
1433
1642
|
overlays.push(renderShellQuitOverlay(model.columns, model.rows, options.i18n, themedFrameCtx));
|
|
1434
1643
|
}
|
|
1644
|
+
const dimBackground = overlays.length > 0;
|
|
1645
|
+
if (model.perfHudOpen) {
|
|
1646
|
+
overlays.push(renderFramePerfHudOverlay(model, themedFrameCtx));
|
|
1647
|
+
}
|
|
1435
1648
|
if (bodySurface != null && bodyRect.width > 0 && bodyRect.height > 0) {
|
|
1436
1649
|
frameSurface.blit(bodySurface, bodyRect.col, bodyRect.row);
|
|
1437
1650
|
}
|
|
1438
|
-
return compositeSurfaceInto(frameSurface, frameSurface, overlays, { dim:
|
|
1651
|
+
return compositeSurfaceInto(frameSurface, frameSurface, overlays, { dim: dimBackground });
|
|
1439
1652
|
},
|
|
1440
1653
|
routeRuntimeIssue(issue) {
|
|
1441
1654
|
if (!frameNotificationOptions.enabled)
|
|
@@ -1443,7 +1656,67 @@ export function createFramedApp(options) {
|
|
|
1443
1656
|
return wrapFrameMsg({ type: 'runtime-issue', issue });
|
|
1444
1657
|
},
|
|
1445
1658
|
};
|
|
1446
|
-
|
|
1659
|
+
const framedApp = app;
|
|
1660
|
+
let hostedRunActive = false;
|
|
1661
|
+
framedApp.run = async (runOptions) => {
|
|
1662
|
+
if (hostedRunActive) {
|
|
1663
|
+
throw new Error('createFramedApp: concurrent app.run() calls on the same framed app are not supported');
|
|
1664
|
+
}
|
|
1665
|
+
hostedRunActive = true;
|
|
1666
|
+
const previousFrameCtx = frameCtx;
|
|
1667
|
+
const previousFrameCtxShellThemeId = frameCtxShellThemeId;
|
|
1668
|
+
const previousDefaultFrameCtx = defaultFrameCtx;
|
|
1669
|
+
const previousResolvedShellThemes = resolvedShellThemes;
|
|
1670
|
+
const runtimeCtx = runOptions?.ctx;
|
|
1671
|
+
if (runtimeCtx != null && options.ctx == null) {
|
|
1672
|
+
useRunScopedFrameCtx = true;
|
|
1673
|
+
frameCtx = runtimeCtx;
|
|
1674
|
+
defaultFrameCtx = runtimeCtx;
|
|
1675
|
+
resolvedShellThemes = [];
|
|
1676
|
+
ensureResolvedShellThemes(runtimeCtx);
|
|
1677
|
+
frameCtxShellThemeId = resolveShellThemeForContext(resolvedShellThemes, runtimeCtx)?.id;
|
|
1678
|
+
}
|
|
1679
|
+
const frameBudgetMs = resolveFrameBudgetMs(runOptions, resolveFrameCtx());
|
|
1680
|
+
let pendingTimingSnapshot;
|
|
1681
|
+
let needsTimingHydrationRender = true;
|
|
1682
|
+
try {
|
|
1683
|
+
await runWithLifecycleHooks(framedApp, {
|
|
1684
|
+
...runOptions,
|
|
1685
|
+
mouse: runOptions?.mouse ?? true,
|
|
1686
|
+
}, {
|
|
1687
|
+
beforeRender(model) {
|
|
1688
|
+
if (pendingTimingSnapshot == null)
|
|
1689
|
+
return model;
|
|
1690
|
+
return applyFrameTimingSnapshot(model, pendingTimingSnapshot);
|
|
1691
|
+
},
|
|
1692
|
+
afterRender({ timings }) {
|
|
1693
|
+
pendingTimingSnapshot = summarizeFrameTimings(timings, frameBudgetMs);
|
|
1694
|
+
if (!needsTimingHydrationRender)
|
|
1695
|
+
return;
|
|
1696
|
+
needsTimingHydrationRender = false;
|
|
1697
|
+
return { requestRender: true };
|
|
1698
|
+
},
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
finally {
|
|
1702
|
+
hostedRunActive = false;
|
|
1703
|
+
useRunScopedFrameCtx = false;
|
|
1704
|
+
frameCtx = previousFrameCtx;
|
|
1705
|
+
frameCtxShellThemeId = previousFrameCtxShellThemeId;
|
|
1706
|
+
defaultFrameCtx = previousDefaultFrameCtx;
|
|
1707
|
+
resolvedShellThemes = previousResolvedShellThemes;
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
return framedApp;
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Create and immediately run a batteries-included framed shell.
|
|
1714
|
+
*
|
|
1715
|
+
* This is the one-call hosted path for users who want the frame to own the
|
|
1716
|
+
* runtime pump while `run(app)` remains the low-level TEA contract.
|
|
1717
|
+
*/
|
|
1718
|
+
export async function runFramedApp(options, runOptions) {
|
|
1719
|
+
await createFramedApp(options).run(runOptions);
|
|
1447
1720
|
}
|
|
1448
1721
|
function focusPane(model, paneId) {
|
|
1449
1722
|
if (model.focusedPaneByPage[model.activePageId] === paneId)
|
|
@@ -1459,431 +1732,4 @@ function focusPane(model, paneId) {
|
|
|
1459
1732
|
function resolveBodyRect(model, options) {
|
|
1460
1733
|
return frameBodyRect(model.columns, model.rows, options.bodyTopRows ?? 1, options.bodyBottomRows ?? 1);
|
|
1461
1734
|
}
|
|
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
1735
|
//# sourceMappingURL=app-frame.js.map
|