@flyingrobots/bijou-tui 3.1.0 → 4.1.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/LICENSE +159 -21
- package/README.md +205 -39
- package/dist/app-frame-actions.d.ts +5 -5
- package/dist/app-frame-actions.d.ts.map +1 -1
- package/dist/app-frame-actions.js +74 -9
- package/dist/app-frame-actions.js.map +1 -1
- package/dist/app-frame-i18n.d.ts +12 -0
- package/dist/app-frame-i18n.d.ts.map +1 -0
- package/dist/app-frame-i18n.js +92 -0
- package/dist/app-frame-i18n.js.map +1 -0
- package/dist/app-frame-layers.d.ts +29 -0
- package/dist/app-frame-layers.d.ts.map +1 -0
- package/dist/app-frame-layers.js +104 -0
- package/dist/app-frame-layers.js.map +1 -0
- package/dist/app-frame-palette.d.ts +6 -2
- package/dist/app-frame-palette.d.ts.map +1 -1
- package/dist/app-frame-palette.js +42 -10
- package/dist/app-frame-palette.js.map +1 -1
- package/dist/app-frame-render.d.ts +28 -14
- package/dist/app-frame-render.d.ts.map +1 -1
- package/dist/app-frame-render.js +285 -138
- package/dist/app-frame-render.js.map +1 -1
- package/dist/app-frame-types.d.ts +41 -21
- package/dist/app-frame-types.d.ts.map +1 -1
- package/dist/app-frame-types.js +8 -6
- package/dist/app-frame-types.js.map +1 -1
- package/dist/app-frame-utils.d.ts +8 -3
- package/dist/app-frame-utils.d.ts.map +1 -1
- package/dist/app-frame-utils.js +42 -27
- package/dist/app-frame-utils.js.map +1 -1
- package/dist/app-frame.d.ts +128 -12
- package/dist/app-frame.d.ts.map +1 -1
- package/dist/app-frame.js +1212 -91
- package/dist/app-frame.js.map +1 -1
- package/dist/browsable-list.d.ts +20 -1
- package/dist/browsable-list.d.ts.map +1 -1
- package/dist/browsable-list.js +48 -10
- package/dist/browsable-list.js.map +1 -1
- package/dist/collection-surface.d.ts +8 -0
- package/dist/collection-surface.d.ts.map +1 -0
- package/dist/collection-surface.js +41 -0
- package/dist/collection-surface.js.map +1 -0
- package/dist/command-palette.d.ts +17 -1
- package/dist/command-palette.d.ts.map +1 -1
- package/dist/command-palette.js +50 -20
- package/dist/command-palette.js.map +1 -1
- package/dist/commands.js +1 -1
- package/dist/commands.js.map +1 -1
- package/dist/css/text-style.d.ts +2 -1
- package/dist/css/text-style.d.ts.map +1 -1
- package/dist/css/text-style.js +33 -0
- package/dist/css/text-style.js.map +1 -1
- package/dist/design-language.d.ts +49 -0
- package/dist/design-language.d.ts.map +1 -0
- package/dist/design-language.js +70 -0
- package/dist/design-language.js.map +1 -0
- package/dist/driver.d.ts +6 -1
- package/dist/driver.d.ts.map +1 -1
- package/dist/driver.js +3 -0
- package/dist/driver.js.map +1 -1
- package/dist/eventbus.d.ts.map +1 -1
- package/dist/eventbus.js +21 -1
- package/dist/eventbus.js.map +1 -1
- package/dist/file-picker.d.ts +19 -1
- package/dist/file-picker.d.ts.map +1 -1
- package/dist/file-picker.js +47 -20
- package/dist/file-picker.js.map +1 -1
- package/dist/flex.d.ts +35 -1
- package/dist/flex.d.ts.map +1 -1
- package/dist/flex.js +127 -1
- package/dist/flex.js.map +1 -1
- package/dist/focus-area.d.ts +20 -1
- package/dist/focus-area.d.ts.map +1 -1
- package/dist/focus-area.js +107 -12
- package/dist/focus-area.js.map +1 -1
- package/dist/grid.d.ts +10 -0
- package/dist/grid.d.ts.map +1 -1
- package/dist/grid.js +25 -0
- package/dist/grid.js.map +1 -1
- package/dist/help.d.ts +41 -0
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +50 -0
- package/dist/help.js.map +1 -1
- package/dist/index.d.ts +23 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -16
- package/dist/index.js.map +1 -1
- package/dist/inspector-drawer.d.ts +36 -0
- package/dist/inspector-drawer.d.ts.map +1 -0
- package/dist/inspector-drawer.js +68 -0
- package/dist/inspector-drawer.js.map +1 -0
- package/dist/layout-node-surface.d.ts +17 -0
- package/dist/layout-node-surface.d.ts.map +1 -0
- package/dist/layout-node-surface.js +61 -0
- package/dist/layout-node-surface.js.map +1 -0
- package/dist/navigable-table.d.ts +16 -1
- package/dist/navigable-table.d.ts.map +1 -1
- package/dist/navigable-table.js +32 -1
- package/dist/navigable-table.js.map +1 -1
- package/dist/notification.d.ts +26 -1
- package/dist/notification.d.ts.map +1 -1
- package/dist/notification.js +294 -41
- package/dist/notification.js.map +1 -1
- package/dist/overlay.d.ts +29 -8
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +248 -137
- package/dist/overlay.js.map +1 -1
- package/dist/pager.d.ts +16 -0
- package/dist/pager.d.ts.map +1 -1
- package/dist/pager.js +61 -1
- package/dist/pager.js.map +1 -1
- package/dist/runtime-engine.d.ts +223 -0
- package/dist/runtime-engine.d.ts.map +1 -0
- package/dist/runtime-engine.js +457 -0
- package/dist/runtime-engine.js.map +1 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +33 -19
- package/dist/runtime.js.map +1 -1
- package/dist/shell-quit.d.ts +12 -0
- package/dist/shell-quit.d.ts.map +1 -0
- package/dist/shell-quit.js +36 -0
- package/dist/shell-quit.js.map +1 -0
- package/dist/split-pane.d.ts +12 -1
- package/dist/split-pane.d.ts.map +1 -1
- package/dist/split-pane.js +31 -1
- package/dist/split-pane.js.map +1 -1
- package/dist/status-bar.d.ts +12 -0
- package/dist/status-bar.d.ts.map +1 -1
- package/dist/status-bar.js +45 -16
- package/dist/status-bar.js.map +1 -1
- package/dist/subapp/mount.d.ts.map +1 -1
- package/dist/subapp/mount.js +3 -0
- package/dist/subapp/mount.js.map +1 -1
- package/dist/surface-layout.d.ts +19 -0
- package/dist/surface-layout.d.ts.map +1 -0
- package/dist/surface-layout.js +87 -0
- package/dist/surface-layout.js.map +1 -0
- package/dist/transition-shaders.d.ts +10 -8
- package/dist/transition-shaders.d.ts.map +1 -1
- package/dist/transition-shaders.js +65 -19
- package/dist/transition-shaders.js.map +1 -1
- package/dist/types.d.ts +21 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -1
- package/dist/view-output.d.ts +5 -4
- package/dist/view-output.d.ts.map +1 -1
- package/dist/view-output.js +37 -29
- package/dist/view-output.js.map +1 -1
- package/dist/viewport.d.ts +30 -1
- package/dist/viewport.d.ts.map +1 -1
- package/dist/viewport.js +77 -1
- package/dist/viewport.js.map +1 -1
- package/package.json +6 -3
- package/dist/layout-v3.d.ts +0 -10
- package/dist/layout-v3.d.ts.map +0 -1
- package/dist/layout-v3.js +0 -35
- package/dist/layout-v3.js.map +0 -1
package/dist/app-frame.js
CHANGED
|
@@ -4,25 +4,99 @@
|
|
|
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 { createSurface,
|
|
8
|
-
import {
|
|
7
|
+
import { createSurface, preparePreferenceSections, preferenceListSurface, resolvePreferenceRowLayout, resolveClock, resolveSafeCtx, } from '@flyingrobots/bijou';
|
|
8
|
+
import { helpViewSurface } from './help.js';
|
|
9
|
+
import { createKeyMap } from './keybindings.js';
|
|
9
10
|
import { isKeyMsg, isMouseMsg, isResizeMsg } from './types.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
11
|
+
import { quit } from './commands.js';
|
|
12
|
+
import { compositeSurfaceInto, drawer, modal } from './overlay.js';
|
|
13
|
+
import { isShellQuitConfirmAccept, isShellQuitConfirmDismiss, isShellQuitRequest, renderShellQuitOverlay, shouldUseShellQuitConfirm, } from './shell-quit.js';
|
|
12
14
|
import { commandPalette, commandPaletteKeyMap, } from './command-palette.js';
|
|
15
|
+
import { createPagerStateForSurface, pagerPageDown, pagerPageUp, pagerScrollBy, pagerScrollToBottom, pagerScrollToTop, pagerSurface, } from './pager.js';
|
|
13
16
|
import { restoreLayoutState } from './layout-preset.js';
|
|
14
|
-
import { createNotificationState, notificationsNeedTick, pushNotification, renderNotificationStack, tickNotifications, trimNotificationsToViewport, } from './notification.js';
|
|
17
|
+
import { countNotificationHistory, createNotificationState, dismissNotification, hitTestNotificationStack, notificationsNeedTick, pushNotification, renderNotificationHistorySurface, renderNotificationReviewEntrySurface, renderNotificationStack, tickNotifications, trimNotificationsToViewport, } from './notification.js';
|
|
18
|
+
import { insetLineSurface } from './collection-surface.js';
|
|
19
|
+
import { vstackSurface } from './surface-layout.js';
|
|
15
20
|
import { isFrameScopedMsg, isPageScopedMsg, wrapCmdForPage, emitMsg, emitMsgForPage, wrapFrameMsg, } from './app-frame-types.js';
|
|
16
21
|
import { createFrameKeyMap, frameBodyRect, mergeBindingSources, } from './app-frame-utils.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
22
|
+
import { activeFrameLayer, describeFrameLayerStack, } from './app-frame-layers.js';
|
|
23
|
+
import { frameEndAnchor, frameMessage, frameNotificationCue, frameNotificationFilterLabel, frameStartAnchor, } from './app-frame-i18n.js';
|
|
24
|
+
import { resolveHeaderLine, renderHelpLine, renderPageContent, renderPageContentInto, renderMaximizedPane, renderMaximizedPaneInto, renderTransition, } from './app-frame-render.js';
|
|
25
|
+
import { applyFrameAction, scrollFocusedPane, switchTab, syncPageFrameState, } from './app-frame-actions.js';
|
|
26
|
+
import { handlePaletteKey, openCommandPalette, openSearchPalette, } from './app-frame-palette.js';
|
|
27
|
+
export { activeFrameLayer, describeFrameLayerStack, underlyingFrameLayer, } from './app-frame-layers.js';
|
|
21
28
|
// ---------------------------------------------------------------------------
|
|
22
29
|
// Frame Notification Helpers
|
|
23
30
|
// ---------------------------------------------------------------------------
|
|
24
31
|
const FRAME_NOTIFICATION_TICK_MS = 40;
|
|
25
32
|
const DEFAULT_FRAME_NOTIFICATION_DURATION_MS = 6_000;
|
|
33
|
+
const SETTINGS_FEEDBACK_TOAST_WIDTH = 40;
|
|
34
|
+
const DEFAULT_NOTIFICATION_CENTER_FILTERS = [
|
|
35
|
+
'ALL',
|
|
36
|
+
'ACTIONABLE',
|
|
37
|
+
'ERROR',
|
|
38
|
+
'WARNING',
|
|
39
|
+
'SUCCESS',
|
|
40
|
+
'INFO',
|
|
41
|
+
];
|
|
42
|
+
const quitHelpKeys = createKeyMap()
|
|
43
|
+
.group('Exit', (g) => g
|
|
44
|
+
.bind('q', 'Quit', { type: 'toggle-help' })
|
|
45
|
+
.bind('escape', 'Quit', { type: 'toggle-help' })
|
|
46
|
+
.bind('ctrl+c', 'Quit', { type: 'toggle-help' }));
|
|
47
|
+
const helpLayerHelpKeys = createKeyMap()
|
|
48
|
+
.group('Help', (g) => g
|
|
49
|
+
.bind('escape', 'Close help', { type: 'noop' })
|
|
50
|
+
.bind('?', 'Close help', { type: 'noop' })
|
|
51
|
+
.bind('up', 'Scroll up', { type: 'noop' })
|
|
52
|
+
.bind('down', 'Scroll down', { type: 'noop' })
|
|
53
|
+
.bind('j', 'Scroll down', { type: 'noop' })
|
|
54
|
+
.bind('k', 'Scroll up', { type: 'noop' })
|
|
55
|
+
.bind('d', 'Page down', { type: 'noop' })
|
|
56
|
+
.bind('u', 'Page up', { type: 'noop' })
|
|
57
|
+
.bind('g', 'Top', { type: 'noop' })
|
|
58
|
+
.bind('shift+g', 'Bottom', { type: 'noop' }));
|
|
59
|
+
const settingsHelpKeys = createKeyMap()
|
|
60
|
+
.group('Settings', (g) => g
|
|
61
|
+
.bind('escape', 'Close settings', { type: 'toggle-settings' })
|
|
62
|
+
.bind('f2', 'Close settings', { type: 'toggle-settings' })
|
|
63
|
+
.bind('up', 'Previous row', { type: 'scroll-up' })
|
|
64
|
+
.bind('down', 'Next row', { type: 'scroll-down' })
|
|
65
|
+
.bind('enter', 'Activate setting', { type: 'toggle-settings' })
|
|
66
|
+
.bind('space', 'Activate setting', { type: 'toggle-settings' })
|
|
67
|
+
.bind('j', 'Scroll down', { type: 'scroll-down' })
|
|
68
|
+
.bind('k', 'Scroll up', { type: 'scroll-up' })
|
|
69
|
+
.bind('d', 'Page down', { type: 'page-down' })
|
|
70
|
+
.bind('u', 'Page up', { type: 'page-up' })
|
|
71
|
+
.bind('g', 'Top', { type: 'top' })
|
|
72
|
+
.bind('shift+g', 'Bottom', { type: 'bottom' })
|
|
73
|
+
.bind('/', 'Search', { type: 'open-search' })
|
|
74
|
+
.bind('ctrl+p', 'Open command palette', { type: 'open-palette' })
|
|
75
|
+
.bind(':', 'Open command palette', { type: 'open-palette' })
|
|
76
|
+
.bind('?', 'Toggle help', { type: 'toggle-help' }));
|
|
77
|
+
const notificationCenterHelpKeys = createKeyMap()
|
|
78
|
+
.group('Notifications', (g) => g
|
|
79
|
+
.bind('shift+n', 'Close notification center', { type: 'noop' })
|
|
80
|
+
.bind('up', 'Scroll up', { type: 'noop' })
|
|
81
|
+
.bind('down', 'Scroll down', { type: 'noop' })
|
|
82
|
+
.bind('j', 'Scroll down', { type: 'noop' })
|
|
83
|
+
.bind('k', 'Scroll up', { type: 'noop' })
|
|
84
|
+
.bind('d', 'Page down', { type: 'noop' })
|
|
85
|
+
.bind('u', 'Page up', { type: 'noop' })
|
|
86
|
+
.bind('g', 'Top', { type: 'noop' })
|
|
87
|
+
.bind('shift+g', 'Bottom', { type: 'noop' })
|
|
88
|
+
.bind('f', 'Cycle filter', { type: 'noop' })
|
|
89
|
+
.bind('/', 'Search', { type: 'noop' })
|
|
90
|
+
.bind('ctrl+p', 'Open command palette', { type: 'noop' })
|
|
91
|
+
.bind(':', 'Open command palette', { type: 'noop' })
|
|
92
|
+
.bind('?', 'Toggle help', { type: 'noop' }));
|
|
93
|
+
const quitConfirmHelpKeys = createKeyMap()
|
|
94
|
+
.group('Quit', (g) => g
|
|
95
|
+
.bind('y', 'Quit', { type: 'noop' })
|
|
96
|
+
.bind('enter', 'Quit', { type: 'noop' })
|
|
97
|
+
.bind('n', 'Stay', { type: 'noop' })
|
|
98
|
+
.bind('escape', 'Stay', { type: 'noop' })
|
|
99
|
+
.bind('q', 'Stay', { type: 'noop' }));
|
|
26
100
|
function resolveFrameNotificationOptions(options) {
|
|
27
101
|
if (options.runtimeNotifications === false) {
|
|
28
102
|
return {
|
|
@@ -79,8 +153,13 @@ export function createFramedApp(options) {
|
|
|
79
153
|
if (!pagesById.has(defaultPageId)) {
|
|
80
154
|
throw new Error(`createFramedApp: defaultPageId "${defaultPageId}" not found in pages`);
|
|
81
155
|
}
|
|
82
|
-
const frameKeys = createFrameKeyMap(
|
|
156
|
+
const frameKeys = createFrameKeyMap({
|
|
157
|
+
enableSettings: options.settings != null,
|
|
158
|
+
enableNotifications: options.notificationCenter != null || options.runtimeNotifications !== false,
|
|
159
|
+
i18n: options.i18n,
|
|
160
|
+
});
|
|
83
161
|
const frameNotificationOptions = resolveFrameNotificationOptions(options);
|
|
162
|
+
let composedFrameScratch = null;
|
|
84
163
|
const paletteKeys = commandPaletteKeyMap({
|
|
85
164
|
focusNext: { type: 'cp-next' },
|
|
86
165
|
focusPrev: { type: 'cp-prev' },
|
|
@@ -89,12 +168,301 @@ export function createFramedApp(options) {
|
|
|
89
168
|
select: { type: 'cp-select' },
|
|
90
169
|
close: { type: 'cp-close' },
|
|
91
170
|
});
|
|
171
|
+
function getComposedFrameScratch(width, height) {
|
|
172
|
+
if (composedFrameScratch == null
|
|
173
|
+
|| composedFrameScratch.width !== width
|
|
174
|
+
|| composedFrameScratch.height !== height) {
|
|
175
|
+
composedFrameScratch = createSurface(width, height);
|
|
176
|
+
}
|
|
177
|
+
return composedFrameScratch;
|
|
178
|
+
}
|
|
92
179
|
function withObservedKey(model, cmds, msg, route) {
|
|
93
180
|
const observed = options.observeKey?.(msg, route);
|
|
94
181
|
if (observed === undefined)
|
|
95
182
|
return [...cmds];
|
|
96
183
|
return [emitMsgForPage(model.activePageId, observed), ...cmds];
|
|
97
184
|
}
|
|
185
|
+
function applyQuitRequest(model, msg) {
|
|
186
|
+
if (!shouldUseShellQuitConfirm()) {
|
|
187
|
+
return [model, withObservedKey(model, [quit()], msg, 'frame')];
|
|
188
|
+
}
|
|
189
|
+
if (model.quitConfirmOpen) {
|
|
190
|
+
return [model, withObservedKey(model, [], msg, 'frame')];
|
|
191
|
+
}
|
|
192
|
+
return [{
|
|
193
|
+
...model,
|
|
194
|
+
quitConfirmOpen: true,
|
|
195
|
+
helpOpen: false,
|
|
196
|
+
helpScrollY: 0,
|
|
197
|
+
settingsOpen: false,
|
|
198
|
+
notificationCenterOpen: false,
|
|
199
|
+
commandPalette: undefined,
|
|
200
|
+
commandPaletteEntries: undefined,
|
|
201
|
+
commandPaletteTitle: undefined,
|
|
202
|
+
commandPaletteKind: undefined,
|
|
203
|
+
}, withObservedKey(model, [], msg, 'frame')];
|
|
204
|
+
}
|
|
205
|
+
function closeCommandPalette(model) {
|
|
206
|
+
return {
|
|
207
|
+
...model,
|
|
208
|
+
commandPalette: undefined,
|
|
209
|
+
commandPaletteEntries: undefined,
|
|
210
|
+
commandPaletteTitle: undefined,
|
|
211
|
+
commandPaletteKind: undefined,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function resolveLayerContext(model) {
|
|
215
|
+
const activePage = pagesById.get(model.activePageId);
|
|
216
|
+
const activePageModel = model.pageModels[model.activePageId];
|
|
217
|
+
const inputAreas = resolveInputAreas(activePage, activePageModel);
|
|
218
|
+
const activeInputArea = findInputAreaByPaneId(inputAreas, model.focusedPaneByPage[model.activePageId]);
|
|
219
|
+
const modalKeyMap = activePage.modalKeyMap?.(activePageModel);
|
|
220
|
+
const pageModalOpen = modalKeyMap != null;
|
|
221
|
+
const activeLayer = activeFrameLayer(model, { pageModalOpen });
|
|
222
|
+
return {
|
|
223
|
+
activePage,
|
|
224
|
+
activePageModel,
|
|
225
|
+
inputAreas,
|
|
226
|
+
activeInputArea,
|
|
227
|
+
modalKeyMap,
|
|
228
|
+
pageModalOpen,
|
|
229
|
+
activeLayer,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function resolveWorkspaceHelpSource(activePage, activeInputArea) {
|
|
233
|
+
return mergeBindingSources(frameKeys, quitHelpKeys, options.globalKeys, activeInputArea?.helpSource ?? activeInputArea?.keyMap, activePage.helpSource ?? activePage.keyMap);
|
|
234
|
+
}
|
|
235
|
+
function resolveWorkspaceHintSource(model, activePage, activeInputArea) {
|
|
236
|
+
const helpLineOverride = options.helpLineSource?.({
|
|
237
|
+
model,
|
|
238
|
+
activePage,
|
|
239
|
+
frameKeys,
|
|
240
|
+
globalKeys: options.globalKeys,
|
|
241
|
+
});
|
|
242
|
+
if (typeof helpLineOverride === 'string') {
|
|
243
|
+
return helpLineOverride;
|
|
244
|
+
}
|
|
245
|
+
return helpLineOverride ?? mergeBindingSources(frameKeys, options.globalKeys, activeInputArea?.helpSource ?? activeInputArea?.keyMap, activePage.helpSource ?? activePage.keyMap);
|
|
246
|
+
}
|
|
247
|
+
function resolveLayerMetadata(model, activePage, activeInputArea, modalKeyMap) {
|
|
248
|
+
const settings = resolveFrameSettings(model, options, pagesById);
|
|
249
|
+
const notificationCenter = resolveFrameNotificationCenter(model, options, pagesById);
|
|
250
|
+
const workspaceHintSource = resolveWorkspaceHintSource(model, activePage, activeInputArea);
|
|
251
|
+
const workspaceHelpSource = resolveWorkspaceHelpSource(activePage, activeInputArea);
|
|
252
|
+
const paletteHint = frameMessage(options.i18n, 'palette.hint', 'Enter select • Esc close');
|
|
253
|
+
const helpHint = frameMessage(options.i18n, 'help.hint', 'j/k scroll • d/u page • g/G top/bottom • mouse wheel • ?/Esc close');
|
|
254
|
+
const settingsHint = frameMessage(options.i18n, 'settings.footer', 'F2/Esc close • ↑/↓ rows • Enter toggle • / search • q quit');
|
|
255
|
+
const notificationsHint = frameMessage(options.i18n, 'notifications.footer', 'Shift+N close • f filter • j/k scroll • q quit');
|
|
256
|
+
const quitHint = frameMessage(options.i18n, 'quit.footer', 'Y quit • N stay');
|
|
257
|
+
const paletteTitle = model.commandPaletteTitle
|
|
258
|
+
?? frameMessage(options.i18n, 'palette.title', 'Command Palette');
|
|
259
|
+
const searchTitle = model.commandPaletteTitle
|
|
260
|
+
?? activePage.searchTitle
|
|
261
|
+
?? frameMessage(options.i18n, 'search.title', 'Search');
|
|
262
|
+
const notificationsTitle = notificationCenter == null
|
|
263
|
+
? frameMessage(options.i18n, 'notifications.title', 'Notifications')
|
|
264
|
+
: `${notificationCenter.title} • ${frameNotificationFilterLabel(options.i18n, notificationCenter.activeFilter)}`;
|
|
265
|
+
return {
|
|
266
|
+
workspace: {
|
|
267
|
+
title: activePage.title,
|
|
268
|
+
hintSource: workspaceHintSource,
|
|
269
|
+
helpSource: workspaceHelpSource,
|
|
270
|
+
},
|
|
271
|
+
'page-modal': {
|
|
272
|
+
title: activePage.title,
|
|
273
|
+
hintSource: modalKeyMap ?? activePage.helpSource ?? activePage.keyMap,
|
|
274
|
+
helpSource: mergeBindingSources(quitHelpKeys, modalKeyMap, activePage.helpSource ?? activePage.keyMap),
|
|
275
|
+
},
|
|
276
|
+
settings: {
|
|
277
|
+
title: settings?.title ?? frameMessage(options.i18n, 'settings.title', 'Settings'),
|
|
278
|
+
hintSource: settingsHint,
|
|
279
|
+
helpSource: mergeBindingSources(settingsHelpKeys, quitHelpKeys),
|
|
280
|
+
},
|
|
281
|
+
help: {
|
|
282
|
+
title: frameMessage(options.i18n, 'help.title', 'Keyboard Help'),
|
|
283
|
+
hintSource: helpHint,
|
|
284
|
+
helpSource: helpLayerHelpKeys,
|
|
285
|
+
},
|
|
286
|
+
'notification-center': {
|
|
287
|
+
title: notificationsTitle,
|
|
288
|
+
hintSource: notificationsHint,
|
|
289
|
+
helpSource: mergeBindingSources(notificationCenterHelpKeys, quitHelpKeys),
|
|
290
|
+
},
|
|
291
|
+
search: {
|
|
292
|
+
title: searchTitle,
|
|
293
|
+
hintSource: paletteHint,
|
|
294
|
+
helpSource: mergeBindingSources(paletteKeys, quitHelpKeys),
|
|
295
|
+
},
|
|
296
|
+
'command-palette': {
|
|
297
|
+
title: paletteTitle,
|
|
298
|
+
hintSource: paletteHint,
|
|
299
|
+
helpSource: mergeBindingSources(paletteKeys, quitHelpKeys),
|
|
300
|
+
},
|
|
301
|
+
'quit-confirm': {
|
|
302
|
+
title: frameMessage(options.i18n, 'quit.title', 'Quit?'),
|
|
303
|
+
hintSource: quitHint,
|
|
304
|
+
helpSource: quitConfirmHelpKeys,
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function resolvePresentedLayerContext(model) {
|
|
309
|
+
const { activePage, activePageModel, inputAreas, activeInputArea, modalKeyMap, pageModalOpen, } = resolveLayerContext(model);
|
|
310
|
+
const layerStack = describeFrameLayerStack(model, {
|
|
311
|
+
pageModalOpen,
|
|
312
|
+
layers: resolveLayerMetadata(model, activePage, activeInputArea, modalKeyMap),
|
|
313
|
+
});
|
|
314
|
+
const activeLayer = layerStack[layerStack.length - 1];
|
|
315
|
+
const underlyingLayer = layerStack.length > 1 ? layerStack[layerStack.length - 2] : undefined;
|
|
316
|
+
return {
|
|
317
|
+
activePage,
|
|
318
|
+
activePageModel,
|
|
319
|
+
inputAreas,
|
|
320
|
+
activeInputArea,
|
|
321
|
+
modalKeyMap,
|
|
322
|
+
pageModalOpen,
|
|
323
|
+
layerStack,
|
|
324
|
+
activeLayer,
|
|
325
|
+
underlyingLayer,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function updateTargetPage(model, targetPageId, targetMsg) {
|
|
329
|
+
const targetPage = pagesById.get(targetPageId);
|
|
330
|
+
if (targetPage == null)
|
|
331
|
+
return [model, []];
|
|
332
|
+
const pageModel = model.pageModels[targetPageId];
|
|
333
|
+
const updateResult = targetPage.update(targetMsg, pageModel);
|
|
334
|
+
const [nextPageModel, cmds = []] = updateResult;
|
|
335
|
+
const nextModels = { ...model.pageModels, [targetPageId]: nextPageModel };
|
|
336
|
+
const synced = syncPageFrameState({ ...model, pageModels: nextModels }, targetPageId, pagesById);
|
|
337
|
+
const wrappedCmds = cmds.map((cmd) => wrapCmdForPage(targetPageId, cmd));
|
|
338
|
+
return [synced, wrappedCmds];
|
|
339
|
+
}
|
|
340
|
+
function handleFrameMouse(msg, model) {
|
|
341
|
+
const { activePage, activePageModel, inputAreas, activeLayer, } = resolveLayerContext(model);
|
|
342
|
+
if (activeLayer.kind === 'help') {
|
|
343
|
+
if (msg.action === 'scroll-up' || msg.action === 'scroll-down') {
|
|
344
|
+
return [applyHelpScroll(model, activePage, msg.action === 'scroll-down' ? 3 : -3, frameKeys, paletteKeys, options, pagesById), []];
|
|
345
|
+
}
|
|
346
|
+
return [model, []];
|
|
347
|
+
}
|
|
348
|
+
if (activeLayer.kind === 'search' || activeLayer.kind === 'command-palette') {
|
|
349
|
+
return [model, []];
|
|
350
|
+
}
|
|
351
|
+
if (activeLayer.kind === 'quit-confirm' || activeLayer.kind === 'page-modal') {
|
|
352
|
+
return [model, []];
|
|
353
|
+
}
|
|
354
|
+
if (activeLayer.kind === 'settings') {
|
|
355
|
+
const layout = resolveSettingsLayout(model, options, pagesById);
|
|
356
|
+
if (layout != null) {
|
|
357
|
+
if (msg.action === 'scroll-up' || msg.action === 'scroll-down') {
|
|
358
|
+
if (isInsideSettingsDrawer(msg.col, msg.row, layout, model)) {
|
|
359
|
+
return [
|
|
360
|
+
scrollSettingsBy(model, layout, msg.action === 'scroll-down' ? 3 : -3),
|
|
361
|
+
[],
|
|
362
|
+
];
|
|
363
|
+
}
|
|
364
|
+
return [model, []];
|
|
365
|
+
}
|
|
366
|
+
if (msg.action === 'press' && msg.button === 'left') {
|
|
367
|
+
if (!isInsideSettingsDrawer(msg.col, msg.row, layout, model)) {
|
|
368
|
+
return [model, []];
|
|
369
|
+
}
|
|
370
|
+
const hit = settingsRowAtPosition(msg.col, msg.row, model, layout);
|
|
371
|
+
if (hit == null)
|
|
372
|
+
return [model, []];
|
|
373
|
+
const focusedModel = { ...model, settingsFocusIndex: hit.index };
|
|
374
|
+
if (hit.row.action === undefined || hit.row.enabled === false || hit.row.kind === 'info') {
|
|
375
|
+
return [focusedModel, []];
|
|
376
|
+
}
|
|
377
|
+
return activateSettingsRow(focusedModel, hit.row);
|
|
378
|
+
}
|
|
379
|
+
return [model, []];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (activeLayer.kind === 'notification-center') {
|
|
383
|
+
const layout = resolveNotificationCenterLayout(model, options, pagesById);
|
|
384
|
+
if (layout != null) {
|
|
385
|
+
if (msg.action === 'scroll-up' || msg.action === 'scroll-down') {
|
|
386
|
+
if (isInsideNotificationCenterDrawer(msg.col, msg.row, layout, model)) {
|
|
387
|
+
return [
|
|
388
|
+
scrollNotificationCenterBy(model, layout, msg.action === 'scroll-down' ? 3 : -3),
|
|
389
|
+
[],
|
|
390
|
+
];
|
|
391
|
+
}
|
|
392
|
+
return [model, []];
|
|
393
|
+
}
|
|
394
|
+
if (msg.action === 'press' && msg.button === 'left') {
|
|
395
|
+
return [model, []];
|
|
396
|
+
}
|
|
397
|
+
return [model, []];
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (msg.action === 'press' && msg.button === 'left') {
|
|
401
|
+
if (frameNotificationOptions.enabled) {
|
|
402
|
+
const nowMs = resolveClock(resolveSafeCtx()).now();
|
|
403
|
+
const notificationTarget = hitTestNotificationStack(model.runtimeNotifications, {
|
|
404
|
+
screenWidth: model.columns,
|
|
405
|
+
screenHeight: model.rows,
|
|
406
|
+
margin: frameNotificationOptions.margin,
|
|
407
|
+
gap: frameNotificationOptions.gap,
|
|
408
|
+
ctx: resolveSafeCtx() ?? undefined,
|
|
409
|
+
}, msg.col, msg.row);
|
|
410
|
+
if (notificationTarget?.kind === 'dismiss') {
|
|
411
|
+
return applyFrameNotificationState(model, dismissNotification(model.runtimeNotifications, notificationTarget.item.id, nowMs), nowMs);
|
|
412
|
+
}
|
|
413
|
+
if (notificationTarget != null) {
|
|
414
|
+
return [model, []];
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (msg.row === 0) {
|
|
418
|
+
const header = resolveHeaderLine(model, options, pagesById);
|
|
419
|
+
const tab = header.tabTargets.find((target) => msg.col >= target.startCol && msg.col <= target.endCol);
|
|
420
|
+
if (tab != null) {
|
|
421
|
+
const currentIndex = model.pageOrder.indexOf(model.activePageId);
|
|
422
|
+
const nextIndex = model.pageOrder.indexOf(tab.pageId);
|
|
423
|
+
if (currentIndex >= 0 && nextIndex >= 0 && nextIndex !== currentIndex) {
|
|
424
|
+
return switchTab(model, nextIndex - currentIndex, pagesById, options);
|
|
425
|
+
}
|
|
426
|
+
return [model, []];
|
|
427
|
+
}
|
|
428
|
+
return [model, []];
|
|
429
|
+
}
|
|
430
|
+
const clickedPane = paneHitAtPosition(model, msg.col, msg.row, pagesById, options);
|
|
431
|
+
if (clickedPane != null) {
|
|
432
|
+
const focusedModel = focusPane(model, clickedPane.paneId);
|
|
433
|
+
const inputArea = findInputAreaByPaneId(inputAreas, clickedPane.paneId);
|
|
434
|
+
const areaMsg = inputArea?.mouse?.({
|
|
435
|
+
msg,
|
|
436
|
+
model: activePageModel,
|
|
437
|
+
rect: clickedPane.rect,
|
|
438
|
+
});
|
|
439
|
+
if (areaMsg !== undefined) {
|
|
440
|
+
return [focusedModel, [emitMsgForPage(model.activePageId, areaMsg)]];
|
|
441
|
+
}
|
|
442
|
+
return [focusedModel, [emitMsgForPage(model.activePageId, msg)]];
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (msg.action === 'scroll-up' || msg.action === 'scroll-down') {
|
|
446
|
+
const hoveredPane = paneHitAtPosition(model, msg.col, msg.row, pagesById, options);
|
|
447
|
+
if (hoveredPane != null) {
|
|
448
|
+
const focusedModel = focusPane(model, hoveredPane.paneId);
|
|
449
|
+
const inputArea = findInputAreaByPaneId(inputAreas, hoveredPane.paneId);
|
|
450
|
+
const areaMsg = inputArea?.mouse?.({
|
|
451
|
+
msg,
|
|
452
|
+
model: activePageModel,
|
|
453
|
+
rect: hoveredPane.rect,
|
|
454
|
+
});
|
|
455
|
+
if (areaMsg !== undefined) {
|
|
456
|
+
return [focusedModel, [emitMsgForPage(model.activePageId, areaMsg)]];
|
|
457
|
+
}
|
|
458
|
+
const action = msg.action === 'scroll-down'
|
|
459
|
+
? { type: 'scroll-down' }
|
|
460
|
+
: { type: 'scroll-up' };
|
|
461
|
+
return [scrollFocusedPane(focusedModel, action, pagesById, options), []];
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
98
466
|
function applyFrameNotificationState(model, notifications, nowMs, forceTick = false) {
|
|
99
467
|
const trimmed = trimNotificationsToViewport(notifications, {
|
|
100
468
|
screenWidth: model.columns,
|
|
@@ -113,6 +481,32 @@ export function createFramedApp(options) {
|
|
|
113
481
|
}
|
|
114
482
|
return [nextModel, []];
|
|
115
483
|
}
|
|
484
|
+
function activateSettingsRow(model, row) {
|
|
485
|
+
if (row.action === undefined || row.enabled === false || row.kind === 'info') {
|
|
486
|
+
return [model, []];
|
|
487
|
+
}
|
|
488
|
+
const cmds = [emitMsgForPage(model.activePageId, row.action)];
|
|
489
|
+
if (!frameNotificationOptions.enabled) {
|
|
490
|
+
return [model, cmds];
|
|
491
|
+
}
|
|
492
|
+
const feedback = row.feedback ?? {
|
|
493
|
+
title: 'Setting updated',
|
|
494
|
+
message: `${row.label} updated.`,
|
|
495
|
+
};
|
|
496
|
+
const nowMs = resolveClock(resolveSafeCtx()).now();
|
|
497
|
+
const notifications = pushNotification(model.runtimeNotifications, {
|
|
498
|
+
title: feedback.title ?? 'Setting updated',
|
|
499
|
+
message: feedback.message,
|
|
500
|
+
variant: 'TOAST',
|
|
501
|
+
tone: feedback.tone ?? 'INFO',
|
|
502
|
+
width: SETTINGS_FEEDBACK_TOAST_WIDTH,
|
|
503
|
+
placement: frameNotificationOptions.placement,
|
|
504
|
+
durationMs: feedback.durationMs ?? 2_500,
|
|
505
|
+
overflow: frameNotificationOptions.overflow,
|
|
506
|
+
}, nowMs);
|
|
507
|
+
const [nextModel, notificationCmds] = applyFrameNotificationState(model, notifications, nowMs);
|
|
508
|
+
return [nextModel, [...cmds, ...notificationCmds]];
|
|
509
|
+
}
|
|
116
510
|
const app = {
|
|
117
511
|
init() {
|
|
118
512
|
const pageModels = {};
|
|
@@ -131,6 +525,14 @@ export function createFramedApp(options) {
|
|
|
131
525
|
columns: Math.max(1, options.initialColumns ?? 80),
|
|
132
526
|
rows: Math.max(1, options.initialRows ?? 24),
|
|
133
527
|
helpOpen: false,
|
|
528
|
+
helpScrollY: 0,
|
|
529
|
+
commandPaletteKind: undefined,
|
|
530
|
+
settingsOpen: false,
|
|
531
|
+
notificationCenterOpen: false,
|
|
532
|
+
quitConfirmOpen: false,
|
|
533
|
+
settingsFocusIndex: 0,
|
|
534
|
+
settingsScrollY: 0,
|
|
535
|
+
notificationCenterScrollY: 0,
|
|
134
536
|
transitionProgress: 1,
|
|
135
537
|
transitionGeneration: 0,
|
|
136
538
|
transitionFrame: 0,
|
|
@@ -139,6 +541,7 @@ export function createFramedApp(options) {
|
|
|
139
541
|
dockStateByPage: {},
|
|
140
542
|
splitRatioOverrides: {},
|
|
141
543
|
runtimeNotifications: createNotificationState(),
|
|
544
|
+
runtimeNotificationHistoryFilter: 'ALL',
|
|
142
545
|
runtimeNotificationLoopActive: false,
|
|
143
546
|
};
|
|
144
547
|
for (const pageId of pageOrder) {
|
|
@@ -239,23 +642,226 @@ export function createFramedApp(options) {
|
|
|
239
642
|
}, []];
|
|
240
643
|
}
|
|
241
644
|
if (isKeyMsg(msg)) {
|
|
242
|
-
|
|
645
|
+
const { activePage, activeInputArea, modalKeyMap, activeLayer, } = resolveLayerContext(model);
|
|
646
|
+
if (activeLayer.kind === 'search' || activeLayer.kind === 'command-palette') {
|
|
647
|
+
if (msg.ctrl && !msg.alt && msg.key === 'c') {
|
|
648
|
+
return applyQuitRequest(model, msg);
|
|
649
|
+
}
|
|
650
|
+
if (!msg.ctrl && !msg.alt && !msg.shift && msg.key === 'escape') {
|
|
651
|
+
return [closeCommandPalette(model), withObservedKey(model, [], msg, 'palette')];
|
|
652
|
+
}
|
|
653
|
+
const frameAction = frameKeys.handle(msg);
|
|
654
|
+
if (frameAction?.type === 'open-search') {
|
|
655
|
+
if (activeLayer.kind === 'search') {
|
|
656
|
+
return [closeCommandPalette(model), withObservedKey(model, [], msg, 'palette')];
|
|
657
|
+
}
|
|
658
|
+
return [openSearchPalette(model, frameKeys, options, pagesById), withObservedKey(model, [], msg, 'palette')];
|
|
659
|
+
}
|
|
660
|
+
if (frameAction?.type === 'open-palette') {
|
|
661
|
+
if (activeLayer.kind === 'command-palette') {
|
|
662
|
+
return [closeCommandPalette(model), withObservedKey(model, [], msg, 'palette')];
|
|
663
|
+
}
|
|
664
|
+
return [openCommandPalette(model, frameKeys, options, pagesById), withObservedKey(model, [], msg, 'palette')];
|
|
665
|
+
}
|
|
666
|
+
if (frameAction?.type === 'toggle-notifications') {
|
|
667
|
+
const [nextModel, cmds] = applyFrameAction(frameAction, closeCommandPalette(model), options, pagesById);
|
|
668
|
+
return [nextModel, withObservedKey(model, cmds, msg, 'palette')];
|
|
669
|
+
}
|
|
243
670
|
const [nextModel, cmds] = handlePaletteKey(msg, model, paletteKeys, options, pagesById);
|
|
244
671
|
return [nextModel, withObservedKey(model, cmds, msg, 'palette')];
|
|
245
672
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
673
|
+
if (activeLayer.kind === 'help') {
|
|
674
|
+
if (!msg.ctrl && !msg.alt && (msg.key === '?' || msg.key === 'escape')) {
|
|
675
|
+
return [{ ...model, helpOpen: false, helpScrollY: 0 }, withObservedKey(model, [], msg, 'help')];
|
|
676
|
+
}
|
|
677
|
+
if (isShellQuitRequest(msg)) {
|
|
678
|
+
return applyQuitRequest(model, msg);
|
|
679
|
+
}
|
|
680
|
+
const helpAction = frameKeys.handle(msg);
|
|
681
|
+
if (helpAction && isHelpScrollAction(helpAction)) {
|
|
682
|
+
return [
|
|
683
|
+
applyHelpScrollAction(model, activePage, helpAction, frameKeys, paletteKeys, options, pagesById),
|
|
684
|
+
withObservedKey(model, [], msg, 'help'),
|
|
685
|
+
];
|
|
250
686
|
}
|
|
251
687
|
return [model, withObservedKey(model, [], msg, 'help')];
|
|
252
688
|
}
|
|
253
|
-
|
|
689
|
+
if (activeLayer.kind === 'settings') {
|
|
690
|
+
const layout = resolveSettingsLayout(model, options, pagesById);
|
|
691
|
+
if (layout != null) {
|
|
692
|
+
const settingsFrameAction = frameKeys.handle(msg);
|
|
693
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'escape') {
|
|
694
|
+
return [{
|
|
695
|
+
...model,
|
|
696
|
+
settingsOpen: false,
|
|
697
|
+
}, withObservedKey(model, [], msg, 'frame')];
|
|
698
|
+
}
|
|
699
|
+
if (msg.ctrl && !msg.alt && msg.key === ',') {
|
|
700
|
+
return [{
|
|
701
|
+
...model,
|
|
702
|
+
settingsOpen: false,
|
|
703
|
+
}, withObservedKey(model, [], msg, 'frame')];
|
|
704
|
+
}
|
|
705
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'f2') {
|
|
706
|
+
return [{
|
|
707
|
+
...model,
|
|
708
|
+
settingsOpen: false,
|
|
709
|
+
}, withObservedKey(model, [], msg, 'frame')];
|
|
710
|
+
}
|
|
711
|
+
if (!msg.ctrl && !msg.alt && msg.key === '?') {
|
|
712
|
+
return [{ ...model, helpOpen: true }, withObservedKey(model, [], msg, 'frame')];
|
|
713
|
+
}
|
|
714
|
+
if (isShellQuitRequest(msg)) {
|
|
715
|
+
return applyQuitRequest(model, msg);
|
|
716
|
+
}
|
|
717
|
+
if (options.enableCommandPalette && !msg.ctrl && !msg.alt && msg.key === '/') {
|
|
718
|
+
return [openSearchPalette(model, frameKeys, options, pagesById), withObservedKey(model, [], msg, 'frame')];
|
|
719
|
+
}
|
|
720
|
+
if (options.enableCommandPalette && ((msg.ctrl && !msg.alt && msg.key === 'p') || (!msg.ctrl && !msg.alt && msg.key === ':'))) {
|
|
721
|
+
return [openCommandPalette(model, frameKeys, options, pagesById), withObservedKey(model, [], msg, 'frame')];
|
|
722
|
+
}
|
|
723
|
+
if (settingsFrameAction?.type === 'toggle-notifications') {
|
|
724
|
+
const [nextModel, cmds] = applyFrameAction(settingsFrameAction, model, options, pagesById);
|
|
725
|
+
return [nextModel, withObservedKey(model, cmds, msg, 'frame')];
|
|
726
|
+
}
|
|
727
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'up') {
|
|
728
|
+
return [moveSettingsFocus(model, layout, -1), withObservedKey(model, [], msg, 'frame')];
|
|
729
|
+
}
|
|
730
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'down') {
|
|
731
|
+
return [moveSettingsFocus(model, layout, 1), withObservedKey(model, [], msg, 'frame')];
|
|
732
|
+
}
|
|
733
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'j') {
|
|
734
|
+
return [scrollSettingsBy(model, layout, 1), withObservedKey(model, [], msg, 'frame')];
|
|
735
|
+
}
|
|
736
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'k') {
|
|
737
|
+
return [scrollSettingsBy(model, layout, -1), withObservedKey(model, [], msg, 'frame')];
|
|
738
|
+
}
|
|
739
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'd') {
|
|
740
|
+
return [scrollSettingsBy(model, layout, Math.max(1, layout.contentHeight - 1)), withObservedKey(model, [], msg, 'frame')];
|
|
741
|
+
}
|
|
742
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'u') {
|
|
743
|
+
return [scrollSettingsBy(model, layout, -Math.max(1, layout.contentHeight - 1)), withObservedKey(model, [], msg, 'frame')];
|
|
744
|
+
}
|
|
745
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'g') {
|
|
746
|
+
return [{ ...model, settingsScrollY: 0 }, withObservedKey(model, [], msg, 'frame')];
|
|
747
|
+
}
|
|
748
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'G') {
|
|
749
|
+
return [{ ...model, settingsScrollY: layout.maxScrollY }, withObservedKey(model, [], msg, 'frame')];
|
|
750
|
+
}
|
|
751
|
+
if (!msg.ctrl && !msg.alt && (msg.key === 'enter' || msg.key === 'space')) {
|
|
752
|
+
const row = layout.rows[clampSettingsFocus(model, layout)]?.row;
|
|
753
|
+
if (row?.action !== undefined && row.enabled !== false && row.kind !== 'info') {
|
|
754
|
+
const [nextModel, cmds] = activateSettingsRow(model, row);
|
|
755
|
+
return [nextModel, withObservedKey(model, cmds, msg, 'frame')];
|
|
756
|
+
}
|
|
757
|
+
return [model, withObservedKey(model, [], msg, 'frame')];
|
|
758
|
+
}
|
|
759
|
+
return [model, withObservedKey(model, [], msg, 'frame')];
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (activeLayer.kind === 'notification-center') {
|
|
763
|
+
const layout = resolveNotificationCenterLayout(model, options, pagesById);
|
|
764
|
+
if (layout != null) {
|
|
765
|
+
const centerFrameAction = frameKeys.handle(msg);
|
|
766
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'escape') {
|
|
767
|
+
return [{
|
|
768
|
+
...model,
|
|
769
|
+
notificationCenterOpen: false,
|
|
770
|
+
notificationCenterScrollY: 0,
|
|
771
|
+
}, withObservedKey(model, [], msg, 'frame')];
|
|
772
|
+
}
|
|
773
|
+
if (isShellQuitRequest(msg)) {
|
|
774
|
+
return applyQuitRequest(model, msg);
|
|
775
|
+
}
|
|
776
|
+
if (centerFrameAction?.type === 'toggle-notifications') {
|
|
777
|
+
const [nextModel, cmds] = applyFrameAction(centerFrameAction, model, options, pagesById);
|
|
778
|
+
return [nextModel, withObservedKey(model, cmds, msg, 'frame')];
|
|
779
|
+
}
|
|
780
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'f2') {
|
|
781
|
+
const [nextModel, cmds] = applyFrameAction({ type: 'toggle-settings' }, model, options, pagesById);
|
|
782
|
+
return [nextModel, withObservedKey(model, cmds, msg, 'frame')];
|
|
783
|
+
}
|
|
784
|
+
if (!msg.ctrl && !msg.alt && msg.key === '?') {
|
|
785
|
+
return [{
|
|
786
|
+
...model,
|
|
787
|
+
helpOpen: true,
|
|
788
|
+
notificationCenterOpen: false,
|
|
789
|
+
notificationCenterScrollY: 0,
|
|
790
|
+
}, withObservedKey(model, [], msg, 'frame')];
|
|
791
|
+
}
|
|
792
|
+
if (options.enableCommandPalette && !msg.ctrl && !msg.alt && msg.key === '/') {
|
|
793
|
+
return [openSearchPalette({
|
|
794
|
+
...model,
|
|
795
|
+
notificationCenterOpen: false,
|
|
796
|
+
notificationCenterScrollY: 0,
|
|
797
|
+
}, frameKeys, options, pagesById), withObservedKey(model, [], msg, 'frame')];
|
|
798
|
+
}
|
|
799
|
+
if (options.enableCommandPalette && ((msg.ctrl && !msg.alt && msg.key === 'p') || (!msg.ctrl && !msg.alt && msg.key === ':'))) {
|
|
800
|
+
return [openCommandPalette({
|
|
801
|
+
...model,
|
|
802
|
+
notificationCenterOpen: false,
|
|
803
|
+
notificationCenterScrollY: 0,
|
|
804
|
+
}, frameKeys, options, pagesById), withObservedKey(model, [], msg, 'frame')];
|
|
805
|
+
}
|
|
806
|
+
if (!msg.ctrl && !msg.alt && (msg.key === 'up' || msg.key === 'k')) {
|
|
807
|
+
return [scrollNotificationCenterBy(model, layout, -1), withObservedKey(model, [], msg, 'frame')];
|
|
808
|
+
}
|
|
809
|
+
if (!msg.ctrl && !msg.alt && (msg.key === 'down' || msg.key === 'j')) {
|
|
810
|
+
return [scrollNotificationCenterBy(model, layout, 1), withObservedKey(model, [], msg, 'frame')];
|
|
811
|
+
}
|
|
812
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'd') {
|
|
813
|
+
return [scrollNotificationCenterBy(model, layout, Math.max(1, layout.contentHeight - 2)), withObservedKey(model, [], msg, 'frame')];
|
|
814
|
+
}
|
|
815
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'u') {
|
|
816
|
+
return [scrollNotificationCenterBy(model, layout, -Math.max(1, layout.contentHeight - 2)), withObservedKey(model, [], msg, 'frame')];
|
|
817
|
+
}
|
|
818
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'g') {
|
|
819
|
+
return [{ ...model, notificationCenterScrollY: 0 }, withObservedKey(model, [], msg, 'frame')];
|
|
820
|
+
}
|
|
821
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'G') {
|
|
822
|
+
return [{ ...model, notificationCenterScrollY: layout.maxScrollY }, withObservedKey(model, [], msg, 'frame')];
|
|
823
|
+
}
|
|
824
|
+
if (!msg.ctrl && !msg.alt && msg.key === 'f') {
|
|
825
|
+
const [nextModel, cmds] = cycleNotificationCenterFilter(model, layout);
|
|
826
|
+
return [nextModel, withObservedKey(model, cmds, msg, 'frame')];
|
|
827
|
+
}
|
|
828
|
+
return [model, withObservedKey(model, [], msg, 'frame')];
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (activeLayer.kind === 'quit-confirm') {
|
|
832
|
+
if (isShellQuitConfirmAccept(msg)) {
|
|
833
|
+
return [{
|
|
834
|
+
...model,
|
|
835
|
+
quitConfirmOpen: false,
|
|
836
|
+
}, withObservedKey(model, [quit()], msg, 'frame')];
|
|
837
|
+
}
|
|
838
|
+
if (isShellQuitConfirmDismiss(msg)) {
|
|
839
|
+
return [{
|
|
840
|
+
...model,
|
|
841
|
+
quitConfirmOpen: false,
|
|
842
|
+
}, withObservedKey(model, [], msg, 'frame')];
|
|
843
|
+
}
|
|
844
|
+
return [model, withObservedKey(model, [], msg, 'frame')];
|
|
845
|
+
}
|
|
846
|
+
if (activeLayer.kind === 'page-modal' && modalKeyMap != null) {
|
|
847
|
+
const modalAction = modalKeyMap.handle(msg);
|
|
848
|
+
if (modalAction !== undefined) {
|
|
849
|
+
return [model, withObservedKey(model, [emitMsgForPage(model.activePageId, modalAction)], msg, 'page')];
|
|
850
|
+
}
|
|
851
|
+
return [model, withObservedKey(model, [], msg, 'page')];
|
|
852
|
+
}
|
|
853
|
+
if (isShellQuitRequest(msg)) {
|
|
854
|
+
return applyQuitRequest(model, msg);
|
|
855
|
+
}
|
|
856
|
+
const paneAction = activeInputArea?.keyMap?.handle(msg);
|
|
254
857
|
const pageAction = activePage.keyMap?.handle(msg);
|
|
255
858
|
const globalAction = options.globalKeys?.handle(msg);
|
|
256
859
|
const frameAction = frameKeys.handle(msg);
|
|
257
860
|
const keyPriority = options.keyPriority ?? 'frame-first';
|
|
258
861
|
if (keyPriority === 'page-first') {
|
|
862
|
+
if (paneAction !== undefined) {
|
|
863
|
+
return [model, withObservedKey(model, [emitMsgForPage(model.activePageId, paneAction)], msg, 'page')];
|
|
864
|
+
}
|
|
259
865
|
if (pageAction !== undefined) {
|
|
260
866
|
return [model, withObservedKey(model, [emitMsgForPage(model.activePageId, pageAction)], msg, 'page')];
|
|
261
867
|
}
|
|
@@ -263,6 +869,9 @@ export function createFramedApp(options) {
|
|
|
263
869
|
return [model, withObservedKey(model, [emitMsg(globalAction)], msg, 'global')];
|
|
264
870
|
}
|
|
265
871
|
if (frameAction !== undefined) {
|
|
872
|
+
if (frameAction.type === 'open-search' && options.enableCommandPalette) {
|
|
873
|
+
return [openSearchPalette(model, frameKeys, options, pagesById), withObservedKey(model, [], msg, 'frame')];
|
|
874
|
+
}
|
|
266
875
|
if (frameAction.type === 'open-palette' && options.enableCommandPalette) {
|
|
267
876
|
return [openCommandPalette(model, frameKeys, options, pagesById), withObservedKey(model, [], msg, 'frame')];
|
|
268
877
|
}
|
|
@@ -273,12 +882,18 @@ export function createFramedApp(options) {
|
|
|
273
882
|
}
|
|
274
883
|
if (frameAction !== undefined) {
|
|
275
884
|
// Handle palette opening here since applyFrameAction doesn't have access to palette deps
|
|
885
|
+
if (frameAction.type === 'open-search' && options.enableCommandPalette) {
|
|
886
|
+
return [openSearchPalette(model, frameKeys, options, pagesById), withObservedKey(model, [], msg, 'frame')];
|
|
887
|
+
}
|
|
276
888
|
if (frameAction.type === 'open-palette' && options.enableCommandPalette) {
|
|
277
889
|
return [openCommandPalette(model, frameKeys, options, pagesById), withObservedKey(model, [], msg, 'frame')];
|
|
278
890
|
}
|
|
279
891
|
const [nextModel, cmds] = applyFrameAction(frameAction, model, options, pagesById);
|
|
280
892
|
return [nextModel, withObservedKey(model, cmds, msg, 'frame')];
|
|
281
893
|
}
|
|
894
|
+
if (paneAction !== undefined) {
|
|
895
|
+
return [model, withObservedKey(model, [emitMsgForPage(model.activePageId, paneAction)], msg, 'page')];
|
|
896
|
+
}
|
|
282
897
|
if (globalAction !== undefined) {
|
|
283
898
|
return [model, withObservedKey(model, [emitMsg(globalAction)], msg, 'global')];
|
|
284
899
|
}
|
|
@@ -288,54 +903,51 @@ export function createFramedApp(options) {
|
|
|
288
903
|
return [model, withObservedKey(model, [], msg, 'unhandled')];
|
|
289
904
|
}
|
|
290
905
|
if (isMouseMsg(msg)) {
|
|
291
|
-
|
|
906
|
+
const frameResult = handleFrameMouse(msg, model);
|
|
907
|
+
if (frameResult != null)
|
|
908
|
+
return frameResult;
|
|
909
|
+
return updateTargetPage(model, model.activePageId, msg);
|
|
292
910
|
}
|
|
293
911
|
// Custom message path: route to originating page when command messages are scoped.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const targetPage = pagesById.get(targetPageId);
|
|
297
|
-
if (targetPage == null)
|
|
298
|
-
return [model, []];
|
|
299
|
-
const targetMsg = scoped?.msg ?? msg;
|
|
300
|
-
const pageModel = model.pageModels[targetPageId];
|
|
301
|
-
const updateResult = targetPage.update(targetMsg, pageModel);
|
|
302
|
-
let nextPageModel = pageModel; // Default to current
|
|
303
|
-
let cmds = [];
|
|
304
|
-
if (updateResult !== undefined && updateResult !== null) {
|
|
305
|
-
if (Array.isArray(updateResult)) {
|
|
306
|
-
nextPageModel = (updateResult[0] ?? pageModel);
|
|
307
|
-
cmds = (updateResult[1] ?? []);
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
nextPageModel = updateResult;
|
|
311
|
-
}
|
|
912
|
+
if (isPageScopedMsg(msg)) {
|
|
913
|
+
return updateTargetPage(model, msg.pageId, msg.msg);
|
|
312
914
|
}
|
|
313
|
-
|
|
314
|
-
const synced = syncPageFrameState({ ...model, pageModels: nextModels }, targetPageId, pagesById);
|
|
315
|
-
const wrappedCmds = Array.isArray(cmds) ? cmds.map((cmd) => wrapCmdForPage(targetPageId, cmd)) : [];
|
|
316
|
-
return [synced, wrappedCmds];
|
|
915
|
+
return updateTargetPage(model, model.activePageId, msg);
|
|
317
916
|
},
|
|
318
917
|
view(model) {
|
|
319
|
-
const activePage =
|
|
320
|
-
const header =
|
|
321
|
-
const helpLine = renderHelpLine(model,
|
|
322
|
-
const bodyRect =
|
|
918
|
+
const { activePage, layerStack, activeLayer, } = resolvePresentedLayerContext(model);
|
|
919
|
+
const header = resolveHeaderLine(model, options, pagesById).surface;
|
|
920
|
+
const helpLine = renderHelpLine(model, activeLayer, options.i18n, resolveNotificationFooterCue(model, options, pagesById));
|
|
921
|
+
const bodyRect = resolveBodyRect(model, options);
|
|
323
922
|
// Check for maximized pane — if set, render only that pane at full body rect
|
|
324
923
|
const maxState = model.maximizedPaneByPage[model.activePageId];
|
|
325
924
|
const maximizedPaneId = maxState?.maximizedPaneId;
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
925
|
+
const frameSurface = getComposedFrameScratch(model.columns, model.rows);
|
|
926
|
+
frameSurface.clear();
|
|
927
|
+
frameSurface.blit(header, 0, 0);
|
|
928
|
+
if (model.rows > 1) {
|
|
929
|
+
frameSurface.blit(helpLine, 0, model.rows - 1);
|
|
930
|
+
}
|
|
931
|
+
let activeResult;
|
|
932
|
+
let bodySurface;
|
|
330
933
|
const activeTransition = model.activeTransition ?? options.transition;
|
|
331
934
|
if (model.previousPageId != null && model.transitionProgress < 1 && activeTransition && activeTransition !== 'none') {
|
|
935
|
+
const activeBodyResult = maximizedPaneId
|
|
936
|
+
? renderMaximizedPane(model.activePageId, model, bodyRect, pagesById, maximizedPaneId)
|
|
937
|
+
: renderPageContent(model.activePageId, model, bodyRect, pagesById);
|
|
938
|
+
activeResult = activeBodyResult;
|
|
939
|
+
bodySurface = activeBodyResult.surface;
|
|
332
940
|
const ctx = resolveSafeCtx();
|
|
333
941
|
if (ctx) {
|
|
334
942
|
const prevResult = renderPageContent(model.previousPageId, model, bodyRect, pagesById);
|
|
335
|
-
|
|
943
|
+
bodySurface = renderTransition(prevResult.surface, activeBodyResult.surface, activeTransition, model.transitionProgress, bodyRect.width, bodyRect.height, ctx, model.transitionFrame);
|
|
336
944
|
}
|
|
337
945
|
}
|
|
338
|
-
|
|
946
|
+
else {
|
|
947
|
+
activeResult = maximizedPaneId
|
|
948
|
+
? renderMaximizedPaneInto(model.activePageId, model, bodyRect, pagesById, maximizedPaneId, frameSurface)
|
|
949
|
+
: renderPageContentInto(model.activePageId, model, bodyRect, pagesById, frameSurface);
|
|
950
|
+
}
|
|
339
951
|
const overlays = [];
|
|
340
952
|
if (options.overlayFactory != null) {
|
|
341
953
|
overlays.push(...options.overlayFactory({
|
|
@@ -355,12 +967,31 @@ export function createFramedApp(options) {
|
|
|
355
967
|
ctx: ctx ?? undefined,
|
|
356
968
|
}));
|
|
357
969
|
}
|
|
970
|
+
if (model.settingsOpen) {
|
|
971
|
+
const settingsLayer = layerStack.find((layer) => layer.kind === 'settings');
|
|
972
|
+
const settingsOverlay = renderSettingsDrawer(model, options, pagesById, settingsLayer?.title);
|
|
973
|
+
if (settingsOverlay != null) {
|
|
974
|
+
overlays.push(settingsOverlay);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (model.notificationCenterOpen) {
|
|
978
|
+
const notificationLayer = layerStack.find((layer) => layer.kind === 'notification-center');
|
|
979
|
+
const notificationCenterOverlay = renderNotificationCenterDrawer(model, options, pagesById, notificationLayer?.title);
|
|
980
|
+
if (notificationCenterOverlay != null) {
|
|
981
|
+
overlays.push(notificationCenterOverlay);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
358
984
|
if (model.helpOpen) {
|
|
359
|
-
const
|
|
985
|
+
const helpOverlay = renderHelpOverlay(model, activePage, frameKeys, paletteKeys, options, pagesById);
|
|
360
986
|
overlays.push(modal({
|
|
361
|
-
title:
|
|
362
|
-
|
|
363
|
-
|
|
987
|
+
title: activeLayer.kind === 'help'
|
|
988
|
+
? (activeLayer.title ?? frameMessage(options.i18n, 'help.title', 'Keyboard Help'))
|
|
989
|
+
: frameMessage(options.i18n, 'help.title', 'Keyboard Help'),
|
|
990
|
+
body: helpOverlay.body,
|
|
991
|
+
hint: typeof activeLayer.hintSource === 'string'
|
|
992
|
+
? activeLayer.hintSource
|
|
993
|
+
: frameMessage(options.i18n, 'help.hint', 'j/k scroll • d/u page • g/G top/bottom • mouse wheel • ?/Esc close'),
|
|
994
|
+
width: helpOverlay.body.width + 4,
|
|
364
995
|
screenWidth: model.columns,
|
|
365
996
|
screenHeight: model.rows,
|
|
366
997
|
}));
|
|
@@ -368,25 +999,27 @@ export function createFramedApp(options) {
|
|
|
368
999
|
if (model.commandPalette != null) {
|
|
369
1000
|
const paletteWidth = Math.max(20, Math.min(80, model.columns - 4));
|
|
370
1001
|
const paletteBody = commandPalette(model.commandPalette, { width: Math.max(16, paletteWidth - 4) });
|
|
1002
|
+
const paletteLayer = activeLayer.kind === 'search' || activeLayer.kind === 'command-palette'
|
|
1003
|
+
? activeLayer
|
|
1004
|
+
: undefined;
|
|
371
1005
|
overlays.push(modal({
|
|
372
|
-
title: 'Command Palette',
|
|
1006
|
+
title: paletteLayer?.title ?? model.commandPaletteTitle ?? frameMessage(options.i18n, 'palette.title', 'Command Palette'),
|
|
373
1007
|
body: paletteBody,
|
|
374
|
-
hint:
|
|
1008
|
+
hint: typeof paletteLayer?.hintSource === 'string'
|
|
1009
|
+
? paletteLayer.hintSource
|
|
1010
|
+
: frameMessage(options.i18n, 'palette.hint', 'Enter select • Esc close'),
|
|
375
1011
|
width: paletteWidth,
|
|
376
1012
|
screenWidth: model.columns,
|
|
377
1013
|
screenHeight: model.rows,
|
|
378
1014
|
}));
|
|
379
1015
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
overlays,
|
|
388
|
-
dimBackground: overlays.length > 0,
|
|
389
|
-
});
|
|
1016
|
+
if (model.quitConfirmOpen) {
|
|
1017
|
+
overlays.push(renderShellQuitOverlay(model.columns, model.rows, options.i18n));
|
|
1018
|
+
}
|
|
1019
|
+
if (bodySurface != null && bodyRect.width > 0 && bodyRect.height > 0) {
|
|
1020
|
+
frameSurface.blit(bodySurface, bodyRect.col, bodyRect.row);
|
|
1021
|
+
}
|
|
1022
|
+
return compositeSurfaceInto(frameSurface, frameSurface, overlays, { dim: overlays.length > 0 });
|
|
390
1023
|
},
|
|
391
1024
|
routeRuntimeIssue(issue) {
|
|
392
1025
|
if (!frameNotificationOptions.enabled)
|
|
@@ -396,37 +1029,525 @@ export function createFramedApp(options) {
|
|
|
396
1029
|
};
|
|
397
1030
|
return app;
|
|
398
1031
|
}
|
|
399
|
-
function
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
1032
|
+
function focusPane(model, paneId) {
|
|
1033
|
+
if (model.focusedPaneByPage[model.activePageId] === paneId)
|
|
1034
|
+
return model;
|
|
1035
|
+
return {
|
|
1036
|
+
...model,
|
|
1037
|
+
focusedPaneByPage: {
|
|
1038
|
+
...model.focusedPaneByPage,
|
|
1039
|
+
[model.activePageId]: paneId,
|
|
1040
|
+
},
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
function paneHitAtPosition(model, col, row, pagesById, options) {
|
|
1044
|
+
const bodyRect = resolveBodyRect(model, options);
|
|
1045
|
+
const maxState = model.maximizedPaneByPage[model.activePageId];
|
|
1046
|
+
const maximizedPaneId = maxState?.maximizedPaneId;
|
|
1047
|
+
const renderResult = maximizedPaneId
|
|
1048
|
+
? renderMaximizedPane(model.activePageId, model, bodyRect, pagesById, maximizedPaneId)
|
|
1049
|
+
: renderPageContent(model.activePageId, model, bodyRect, pagesById);
|
|
1050
|
+
for (const [paneId, rect] of renderResult.paneRects.entries()) {
|
|
1051
|
+
if (col >= rect.col
|
|
1052
|
+
&& col < rect.col + rect.width
|
|
1053
|
+
&& row >= rect.row
|
|
1054
|
+
&& row < rect.row + rect.height) {
|
|
1055
|
+
return { paneId, rect };
|
|
1056
|
+
}
|
|
407
1057
|
}
|
|
408
|
-
|
|
409
|
-
|
|
1058
|
+
return undefined;
|
|
1059
|
+
}
|
|
1060
|
+
function resolveBodyRect(model, options) {
|
|
1061
|
+
return frameBodyRect(model.columns, model.rows, options.bodyTopRows ?? 1, options.bodyBottomRows ?? 1);
|
|
1062
|
+
}
|
|
1063
|
+
function renderHelpOverlay(model, activePage, frameKeys, paletteKeys, options, pagesById) {
|
|
1064
|
+
const activePageModel = model.pageModels[model.activePageId];
|
|
1065
|
+
const activeInputArea = findInputAreaByPaneId(resolveInputAreas(activePage, activePageModel), model.focusedPaneByPage[model.activePageId]);
|
|
1066
|
+
const modalKeyMap = activePage.modalKeyMap?.(activePageModel);
|
|
1067
|
+
const settings = resolveFrameSettings(model, options, pagesById);
|
|
1068
|
+
const notificationCenter = resolveFrameNotificationCenter(model, options, pagesById);
|
|
1069
|
+
const workspaceHintSource = options.helpLineSource?.({
|
|
1070
|
+
model,
|
|
1071
|
+
activePage,
|
|
1072
|
+
frameKeys,
|
|
1073
|
+
globalKeys: options.globalKeys,
|
|
1074
|
+
});
|
|
1075
|
+
const workspaceHelpSource = mergeBindingSources(frameKeys, quitHelpKeys, options.globalKeys, activeInputArea?.helpSource ?? activeInputArea?.keyMap, activePage.helpSource ?? activePage.keyMap);
|
|
1076
|
+
const layerStack = describeFrameLayerStack(model, {
|
|
1077
|
+
pageModalOpen: modalKeyMap != null,
|
|
1078
|
+
layers: {
|
|
1079
|
+
workspace: {
|
|
1080
|
+
title: activePage.title,
|
|
1081
|
+
hintSource: typeof workspaceHintSource === 'string'
|
|
1082
|
+
? workspaceHintSource
|
|
1083
|
+
: workspaceHintSource ?? mergeBindingSources(frameKeys, options.globalKeys, activeInputArea?.helpSource ?? activeInputArea?.keyMap, activePage.helpSource ?? activePage.keyMap),
|
|
1084
|
+
helpSource: workspaceHelpSource,
|
|
1085
|
+
},
|
|
1086
|
+
'page-modal': {
|
|
1087
|
+
title: activePage.title,
|
|
1088
|
+
hintSource: modalKeyMap ?? activePage.helpSource ?? activePage.keyMap,
|
|
1089
|
+
helpSource: mergeBindingSources(quitHelpKeys, modalKeyMap, activePage.helpSource ?? activePage.keyMap),
|
|
1090
|
+
},
|
|
1091
|
+
settings: {
|
|
1092
|
+
title: settings?.title ?? frameMessage(options.i18n, 'settings.title', 'Settings'),
|
|
1093
|
+
hintSource: frameMessage(options.i18n, 'settings.footer', 'F2/Esc close • ↑/↓ rows • Enter toggle • / search • q quit'),
|
|
1094
|
+
helpSource: mergeBindingSources(settingsHelpKeys, quitHelpKeys),
|
|
1095
|
+
},
|
|
1096
|
+
help: {
|
|
1097
|
+
title: frameMessage(options.i18n, 'help.title', 'Keyboard Help'),
|
|
1098
|
+
hintSource: frameMessage(options.i18n, 'help.hint', 'j/k scroll • d/u page • g/G top/bottom • mouse wheel • ?/Esc close'),
|
|
1099
|
+
helpSource: helpLayerHelpKeys,
|
|
1100
|
+
},
|
|
1101
|
+
'notification-center': {
|
|
1102
|
+
title: notificationCenter == null
|
|
1103
|
+
? frameMessage(options.i18n, 'notifications.title', 'Notifications')
|
|
1104
|
+
: `${notificationCenter.title} • ${frameNotificationFilterLabel(options.i18n, notificationCenter.activeFilter)}`,
|
|
1105
|
+
hintSource: frameMessage(options.i18n, 'notifications.footer', 'Shift+N close • f filter • j/k scroll • q quit'),
|
|
1106
|
+
helpSource: mergeBindingSources(notificationCenterHelpKeys, quitHelpKeys),
|
|
1107
|
+
},
|
|
1108
|
+
search: {
|
|
1109
|
+
title: model.commandPaletteTitle ?? activePage.searchTitle ?? frameMessage(options.i18n, 'search.title', 'Search'),
|
|
1110
|
+
hintSource: frameMessage(options.i18n, 'palette.hint', 'Enter select • Esc close'),
|
|
1111
|
+
helpSource: mergeBindingSources(paletteKeys, quitHelpKeys),
|
|
1112
|
+
},
|
|
1113
|
+
'command-palette': {
|
|
1114
|
+
title: model.commandPaletteTitle ?? frameMessage(options.i18n, 'palette.title', 'Command Palette'),
|
|
1115
|
+
hintSource: frameMessage(options.i18n, 'palette.hint', 'Enter select • Esc close'),
|
|
1116
|
+
helpSource: mergeBindingSources(paletteKeys, quitHelpKeys),
|
|
1117
|
+
},
|
|
1118
|
+
'quit-confirm': {
|
|
1119
|
+
title: frameMessage(options.i18n, 'quit.title', 'Quit?'),
|
|
1120
|
+
hintSource: frameMessage(options.i18n, 'quit.footer', 'Y quit • N stay'),
|
|
1121
|
+
helpSource: quitConfirmHelpKeys,
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
const beneathHelpLayer = layerStack.length > 1 ? layerStack[layerStack.length - 2] : undefined;
|
|
1126
|
+
const source = beneathHelpLayer?.helpSource ?? workspaceHelpSource;
|
|
1127
|
+
const maxDialogWidth = Math.max(28, Math.min(model.columns - 4, 88));
|
|
1128
|
+
const bodyWidth = Math.max(20, maxDialogWidth - 4);
|
|
1129
|
+
const helpSurface = helpViewSurface(source, {
|
|
1130
|
+
title: undefined,
|
|
1131
|
+
width: bodyWidth,
|
|
1132
|
+
});
|
|
1133
|
+
const pagerHeight = Math.max(4, Math.min(helpSurface.height + 1, Math.max(4, model.rows - 8)));
|
|
1134
|
+
const pagerState = createPagerStateForSurface(helpSurface, {
|
|
1135
|
+
width: bodyWidth,
|
|
1136
|
+
height: pagerHeight,
|
|
1137
|
+
});
|
|
1138
|
+
const scrollY = Math.max(0, Math.min(model.helpScrollY, pagerState.scroll.maxY));
|
|
1139
|
+
const scrolledState = {
|
|
1140
|
+
...pagerState,
|
|
1141
|
+
scroll: {
|
|
1142
|
+
...pagerState.scroll,
|
|
1143
|
+
y: scrollY,
|
|
1144
|
+
},
|
|
1145
|
+
};
|
|
1146
|
+
return {
|
|
1147
|
+
body: pagerSurface(helpSurface, scrolledState, { showScrollbar: true, showStatus: true }),
|
|
1148
|
+
maxScrollY: pagerState.scroll.maxY,
|
|
1149
|
+
scrollY,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
function isHelpScrollAction(action) {
|
|
1153
|
+
return action.type === 'scroll-up'
|
|
1154
|
+
|| action.type === 'scroll-down'
|
|
1155
|
+
|| action.type === 'page-up'
|
|
1156
|
+
|| action.type === 'page-down'
|
|
1157
|
+
|| action.type === 'top'
|
|
1158
|
+
|| action.type === 'bottom';
|
|
1159
|
+
}
|
|
1160
|
+
function applyHelpScrollAction(model, activePage, action, frameKeys, paletteKeys, options, pagesById) {
|
|
1161
|
+
const overlay = renderHelpOverlay(model, activePage, frameKeys, paletteKeys, options, pagesById);
|
|
1162
|
+
const pagerState = {
|
|
1163
|
+
scroll: {
|
|
1164
|
+
y: overlay.scrollY,
|
|
1165
|
+
maxY: overlay.maxScrollY,
|
|
1166
|
+
x: 0,
|
|
1167
|
+
maxX: 0,
|
|
1168
|
+
totalLines: overlay.maxScrollY + Math.max(1, overlay.body.height - 1),
|
|
1169
|
+
visibleLines: Math.max(1, overlay.body.height - 1),
|
|
1170
|
+
},
|
|
1171
|
+
content: '',
|
|
1172
|
+
width: overlay.body.width,
|
|
1173
|
+
height: overlay.body.height,
|
|
1174
|
+
};
|
|
1175
|
+
let next = pagerState;
|
|
1176
|
+
switch (action.type) {
|
|
1177
|
+
case 'scroll-up':
|
|
1178
|
+
next = pagerScrollBy(pagerState, -1);
|
|
1179
|
+
break;
|
|
1180
|
+
case 'scroll-down':
|
|
1181
|
+
next = pagerScrollBy(pagerState, 1);
|
|
1182
|
+
break;
|
|
1183
|
+
case 'page-up':
|
|
1184
|
+
next = pagerPageUp(pagerState);
|
|
1185
|
+
break;
|
|
1186
|
+
case 'page-down':
|
|
1187
|
+
next = pagerPageDown(pagerState);
|
|
1188
|
+
break;
|
|
1189
|
+
case 'top':
|
|
1190
|
+
next = pagerScrollToTop(pagerState);
|
|
1191
|
+
break;
|
|
1192
|
+
case 'bottom':
|
|
1193
|
+
next = pagerScrollToBottom(pagerState);
|
|
1194
|
+
break;
|
|
410
1195
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
1196
|
+
return {
|
|
1197
|
+
...model,
|
|
1198
|
+
helpScrollY: next.scroll.y,
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
function applyHelpScroll(model, activePage, delta, frameKeys, paletteKeys, options, pagesById) {
|
|
1202
|
+
const overlay = renderHelpOverlay(model, activePage, frameKeys, paletteKeys, options, pagesById);
|
|
1203
|
+
return {
|
|
1204
|
+
...model,
|
|
1205
|
+
helpScrollY: Math.max(0, Math.min(overlay.maxScrollY, overlay.scrollY + delta)),
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
function resolveFrameSettings(model, options, pagesById) {
|
|
1209
|
+
const activePage = pagesById.get(model.activePageId);
|
|
1210
|
+
return options.settings?.({
|
|
1211
|
+
model,
|
|
1212
|
+
activePage,
|
|
1213
|
+
pageModel: model.pageModels[model.activePageId],
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
function resolveFrameNotificationCenter(model, options, pagesById) {
|
|
1217
|
+
const activePage = pagesById.get(model.activePageId);
|
|
1218
|
+
const pageModel = model.pageModels[model.activePageId];
|
|
1219
|
+
const provided = options.notificationCenter?.({
|
|
1220
|
+
model,
|
|
1221
|
+
activePage,
|
|
1222
|
+
pageModel,
|
|
1223
|
+
runtimeNotifications: model.runtimeNotifications,
|
|
1224
|
+
});
|
|
1225
|
+
if (provided != null) {
|
|
1226
|
+
const filters = provided.filters != null && provided.filters.length > 0
|
|
1227
|
+
? provided.filters
|
|
1228
|
+
: DEFAULT_NOTIFICATION_CENTER_FILTERS;
|
|
1229
|
+
const activeFilter = filters.includes(provided.activeFilter ?? 'ALL')
|
|
1230
|
+
? (provided.activeFilter ?? 'ALL')
|
|
1231
|
+
: filters[0];
|
|
1232
|
+
return {
|
|
1233
|
+
title: provided.title ?? frameMessage(options.i18n, 'notifications.title', 'Notifications'),
|
|
1234
|
+
state: provided.state,
|
|
1235
|
+
filters,
|
|
1236
|
+
activeFilter,
|
|
1237
|
+
onFilterChange: provided.onFilterChange,
|
|
1238
|
+
};
|
|
414
1239
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
1240
|
+
if (options.runtimeNotifications === false)
|
|
1241
|
+
return undefined;
|
|
1242
|
+
return {
|
|
1243
|
+
title: frameMessage(options.i18n, 'notifications.title', 'Notifications'),
|
|
1244
|
+
state: model.runtimeNotifications,
|
|
1245
|
+
filters: DEFAULT_NOTIFICATION_CENTER_FILTERS,
|
|
1246
|
+
activeFilter: model.runtimeNotificationHistoryFilter,
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
function resolveSettingsLayout(model, options, pagesById) {
|
|
1250
|
+
const settings = resolveFrameSettings(model, options, pagesById);
|
|
1251
|
+
if (settings == null)
|
|
1252
|
+
return undefined;
|
|
1253
|
+
const sections = settings.sections.filter((section) => section.rows.length > 0);
|
|
1254
|
+
if (sections.length === 0)
|
|
1255
|
+
return undefined;
|
|
1256
|
+
const drawerWidth = resolveSettingsDrawerWidth(model.columns);
|
|
1257
|
+
const anchor = frameStartAnchor(options.i18n);
|
|
1258
|
+
const startCol = anchor === 'left' ? 0 : Math.max(0, model.columns - drawerWidth);
|
|
1259
|
+
const contentWidth = Math.max(16, drawerWidth - 4);
|
|
1260
|
+
const preferenceSections = preparePreferenceSections(toPreferenceSections(sections));
|
|
1261
|
+
const rows = [];
|
|
1262
|
+
let line = 0;
|
|
1263
|
+
for (let sectionIndex = 0; sectionIndex < preferenceSections.length; sectionIndex++) {
|
|
1264
|
+
const section = preferenceSections[sectionIndex];
|
|
1265
|
+
if (sectionIndex > 0) {
|
|
1266
|
+
line += 1;
|
|
1267
|
+
}
|
|
1268
|
+
line += 1;
|
|
1269
|
+
line += 1;
|
|
1270
|
+
for (let rowIndex = 0; rowIndex < section.rows.length; rowIndex++) {
|
|
1271
|
+
const preparedRow = section.rows[rowIndex];
|
|
1272
|
+
const row = sections[sectionIndex].rows[rowIndex];
|
|
1273
|
+
const rowLayout = resolvePreferenceRowLayout(preparedRow, contentWidth);
|
|
1274
|
+
rows.push({
|
|
1275
|
+
index: rows.length,
|
|
1276
|
+
line,
|
|
1277
|
+
height: rowLayout.height,
|
|
1278
|
+
row,
|
|
1279
|
+
});
|
|
1280
|
+
line += rowLayout.height;
|
|
1281
|
+
if (rowIndex < section.rows.length - 1) {
|
|
1282
|
+
line += 1;
|
|
1283
|
+
}
|
|
426
1284
|
}
|
|
427
1285
|
}
|
|
1286
|
+
const contentHeight = Math.max(1, model.rows - 2);
|
|
1287
|
+
const totalLines = Math.max(1, line);
|
|
1288
|
+
const maxScrollY = Math.max(0, totalLines - contentHeight);
|
|
1289
|
+
return {
|
|
1290
|
+
settings: {
|
|
1291
|
+
...settings,
|
|
1292
|
+
sections,
|
|
1293
|
+
},
|
|
1294
|
+
preferenceSections,
|
|
1295
|
+
rows,
|
|
1296
|
+
anchor,
|
|
1297
|
+
startCol,
|
|
1298
|
+
drawerWidth,
|
|
1299
|
+
contentWidth,
|
|
1300
|
+
contentHeight,
|
|
1301
|
+
totalLines,
|
|
1302
|
+
maxScrollY,
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function resolveNotificationCenterDrawerWidth(columns) {
|
|
1306
|
+
const boundedColumns = Math.max(28, columns);
|
|
1307
|
+
return Math.min(Math.max(32, Math.floor(boundedColumns * 0.34)), Math.max(32, boundedColumns - 4), 52);
|
|
1308
|
+
}
|
|
1309
|
+
function resolveNotificationCenterLayout(model, options, pagesById) {
|
|
1310
|
+
const center = resolveFrameNotificationCenter(model, options, pagesById);
|
|
1311
|
+
if (center == null)
|
|
1312
|
+
return undefined;
|
|
1313
|
+
const drawerWidth = resolveNotificationCenterDrawerWidth(model.columns);
|
|
1314
|
+
const anchor = frameEndAnchor(options.i18n);
|
|
1315
|
+
const startCol = anchor === 'left' ? 0 : Math.max(0, model.columns - drawerWidth);
|
|
1316
|
+
const contentWidth = Math.max(18, drawerWidth - 4);
|
|
1317
|
+
const content = renderNotificationCenterSurface(center, contentWidth, options.i18n);
|
|
1318
|
+
const contentHeight = Math.max(1, model.rows - 2);
|
|
1319
|
+
const pagerState = createPagerStateForSurface(content, {
|
|
1320
|
+
width: contentWidth,
|
|
1321
|
+
height: contentHeight,
|
|
1322
|
+
});
|
|
1323
|
+
return {
|
|
1324
|
+
center,
|
|
1325
|
+
anchor,
|
|
1326
|
+
startCol,
|
|
1327
|
+
drawerWidth,
|
|
1328
|
+
contentWidth,
|
|
1329
|
+
contentHeight,
|
|
1330
|
+
content,
|
|
1331
|
+
maxScrollY: pagerState.scroll.maxY,
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
function resolveSettingsDrawerWidth(columns) {
|
|
1335
|
+
const boundedColumns = Math.max(24, columns);
|
|
1336
|
+
return Math.min(Math.max(28, Math.floor(boundedColumns * 0.3)), Math.max(28, boundedColumns - 4), 42);
|
|
1337
|
+
}
|
|
1338
|
+
function clampSettingsFocus(model, layout) {
|
|
1339
|
+
if (layout.rows.length === 0)
|
|
1340
|
+
return 0;
|
|
1341
|
+
return Math.max(0, Math.min(model.settingsFocusIndex, layout.rows.length - 1));
|
|
1342
|
+
}
|
|
1343
|
+
function clampSettingsScroll(model, layout) {
|
|
1344
|
+
return Math.max(0, Math.min(model.settingsScrollY, layout.maxScrollY));
|
|
1345
|
+
}
|
|
1346
|
+
function resolveInputAreas(page, pageModel) {
|
|
1347
|
+
return page.inputAreas?.(pageModel) ?? [];
|
|
1348
|
+
}
|
|
1349
|
+
function findInputAreaByPaneId(inputAreas, paneId) {
|
|
1350
|
+
if (paneId == null)
|
|
1351
|
+
return undefined;
|
|
1352
|
+
return inputAreas.find((area) => area.paneId === paneId);
|
|
1353
|
+
}
|
|
1354
|
+
function ensureSettingsRangeVisible(startLine, height, scrollY, visibleLines, maxScrollY) {
|
|
1355
|
+
let next = scrollY;
|
|
1356
|
+
const endLine = startLine + Math.max(1, height) - 1;
|
|
1357
|
+
if (startLine < next) {
|
|
1358
|
+
next = startLine;
|
|
1359
|
+
}
|
|
1360
|
+
else if (endLine >= next + visibleLines) {
|
|
1361
|
+
next = endLine - visibleLines + 1;
|
|
1362
|
+
}
|
|
1363
|
+
return Math.max(0, Math.min(next, maxScrollY));
|
|
1364
|
+
}
|
|
1365
|
+
function moveSettingsFocus(model, layout, delta) {
|
|
1366
|
+
if (layout.rows.length === 0)
|
|
1367
|
+
return model;
|
|
1368
|
+
const nextFocus = Math.max(0, Math.min(clampSettingsFocus(model, layout) + delta, layout.rows.length - 1));
|
|
1369
|
+
const focusedRow = layout.rows[nextFocus];
|
|
1370
|
+
return {
|
|
1371
|
+
...model,
|
|
1372
|
+
settingsFocusIndex: nextFocus,
|
|
1373
|
+
settingsScrollY: ensureSettingsRangeVisible(focusedRow.line, focusedRow.height, clampSettingsScroll(model, layout), layout.contentHeight, layout.maxScrollY),
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
function scrollSettingsBy(model, layout, delta) {
|
|
1377
|
+
return {
|
|
1378
|
+
...model,
|
|
1379
|
+
settingsScrollY: Math.max(0, Math.min(clampSettingsScroll(model, layout) + delta, layout.maxScrollY)),
|
|
1380
|
+
};
|
|
428
1381
|
}
|
|
429
|
-
function
|
|
430
|
-
return
|
|
1382
|
+
function scrollNotificationCenterBy(model, layout, delta) {
|
|
1383
|
+
return {
|
|
1384
|
+
...model,
|
|
1385
|
+
notificationCenterScrollY: Math.max(0, Math.min(model.notificationCenterScrollY + delta, layout.maxScrollY)),
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
function cycleNotificationCenterFilter(model, layout) {
|
|
1389
|
+
const filters = layout.center.filters;
|
|
1390
|
+
if (filters.length < 2)
|
|
1391
|
+
return [model, []];
|
|
1392
|
+
const currentIndex = Math.max(0, filters.indexOf(layout.center.activeFilter));
|
|
1393
|
+
const nextFilter = filters[(currentIndex + 1) % filters.length];
|
|
1394
|
+
if (layout.center.onFilterChange != null) {
|
|
1395
|
+
const action = layout.center.onFilterChange(nextFilter);
|
|
1396
|
+
return [{
|
|
1397
|
+
...model,
|
|
1398
|
+
notificationCenterScrollY: 0,
|
|
1399
|
+
}, action === undefined ? [] : [emitMsgForPage(model.activePageId, action)]];
|
|
1400
|
+
}
|
|
1401
|
+
return [{
|
|
1402
|
+
...model,
|
|
1403
|
+
runtimeNotificationHistoryFilter: nextFilter,
|
|
1404
|
+
notificationCenterScrollY: 0,
|
|
1405
|
+
}, []];
|
|
1406
|
+
}
|
|
1407
|
+
function isInsideSettingsDrawer(col, row, layout, model) {
|
|
1408
|
+
return col >= layout.startCol
|
|
1409
|
+
&& col < layout.startCol + layout.drawerWidth
|
|
1410
|
+
&& row >= 0
|
|
1411
|
+
&& row < model.rows;
|
|
1412
|
+
}
|
|
1413
|
+
function settingsRowAtPosition(col, row, model, layout) {
|
|
1414
|
+
if (!isInsideSettingsDrawer(col, row, layout, model))
|
|
1415
|
+
return undefined;
|
|
1416
|
+
if (row <= 0 || row >= model.rows - 1)
|
|
1417
|
+
return undefined;
|
|
1418
|
+
const contentLine = (row - 1) + clampSettingsScroll(model, layout);
|
|
1419
|
+
return layout.rows.find((candidate) => contentLine >= candidate.line && contentLine < candidate.line + candidate.height);
|
|
1420
|
+
}
|
|
1421
|
+
function isInsideNotificationCenterDrawer(col, row, layout, model) {
|
|
1422
|
+
return col >= layout.startCol
|
|
1423
|
+
&& col < layout.startCol + layout.drawerWidth
|
|
1424
|
+
&& row >= 0
|
|
1425
|
+
&& row < model.rows;
|
|
1426
|
+
}
|
|
1427
|
+
function renderSettingsDrawer(model, options, pagesById, titleOverride) {
|
|
1428
|
+
const layout = resolveSettingsLayout(model, options, pagesById);
|
|
1429
|
+
if (layout == null)
|
|
1430
|
+
return undefined;
|
|
1431
|
+
const scrollY = clampSettingsScroll(model, layout);
|
|
1432
|
+
const content = renderSettingsSurface(layout, model);
|
|
1433
|
+
const pagerState = createPagerStateForSurface(content, {
|
|
1434
|
+
width: layout.contentWidth,
|
|
1435
|
+
height: layout.contentHeight,
|
|
1436
|
+
});
|
|
1437
|
+
const scrolledState = {
|
|
1438
|
+
...pagerState,
|
|
1439
|
+
scroll: {
|
|
1440
|
+
...pagerState.scroll,
|
|
1441
|
+
y: scrollY,
|
|
1442
|
+
},
|
|
1443
|
+
};
|
|
1444
|
+
const body = pagerSurface(content, scrolledState, {
|
|
1445
|
+
showScrollbar: layout.maxScrollY > 0,
|
|
1446
|
+
showStatus: false,
|
|
1447
|
+
});
|
|
1448
|
+
return drawer({
|
|
1449
|
+
anchor: layout.anchor,
|
|
1450
|
+
title: titleOverride ?? layout.settings.title ?? frameMessage(options.i18n, 'settings.title', 'Settings'),
|
|
1451
|
+
content: body,
|
|
1452
|
+
borderToken: layout.settings.borderToken,
|
|
1453
|
+
bgToken: layout.settings.bgToken,
|
|
1454
|
+
ctx: resolveSafeCtx() ?? undefined,
|
|
1455
|
+
width: layout.drawerWidth,
|
|
1456
|
+
screenWidth: model.columns,
|
|
1457
|
+
screenHeight: model.rows,
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
function renderNotificationCenterDrawer(model, options, pagesById, titleOverride) {
|
|
1461
|
+
const layout = resolveNotificationCenterLayout(model, options, pagesById);
|
|
1462
|
+
if (layout == null)
|
|
1463
|
+
return undefined;
|
|
1464
|
+
const pagerState = createPagerStateForSurface(layout.content, {
|
|
1465
|
+
width: layout.contentWidth,
|
|
1466
|
+
height: layout.contentHeight,
|
|
1467
|
+
});
|
|
1468
|
+
const scrolledState = {
|
|
1469
|
+
...pagerState,
|
|
1470
|
+
scroll: {
|
|
1471
|
+
...pagerState.scroll,
|
|
1472
|
+
y: Math.max(0, Math.min(model.notificationCenterScrollY, layout.maxScrollY)),
|
|
1473
|
+
},
|
|
1474
|
+
};
|
|
1475
|
+
const body = pagerSurface(layout.content, scrolledState, {
|
|
1476
|
+
showScrollbar: layout.maxScrollY > 0,
|
|
1477
|
+
showStatus: false,
|
|
1478
|
+
});
|
|
1479
|
+
return drawer({
|
|
1480
|
+
anchor: layout.anchor,
|
|
1481
|
+
title: titleOverride ?? `${layout.center.title} • ${frameNotificationFilterLabel(options.i18n, layout.center.activeFilter)}`,
|
|
1482
|
+
content: body,
|
|
1483
|
+
width: layout.drawerWidth,
|
|
1484
|
+
screenWidth: model.columns,
|
|
1485
|
+
screenHeight: model.rows,
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
function renderSettingsSurface(layout, model) {
|
|
1489
|
+
const focusedIndex = clampSettingsFocus(model, layout);
|
|
1490
|
+
return preferenceListSurface(layout.preferenceSections, {
|
|
1491
|
+
width: layout.contentWidth,
|
|
1492
|
+
selectedRowId: layout.rows[focusedIndex]?.row.id,
|
|
1493
|
+
ctx: resolveSafeCtx() ?? undefined,
|
|
1494
|
+
theme: layout.settings.listTheme,
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
function toPreferenceSections(sections) {
|
|
1498
|
+
return sections.map((section) => ({
|
|
1499
|
+
id: section.id,
|
|
1500
|
+
title: section.title,
|
|
1501
|
+
rows: section.rows.map((row) => toPreferenceRow(row)),
|
|
1502
|
+
}));
|
|
1503
|
+
}
|
|
1504
|
+
function toPreferenceRow(row) {
|
|
1505
|
+
return {
|
|
1506
|
+
id: row.id,
|
|
1507
|
+
label: row.label,
|
|
1508
|
+
description: row.description,
|
|
1509
|
+
valueLabel: row.valueLabel,
|
|
1510
|
+
kind: row.kind,
|
|
1511
|
+
checked: row.checked,
|
|
1512
|
+
enabled: row.enabled,
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
function resolveNotificationFooterCue(model, options, pagesById) {
|
|
1516
|
+
const center = resolveFrameNotificationCenter(model, options, pagesById);
|
|
1517
|
+
if (center == null)
|
|
1518
|
+
return undefined;
|
|
1519
|
+
const liveCount = center.state.items.length;
|
|
1520
|
+
const archivedCount = countNotificationHistory(center.state, center.activeFilter);
|
|
1521
|
+
return frameNotificationCue(options.i18n, liveCount, archivedCount);
|
|
1522
|
+
}
|
|
1523
|
+
function renderNotificationCenterSurface(center, width, i18n) {
|
|
1524
|
+
const ctx = resolveSafeCtx() ?? undefined;
|
|
1525
|
+
const rows = [
|
|
1526
|
+
insetLineSurface(`Live: ${center.state.items.length} • Archived: ${center.state.history.length}`, width),
|
|
1527
|
+
insetLineSurface(`Filter: ${frameNotificationFilterLabel(i18n, center.activeFilter)}`, width),
|
|
1528
|
+
];
|
|
1529
|
+
const liveItems = [...center.state.items].sort((left, right) => right.updatedAtMs - left.updatedAtMs || right.id - left.id);
|
|
1530
|
+
if (liveItems.length > 0) {
|
|
1531
|
+
rows.push(createSurface(width, 1));
|
|
1532
|
+
rows.push(insetLineSurface(ctx == null ? 'Current stack' : ctx.style.bold('Current stack'), width));
|
|
1533
|
+
rows.push(createSurface(width, 1));
|
|
1534
|
+
for (let index = 0; index < liveItems.length; index++) {
|
|
1535
|
+
rows.push(renderNotificationReviewEntrySurface(liveItems[index], {
|
|
1536
|
+
width,
|
|
1537
|
+
ctx,
|
|
1538
|
+
metaLabel: `${liveItems[index].variant} • live`,
|
|
1539
|
+
}));
|
|
1540
|
+
if (index < liveItems.length - 1)
|
|
1541
|
+
rows.push(createSurface(width, 1));
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
rows.push(createSurface(width, 1));
|
|
1545
|
+
rows.push(renderNotificationHistorySurface(center.state, {
|
|
1546
|
+
width,
|
|
1547
|
+
height: Number.MAX_SAFE_INTEGER,
|
|
1548
|
+
filter: center.activeFilter,
|
|
1549
|
+
ctx,
|
|
1550
|
+
}));
|
|
1551
|
+
return vstackSurface(...rows);
|
|
431
1552
|
}
|
|
432
1553
|
//# sourceMappingURL=app-frame.js.map
|