gloomberb 0.11.1 → 0.11.2
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 +10 -9
- package/README.zh-CN.md +6 -6
- package/package.json +1 -1
- package/src/api-client/data.ts +7 -0
- package/src/api-client/index.test.ts +47 -0
- package/src/api-client/index.ts +61 -0
- package/src/api-client/paths.ts +16 -0
- package/src/api-client/types.ts +35 -0
- package/src/app/pane-runtime/index.tsx +4 -1
- package/src/app/pane-runtime/plugin-bindings.test.ts +117 -0
- package/src/app/pane-runtime/plugin-bindings.ts +25 -4
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +25 -14
- package/src/app/runtime/desktop-deeplink.test.ts +16 -0
- package/src/app/runtime/desktop-deeplink.ts +66 -2
- package/src/app/runtime/desktop-menu.ts +3 -0
- package/src/app/runtime/plugin-bindings.ts +30 -28
- package/src/app.tsx +9 -2
- package/src/cli/commands/automation.test.ts +9 -0
- package/src/cli/commands/automation.ts +22 -73
- package/src/cli/index.ts +0 -2
- package/src/components/chart/composite/composite-chart.test.tsx +45 -2
- package/src/components/chart/composite/composite-chart.tsx +12 -9
- package/src/components/chart/composite/scene.ts +0 -21
- package/src/components/chart/composite/tools.ts +49 -0
- package/src/components/command-bar/commands/direct/index.ts +4 -0
- package/src/components/command-bar/commands/registry.ts +8 -1
- package/src/components/command-bar/layout-items/current-layout.ts +1 -1
- package/src/components/command-bar/pane-templates/workflow.ts +4 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -0
- package/src/components/command-bar/routes/root/shortcuts.test.ts +19 -0
- package/src/components/command-bar/routes/root/shortcuts.ts +3 -1
- package/src/components/command-bar/surface/index.test.tsx +6 -6
- package/src/components/command-bar/surface/pane.test.tsx +66 -2
- package/src/components/command-bar/view-model.test.ts +2 -1
- package/src/components/command-bar/workflow/ops.test.ts +54 -0
- package/src/components/command-bar/workflow/ops.ts +1 -1
- package/src/components/layout/detached-pane-shell.tsx +74 -10
- package/src/components/layout/pane/title.ts +31 -3
- package/src/components/layout/shell/active-drag.ts +4 -11
- package/src/components/layout/shell/drag/index.ts +4 -20
- package/src/components/layout/shell/drag/runtime.ts +3 -4
- package/src/components/layout/shell/index.test.tsx +17 -3
- package/src/components/layout/shell/index.tsx +86 -30
- package/src/components/layout/shell/menu.test.ts +37 -1
- package/src/components/layout/shell/menu.ts +14 -0
- package/src/components/layout/shell/native/pointer-runtime.ts +22 -8
- package/src/components/layout/shell/pane/actions.ts +0 -5
- package/src/components/layout/shell/pane/management-shortcuts.ts +9 -4
- package/src/components/layout/shell/shortcuts.ts +6 -3
- package/src/components/layout/shell/ticker-link-menu.test.ts +82 -0
- package/src/components/layout/shell/ticker-link-menu.ts +78 -0
- package/src/components/layout/status-bar.test.tsx +62 -61
- package/src/components/layout/status-bar.tsx +53 -75
- package/src/core/state/app/layout-reducer.ts +36 -0
- package/src/core/state/app/layout.ts +10 -5
- package/src/core/state/app/state.ts +0 -13
- package/src/core/state/app/types.ts +6 -4
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/config/layout.ts +2 -0
- package/src/data/config/store/index.test.ts +27 -6
- package/src/data/config/store/normalize.ts +3 -5
- package/src/data/plugin-state-store.ts +11 -0
- package/src/data/sqlite/cache.test.ts +10 -0
- package/src/i18n/es.ts +3 -4
- package/src/i18n/ja.ts +3 -4
- package/src/i18n/ko.ts +3 -4
- package/src/i18n/zh-cn.ts +3 -4
- package/src/i18n/zh-tw.ts +3 -4
- package/src/layout-marketplace/api.test.ts +54 -0
- package/src/layout-marketplace/api.ts +65 -0
- package/src/layout-marketplace/gallery-desktop.test.tsx +262 -0
- package/src/layout-marketplace/gallery-desktop.tsx +375 -0
- package/src/layout-marketplace/gallery-terminal.tsx +376 -0
- package/src/layout-marketplace/gallery.test.tsx +84 -0
- package/src/layout-marketplace/gallery.tsx +323 -0
- package/src/layout-marketplace/geometry.test.ts +47 -0
- package/src/layout-marketplace/geometry.ts +80 -0
- package/src/layout-marketplace/mini-workspace.test.tsx +122 -0
- package/src/layout-marketplace/mini-workspace.tsx +428 -0
- package/src/layout-marketplace/model.test.ts +118 -0
- package/src/layout-marketplace/model.ts +171 -0
- package/src/layout-marketplace/name-dialog.tsx +60 -0
- package/src/layout-marketplace/pane-imagery.ts +85 -0
- package/src/layout-marketplace/pane.tsx +20 -0
- package/src/layout-marketplace/payload.test.ts +246 -0
- package/src/layout-marketplace/payload.ts +624 -0
- package/src/layout-marketplace/use-marketplace.ts +63 -0
- package/src/market-data/hooks.tsx +0 -41
- package/src/plugins/broker-sync/broker-sync.test.ts +29 -0
- package/src/plugins/broker-sync/normalize.ts +9 -2
- package/src/plugins/builtin/ai/index.tsx +18 -2
- package/src/plugins/builtin/ai/screener/pane.test.tsx +47 -4
- package/src/plugins/builtin/ai/screener/pane.tsx +35 -12
- package/src/plugins/builtin/alerts/alert-engine.test.ts +16 -1
- package/src/plugins/builtin/alerts/alert-engine.ts +4 -0
- package/src/plugins/builtin/alerts/index.tsx +4 -0
- package/src/plugins/builtin/alerts/pane.tsx +2 -8
- package/src/plugins/builtin/analytics/index.tsx +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +3 -0
- package/src/plugins/builtin/cds/client.test.ts +83 -0
- package/src/plugins/builtin/cds/client.ts +67 -0
- package/src/plugins/builtin/cds/index.tsx +41 -0
- package/src/plugins/builtin/cds/model.test.ts +232 -0
- package/src/plugins/builtin/cds/model.ts +401 -0
- package/src/plugins/builtin/cds/pane.test.tsx +132 -0
- package/src/plugins/builtin/cds/pane.tsx +358 -0
- package/src/plugins/builtin/chart-composer/chart-spec.ts +31 -0
- package/src/plugins/builtin/chart-composer/index.tsx +88 -2
- package/src/plugins/builtin/chart-composer/pane-share.test.ts +71 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +81 -14
- package/src/plugins/builtin/chat/content/index.tsx +4 -1
- package/src/plugins/builtin/chat/controller/index.ts +8 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +13 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +14 -2
- package/src/plugins/builtin/chat/controller/utils.ts +4 -6
- package/src/plugins/builtin/chat/controller.test.ts +64 -14
- package/src/plugins/builtin/chat/message/desktop.test.tsx +88 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +3 -1
- package/src/plugins/builtin/chat/message/profile-popover.test.tsx +1 -29
- package/src/plugins/builtin/chat/profile-popover.ts +53 -22
- package/src/plugins/builtin/chat/sidebar.test.tsx +81 -0
- package/src/plugins/builtin/chat/sidebar.tsx +63 -0
- package/src/plugins/builtin/chat/test-harness.tsx +29 -1
- package/src/plugins/builtin/cloud/auth-commands.ts +0 -4
- package/src/plugins/builtin/cloud/auth-model.ts +4 -1
- package/src/plugins/builtin/cloud/plugin.tsx +11 -1
- package/src/plugins/builtin/cloud-tweets/pane-share.test.ts +56 -0
- package/src/plugins/builtin/cloud-tweets/pane.tsx +20 -5
- package/src/plugins/builtin/cloud-tweets/registration.ts +44 -5
- package/src/plugins/builtin/composite-plugins.ts +3 -1
- package/src/plugins/builtin/connections/index.ts +3 -0
- package/src/plugins/builtin/econ/calendar-model.ts +0 -5
- package/src/plugins/builtin/fear-greed/charts.tsx +12 -8
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +4 -1
- package/src/plugins/builtin/layout-manager/index.tsx +17 -4
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/feed-config.test.ts +15 -1
- package/src/plugins/builtin/news/wire/feed-config.ts +25 -5
- package/src/plugins/builtin/news/wire/index.tsx +2 -2
- package/src/plugins/builtin/options/index.tsx +1 -0
- package/src/plugins/builtin/options/view.test.tsx +25 -3
- package/src/plugins/builtin/options/view.tsx +1 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +2 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -4
- package/src/plugins/builtin/portfolio-list/header.tsx +13 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +8 -0
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +31 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +10 -7
- package/src/plugins/builtin/portfolio-list/position-metrics.ts +25 -6
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +21 -16
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +20 -6
- package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +33 -7
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +109 -12
- package/src/plugins/builtin/research/index.tsx +4 -0
- package/src/plugins/builtin/shared/public-pane.test.ts +12 -0
- package/src/plugins/builtin/shared/public-pane.ts +9 -0
- package/src/plugins/builtin/shared/ticker-source.ts +32 -0
- package/src/plugins/builtin/shared/ticker-surface.test.ts +27 -0
- package/src/plugins/builtin/shared/ticker-surface.ts +17 -0
- package/src/plugins/builtin/ticker-detail/index.tsx +10 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +2 -0
- package/src/plugins/builtin/world-indices/index.tsx +2 -0
- package/src/plugins/catalog-browser.test.ts +10 -0
- package/src/plugins/catalog-browser.ts +3 -1
- package/src/plugins/gridlock-notification.ts +2 -1
- package/src/plugins/pane-manager/gridlock.ts +87 -3
- package/src/plugins/pane-manager.test.ts +30 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +2 -2
- package/src/plugins/prediction-markets/controller/index.ts +5 -1
- package/src/plugins/prediction-markets/controller/keyboard.ts +10 -14
- package/src/plugins/prediction-markets/detail/overview.tsx +21 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +43 -34
- package/src/plugins/prediction-markets/detail/trades.tsx +34 -9
- package/src/plugins/prediction-markets/detail.test.tsx +29 -2
- package/src/plugins/prediction-markets/index.tsx +8 -4
- package/src/plugins/prediction-markets/navigation.ts +0 -1
- package/src/plugins/prediction-markets/pane.test.tsx +77 -0
- package/src/plugins/prediction-markets/pane.tsx +4 -0
- package/src/plugins/prediction-markets/plugin.test.tsx +36 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +9 -0
- package/src/plugins/registry/index.ts +2 -0
- package/src/plugins/ticker-navigation.test.ts +24 -0
- package/src/plugins/ticker-navigation.ts +40 -0
- package/src/remote/schema.ts +1 -1
- package/src/renderers/browser/cloud-transport.test.ts +36 -0
- package/src/renderers/browser/cloud-transport.ts +19 -0
- package/src/renderers/browser/config-host.ts +0 -4
- package/src/renderers/browser/deeplink-bridge.test.ts +41 -0
- package/src/renderers/browser/deeplink-bridge.ts +22 -0
- package/src/renderers/browser/main.tsx +3 -0
- package/src/renderers/cloudflare/worker.test.ts +8 -5
- package/src/renderers/cloudflare/worker.ts +6 -5
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/plugin-state.ts +7 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/host/native.ts +0 -5
- package/src/renderers/electrobun/view/remote/persistence.ts +5 -0
- package/src/renderers/electrobun/view/styles.css +107 -7
- package/src/renderers/electrobun/view/toast-host.test.tsx +78 -0
- package/src/renderers/electrobun/view/toast-host.tsx +69 -43
- package/src/renderers/electrobun/view/ui-host.tsx +4 -1
- package/src/renderers/share/layout-view.test.tsx +58 -0
- package/src/renderers/share/layout-view.tsx +133 -0
- package/src/renderers/share/main.tsx +54 -6
- package/src/renderers/share/styles.css +35 -0
- package/src/renderers/share/view.test.tsx +78 -0
- package/src/renderers/share/view.tsx +71 -0
- package/src/shares/api.test.ts +52 -3
- package/src/shares/api.ts +39 -15
- package/src/shares/location.test.ts +19 -0
- package/src/shares/location.ts +11 -0
- package/src/shares/pane.test.ts +110 -0
- package/src/shares/pane.ts +54 -0
- package/src/shares/payload.ts +74 -1
- package/src/state/app/bootstrap.ts +6 -7
- package/src/state/app/context/index.test.ts +41 -1
- package/src/state/app/context/index.test.tsx +34 -16
- package/src/state/selectors-ui.ts +0 -8
- package/src/sync/controller.test.ts +57 -1
- package/src/sync/controller.ts +22 -11
- package/src/sync/react.ts +13 -0
- package/src/theme/font-scale.ts +0 -11
- package/src/types/config.ts +35 -5
- package/src/types/desktop-menu.ts +1 -0
- package/src/types/plugin.ts +36 -0
- package/src/ui/context-menu.tsx +1 -1
- package/src/ui/toast.tsx +2 -0
- package/src/utils/back-navigation.ts +0 -4
|
@@ -32,6 +32,8 @@ export function menuForPane(
|
|
|
32
32
|
openPaneSettings: (paneId: string) => void,
|
|
33
33
|
desktopWindowBridge?: DesktopWindowBridge,
|
|
34
34
|
copyPaneScreenshot?: (paneId: string) => void | Promise<void>,
|
|
35
|
+
sharePane?: () => void | Promise<void>,
|
|
36
|
+
linkItems: ContextMenuItem[] = [],
|
|
35
37
|
): ContextMenuItem[] {
|
|
36
38
|
const baseActions: ContextMenuItem[] = [];
|
|
37
39
|
if (pluginRegistry.hasPaneSettings(pane.instance.instanceId)) {
|
|
@@ -42,6 +44,14 @@ export function menuForPane(
|
|
|
42
44
|
onSelect: () => openPaneSettings(pane.instance.instanceId),
|
|
43
45
|
});
|
|
44
46
|
}
|
|
47
|
+
if (sharePane) {
|
|
48
|
+
baseActions.push({
|
|
49
|
+
id: "share-pane",
|
|
50
|
+
label: "Share Pane",
|
|
51
|
+
accelerator: PANE_MANAGEMENT_ACCELERATORS.share,
|
|
52
|
+
onSelect: sharePane,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
45
55
|
if (copyPaneScreenshot) {
|
|
46
56
|
baseActions.push({
|
|
47
57
|
id: "copy-screenshot",
|
|
@@ -91,6 +101,10 @@ export function menuForPane(
|
|
|
91
101
|
onSelect: () => persistLayout(removePane(layout, pane.instance.instanceId)),
|
|
92
102
|
});
|
|
93
103
|
|
|
104
|
+
if (linkItems.length > 0) {
|
|
105
|
+
baseActions.push(contextMenuDivider("pane:link-divider"), ...linkItems);
|
|
106
|
+
}
|
|
107
|
+
|
|
94
108
|
baseActions.push(
|
|
95
109
|
contextMenuDivider("pane:layout-divider"),
|
|
96
110
|
{
|
|
@@ -22,6 +22,7 @@ interface UseShellNativePointerRuntimeOptions {
|
|
|
22
22
|
setMenuState: Dispatch<SetStateAction<ActionMenuState | null>>;
|
|
23
23
|
transientFocusActive: boolean;
|
|
24
24
|
windowMode: WindowEditState | null;
|
|
25
|
+
commandBarOpen?: boolean;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export function useShellNativePointerRuntime({
|
|
@@ -38,6 +39,7 @@ export function useShellNativePointerRuntime({
|
|
|
38
39
|
setMenuState,
|
|
39
40
|
transientFocusActive,
|
|
40
41
|
windowMode,
|
|
42
|
+
commandBarOpen = false,
|
|
41
43
|
}: UseShellNativePointerRuntimeOptions) {
|
|
42
44
|
const {
|
|
43
45
|
dragRef,
|
|
@@ -59,6 +61,11 @@ export function useShellNativePointerRuntime({
|
|
|
59
61
|
}, [focusPane, menuState, setHoveredMenuItemId, setMenuState]);
|
|
60
62
|
|
|
61
63
|
const handleNativePaneMouseDown = useCallback((paneId: string, event: ShellMouseEvent) => {
|
|
64
|
+
if (commandBarOpen) {
|
|
65
|
+
event.stopPropagation();
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
62
69
|
if (windowMode) {
|
|
63
70
|
selectWindowModePane(paneId);
|
|
64
71
|
event.stopPropagation();
|
|
@@ -69,10 +76,11 @@ export function useShellNativePointerRuntime({
|
|
|
69
76
|
if (event.isDefaultPrevented?.()) return;
|
|
70
77
|
focusNativePane(paneId);
|
|
71
78
|
});
|
|
72
|
-
}, [focusNativePane, selectWindowModePane, windowMode]);
|
|
79
|
+
}, [commandBarOpen, focusNativePane, selectWindowModePane, windowMode]);
|
|
73
80
|
|
|
74
81
|
const startNativeFloatingDrag = useCallback((paneId: string, rect: FloatingRect, event: ShellMouseEvent) => {
|
|
75
82
|
if (!nativePaneChrome) return;
|
|
83
|
+
if (commandBarOpen) return;
|
|
76
84
|
if (windowMode) return;
|
|
77
85
|
if (transientFocusActive) return;
|
|
78
86
|
if (event.button === 2) return;
|
|
@@ -89,10 +97,11 @@ export function useShellNativePointerRuntime({
|
|
|
89
97
|
};
|
|
90
98
|
updateDragFloatingRect({ paneId, rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } });
|
|
91
99
|
event.preventDefault();
|
|
92
|
-
}, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, updateDragFloatingRect, windowMode]);
|
|
100
|
+
}, [commandBarOpen, dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, updateDragFloatingRect, windowMode]);
|
|
93
101
|
|
|
94
102
|
const startNativeDockedDrag = useCallback((paneId: string, rect: LayoutBounds, event: ShellMouseEvent) => {
|
|
95
103
|
if (!nativePaneChrome) return;
|
|
104
|
+
if (commandBarOpen) return;
|
|
96
105
|
if (windowMode) return;
|
|
97
106
|
if (transientFocusActive) return;
|
|
98
107
|
if (event.button === 2) return;
|
|
@@ -108,10 +117,11 @@ export function useShellNativePointerRuntime({
|
|
|
108
117
|
origRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
109
118
|
};
|
|
110
119
|
event.preventDefault();
|
|
111
|
-
}, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, windowMode]);
|
|
120
|
+
}, [commandBarOpen, dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, windowMode]);
|
|
112
121
|
|
|
113
122
|
const startNativeFloatResize = useCallback((paneId: string, rect: FloatingRect, event: ShellMouseEvent) => {
|
|
114
123
|
if (!nativePaneChrome) return;
|
|
124
|
+
if (commandBarOpen) return;
|
|
115
125
|
if (transientFocusActive) return;
|
|
116
126
|
|
|
117
127
|
const pointer = getShellPointer(event);
|
|
@@ -130,10 +140,11 @@ export function useShellNativePointerRuntime({
|
|
|
130
140
|
updateDragFloatingRect({ paneId, rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } });
|
|
131
141
|
event.stopPropagation();
|
|
132
142
|
event.preventDefault();
|
|
133
|
-
}, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, selectWindowModePane, transientFocusActive, updateDragFloatingRect, windowMode]);
|
|
143
|
+
}, [commandBarOpen, dragRef, focusNativePane, getShellPointer, nativePaneChrome, selectWindowModePane, transientFocusActive, updateDragFloatingRect, windowMode]);
|
|
134
144
|
|
|
135
145
|
const startNativeDividerDrag = useCallback((divider: DockDividerLayout, event: ShellMouseEvent) => {
|
|
136
146
|
if (!nativePaneChrome) return;
|
|
147
|
+
if (commandBarOpen) return;
|
|
137
148
|
if (transientFocusActive) return;
|
|
138
149
|
|
|
139
150
|
const pointer = getShellPointer(event);
|
|
@@ -157,27 +168,30 @@ export function useShellNativePointerRuntime({
|
|
|
157
168
|
});
|
|
158
169
|
event.stopPropagation();
|
|
159
170
|
event.preventDefault();
|
|
160
|
-
}, [dragRef, getShellPointer, menuState, nativePaneChrome, setHoveredMenuItemId, setMenuState, transientFocusActive, updateDividerPreview]);
|
|
171
|
+
}, [commandBarOpen, dragRef, getShellPointer, menuState, nativePaneChrome, setHoveredMenuItemId, setMenuState, transientFocusActive, updateDividerPreview]);
|
|
161
172
|
|
|
162
173
|
const handlePaneAction = useCallback((paneId: string, rect: LayoutBounds, event: ShellMouseEvent) => {
|
|
174
|
+
if (commandBarOpen) return;
|
|
163
175
|
if (windowMode) return;
|
|
164
176
|
if (event.button === 2) return;
|
|
165
177
|
event.stopPropagation();
|
|
166
178
|
event.preventDefault();
|
|
167
179
|
openPaneMenu(paneId, rect, event);
|
|
168
|
-
}, [openPaneMenu, windowMode]);
|
|
180
|
+
}, [commandBarOpen, openPaneMenu, windowMode]);
|
|
169
181
|
|
|
170
182
|
const handleNativePaneContextMenu = useCallback((paneId: string, rect: LayoutBounds, event: ShellMouseEvent) => {
|
|
183
|
+
if (commandBarOpen) return;
|
|
171
184
|
if (windowMode) return;
|
|
172
185
|
openPaneMenu(paneId, rect, event);
|
|
173
|
-
}, [openPaneMenu, windowMode]);
|
|
186
|
+
}, [commandBarOpen, openPaneMenu, windowMode]);
|
|
174
187
|
|
|
175
188
|
const handleFloatingCloseMouseDown = useCallback((paneId: string, event: ShellMouseEvent) => {
|
|
189
|
+
if (commandBarOpen) return;
|
|
176
190
|
if (windowMode) return;
|
|
177
191
|
event.stopPropagation();
|
|
178
192
|
event.preventDefault();
|
|
179
193
|
handleFloatingClose(paneId);
|
|
180
|
-
}, [handleFloatingClose, windowMode]);
|
|
194
|
+
}, [commandBarOpen, handleFloatingClose, windowMode]);
|
|
181
195
|
|
|
182
196
|
return {
|
|
183
197
|
handleFloatingCloseMouseDown,
|
|
@@ -54,10 +54,6 @@ export function useShellPaneActions({
|
|
|
54
54
|
visibleLayout,
|
|
55
55
|
width,
|
|
56
56
|
}: UseShellPaneActionsOptions) {
|
|
57
|
-
const openLayoutMenu = useCallback(() => {
|
|
58
|
-
pluginRegistry.openCommandBar("LAY ");
|
|
59
|
-
}, [pluginRegistry]);
|
|
60
|
-
|
|
61
57
|
const openPaneSettings = useCallback((paneId: string) => {
|
|
62
58
|
pluginRegistry.openPaneSettingsFn(paneId);
|
|
63
59
|
closePaneMenu();
|
|
@@ -143,7 +139,6 @@ export function useShellPaneActions({
|
|
|
143
139
|
gridlockVisiblePanes,
|
|
144
140
|
handleFloatingClose,
|
|
145
141
|
openFocusedPaneSettings,
|
|
146
|
-
openLayoutMenu,
|
|
147
142
|
openPaneSettings,
|
|
148
143
|
popOutFocusedPane,
|
|
149
144
|
toggleFocusedPaneFloating,
|
|
@@ -21,9 +21,10 @@ interface ShellPaneManagementShortcutOptions {
|
|
|
21
21
|
hasActiveDrag(): boolean;
|
|
22
22
|
inputCaptured: boolean;
|
|
23
23
|
openFocusedPaneSettings(): boolean;
|
|
24
|
-
|
|
24
|
+
openLayoutGallery(): void;
|
|
25
25
|
overlayOpen: boolean;
|
|
26
26
|
popOutFocusedPane(): boolean;
|
|
27
|
+
shareFocusedPane(): boolean;
|
|
27
28
|
startWindowMode(paneId?: string, mode?: WindowEditMode): void;
|
|
28
29
|
toggleFocusedPaneFullscreen(): boolean;
|
|
29
30
|
toggleFocusedPaneFloating(): boolean;
|
|
@@ -39,9 +40,10 @@ export function useShellPaneManagementShortcuts({
|
|
|
39
40
|
hasActiveDrag,
|
|
40
41
|
inputCaptured,
|
|
41
42
|
openFocusedPaneSettings,
|
|
42
|
-
|
|
43
|
+
openLayoutGallery,
|
|
43
44
|
overlayOpen,
|
|
44
45
|
popOutFocusedPane,
|
|
46
|
+
shareFocusedPane,
|
|
45
47
|
startWindowMode,
|
|
46
48
|
toggleFocusedPaneFullscreen,
|
|
47
49
|
toggleFocusedPaneFloating,
|
|
@@ -119,8 +121,11 @@ export function useShellPaneManagementShortcuts({
|
|
|
119
121
|
case "copy-screenshot":
|
|
120
122
|
handled = copyFocusedPaneScreenshot();
|
|
121
123
|
break;
|
|
122
|
-
case "
|
|
123
|
-
|
|
124
|
+
case "share":
|
|
125
|
+
handled = shareFocusedPane();
|
|
126
|
+
break;
|
|
127
|
+
case "layout-gallery":
|
|
128
|
+
openLayoutGallery();
|
|
124
129
|
handled = true;
|
|
125
130
|
break;
|
|
126
131
|
case "gridlock-all":
|
|
@@ -6,9 +6,10 @@ export const PANE_MANAGEMENT_ACCELERATORS = {
|
|
|
6
6
|
toggleFloating: "CmdOrCtrl+Shift+D",
|
|
7
7
|
popOut: "CmdOrCtrl+Shift+O",
|
|
8
8
|
copyScreenshot: "CmdOrCtrl+Shift+C",
|
|
9
|
+
share: "CmdOrCtrl+Shift+S",
|
|
9
10
|
close: "CmdOrCtrl+W",
|
|
10
11
|
closeAllFloating: "CmdOrCtrl+Alt+W",
|
|
11
|
-
|
|
12
|
+
layoutGallery: "CmdOrCtrl+Shift+L",
|
|
12
13
|
gridlockAll: "CmdOrCtrl+Shift+G",
|
|
13
14
|
windowMode: "CmdOrCtrl+Shift+M",
|
|
14
15
|
windowResizeMode: "CmdOrCtrl+Shift+R",
|
|
@@ -20,9 +21,10 @@ export type PaneManagementShortcut =
|
|
|
20
21
|
| "toggle-floating"
|
|
21
22
|
| "pop-out"
|
|
22
23
|
| "copy-screenshot"
|
|
24
|
+
| "share"
|
|
23
25
|
| "close"
|
|
24
26
|
| "close-all-floating"
|
|
25
|
-
| "layout-
|
|
27
|
+
| "layout-gallery"
|
|
26
28
|
| "gridlock-all"
|
|
27
29
|
| "window-mode"
|
|
28
30
|
| "window-resize-mode";
|
|
@@ -39,10 +41,11 @@ export function resolvePaneManagementShortcut(
|
|
|
39
41
|
if (!shifted && name === ",") return "settings";
|
|
40
42
|
if (!shifted || event.alt) return null;
|
|
41
43
|
if (name === "c") return "copy-screenshot";
|
|
44
|
+
if (name === "s") return "share";
|
|
42
45
|
if (name === "d") return "toggle-floating";
|
|
43
46
|
if (name === "f") return "toggle-fullscreen";
|
|
44
47
|
if (name === "o") return "pop-out";
|
|
45
|
-
if (name === "l") return "layout-
|
|
48
|
+
if (name === "l") return "layout-gallery";
|
|
46
49
|
if (name === "g") return "gridlock-all";
|
|
47
50
|
if (name === "m") return "window-mode";
|
|
48
51
|
if (name === "r") return "window-resize-mode";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createInitialState } from "../../../state/app/context";
|
|
3
|
+
import {
|
|
4
|
+
createDefaultConfig,
|
|
5
|
+
createPaneInstance,
|
|
6
|
+
findPaneInstance,
|
|
7
|
+
TICKER_RESEARCH_PANE_ID,
|
|
8
|
+
type LayoutConfig,
|
|
9
|
+
} from "../../../types/config";
|
|
10
|
+
import type { PaneDef } from "../../../types/plugin";
|
|
11
|
+
import { getPaneDisplayTitle } from "../pane/title";
|
|
12
|
+
import { tickerLinkMenuItems } from "./ticker-link-menu";
|
|
13
|
+
|
|
14
|
+
const panes = new Map<string, PaneDef>([
|
|
15
|
+
["portfolio-list", {
|
|
16
|
+
id: "portfolio-list",
|
|
17
|
+
name: "Portfolio",
|
|
18
|
+
component: () => null,
|
|
19
|
+
defaultPosition: "left",
|
|
20
|
+
tickerSource: true,
|
|
21
|
+
}],
|
|
22
|
+
[TICKER_RESEARCH_PANE_ID, {
|
|
23
|
+
id: TICKER_RESEARCH_PANE_ID,
|
|
24
|
+
name: "Ticker Research",
|
|
25
|
+
component: () => null,
|
|
26
|
+
defaultPosition: "right",
|
|
27
|
+
}],
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
describe("tickerLinkMenuItems", () => {
|
|
31
|
+
test("pins the current ticker when unlinking and can link back to the source", () => {
|
|
32
|
+
const state = createInitialState(createDefaultConfig("/tmp/gloomberb-link-menu-test"));
|
|
33
|
+
state.paneState["portfolio-list:main"] = { collectionId: "main", cursorSymbol: "AAPL" };
|
|
34
|
+
const target = findPaneInstance(state.config.layout, "ticker-detail:main")!;
|
|
35
|
+
let layout: LayoutConfig = state.config.layout;
|
|
36
|
+
|
|
37
|
+
expect(getPaneDisplayTitle(state, target, panes.get(TICKER_RESEARCH_PANE_ID)!, panes)).toBe(
|
|
38
|
+
"AAPL ⧉ Linked to Main Portfolio",
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
tickerLinkMenuItems({
|
|
42
|
+
instance: target,
|
|
43
|
+
layout,
|
|
44
|
+
panes,
|
|
45
|
+
state,
|
|
46
|
+
persistLayout: (nextLayout) => { layout = nextLayout; },
|
|
47
|
+
}).find((item) => item.id === "unlink:portfolio-list:main")?.onSelect?.();
|
|
48
|
+
|
|
49
|
+
const pinned = findPaneInstance(layout, target.instanceId)!;
|
|
50
|
+
expect(pinned.binding).toEqual({ kind: "fixed", symbol: "AAPL" });
|
|
51
|
+
|
|
52
|
+
const previousFollower = createPaneInstance(TICKER_RESEARCH_PANE_ID, {
|
|
53
|
+
instanceId: "ticker-detail:previous",
|
|
54
|
+
binding: { kind: "follow", sourceInstanceId: "portfolio-list:main" },
|
|
55
|
+
});
|
|
56
|
+
layout = {
|
|
57
|
+
...layout,
|
|
58
|
+
instances: [...layout.instances, previousFollower],
|
|
59
|
+
floating: [
|
|
60
|
+
...layout.floating,
|
|
61
|
+
{ instanceId: previousFollower.instanceId, x: 1, y: 1, width: 40, height: 12 },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
const pinnedState = { ...state, config: { ...state.config, layout } };
|
|
65
|
+
tickerLinkMenuItems({
|
|
66
|
+
instance: pinned,
|
|
67
|
+
layout,
|
|
68
|
+
panes,
|
|
69
|
+
state: pinnedState,
|
|
70
|
+
persistLayout: (nextLayout) => { layout = nextLayout; },
|
|
71
|
+
}).find((item) => item.id === "link:portfolio-list:main")?.onSelect?.();
|
|
72
|
+
|
|
73
|
+
expect(findPaneInstance(layout, target.instanceId)?.binding).toEqual({
|
|
74
|
+
kind: "follow",
|
|
75
|
+
sourceInstanceId: "portfolio-list:main",
|
|
76
|
+
});
|
|
77
|
+
expect(findPaneInstance(layout, previousFollower.instanceId)?.binding).toEqual({
|
|
78
|
+
kind: "fixed",
|
|
79
|
+
symbol: "AAPL",
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { updatePaneInstance } from "../../../pane-settings";
|
|
2
|
+
import { listVisibleTickerSourcePanes } from "../../../plugins/ticker-navigation";
|
|
3
|
+
import { resolveTickerForPane, type AppState } from "../../../state/app/context";
|
|
4
|
+
import {
|
|
5
|
+
TICKER_RESEARCH_PANE_ID,
|
|
6
|
+
type LayoutConfig,
|
|
7
|
+
type PaneInstanceConfig,
|
|
8
|
+
} from "../../../types/config";
|
|
9
|
+
import type { ContextMenuItem } from "../../../types/context-menu";
|
|
10
|
+
import type { PaneDef } from "../../../types/plugin";
|
|
11
|
+
import { getPaneDisplayTitle } from "../pane/title";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Flat link controls for a Ticker Research pane: one entry per visible ticker source, plus an
|
|
15
|
+
* unlink entry that pins the pane on the symbol it currently shows.
|
|
16
|
+
*/
|
|
17
|
+
export function tickerLinkMenuItems({
|
|
18
|
+
instance,
|
|
19
|
+
layout,
|
|
20
|
+
panes,
|
|
21
|
+
state,
|
|
22
|
+
persistLayout,
|
|
23
|
+
}: {
|
|
24
|
+
instance: PaneInstanceConfig;
|
|
25
|
+
layout: LayoutConfig;
|
|
26
|
+
panes: ReadonlyMap<string, PaneDef>;
|
|
27
|
+
state: Pick<AppState, "config" | "paneState">;
|
|
28
|
+
persistLayout: (nextLayout: LayoutConfig) => void;
|
|
29
|
+
}): ContextMenuItem[] {
|
|
30
|
+
if (instance.paneId !== TICKER_RESEARCH_PANE_ID) return [];
|
|
31
|
+
|
|
32
|
+
const sourceInstanceId = instance.binding?.kind === "follow" ? instance.binding.sourceInstanceId : null;
|
|
33
|
+
const rebind = (binding: PaneInstanceConfig["binding"]) => {
|
|
34
|
+
persistLayout(updatePaneInstance(layout, instance.instanceId, (current) => ({ ...current, binding })));
|
|
35
|
+
};
|
|
36
|
+
const linkToSource = (nextSourceId: string) => {
|
|
37
|
+
persistLayout({
|
|
38
|
+
...layout,
|
|
39
|
+
instances: layout.instances.map((current) => {
|
|
40
|
+
if (current.instanceId === instance.instanceId) {
|
|
41
|
+
return { ...current, binding: { kind: "follow" as const, sourceInstanceId: nextSourceId } };
|
|
42
|
+
}
|
|
43
|
+
if (
|
|
44
|
+
current.paneId !== TICKER_RESEARCH_PANE_ID
|
|
45
|
+
|| current.binding?.kind !== "follow"
|
|
46
|
+
|| current.binding.sourceInstanceId !== nextSourceId
|
|
47
|
+
) return current;
|
|
48
|
+
|
|
49
|
+
const symbol = resolveTickerForPane(state as AppState, current.instanceId);
|
|
50
|
+
return { ...current, binding: symbol ? { kind: "fixed" as const, symbol } : { kind: "none" as const } };
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return listVisibleTickerSourcePanes(layout, panes).flatMap((source): ContextMenuItem[] => {
|
|
56
|
+
const paneDef = panes.get(source.paneId);
|
|
57
|
+
if (!paneDef) return [];
|
|
58
|
+
const title = getPaneDisplayTitle(state, source, paneDef, panes);
|
|
59
|
+
|
|
60
|
+
if (source.instanceId !== sourceInstanceId) {
|
|
61
|
+
return [{
|
|
62
|
+
id: `link:${source.instanceId}`,
|
|
63
|
+
label: `Link to ${title}`,
|
|
64
|
+
onSelect: () => linkToSource(source.instanceId),
|
|
65
|
+
}];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Unlinking keeps the pane alive, so it only works once a symbol has resolved.
|
|
69
|
+
const symbol = resolveTickerForPane(state as AppState, instance.instanceId);
|
|
70
|
+
return symbol
|
|
71
|
+
? [{
|
|
72
|
+
id: `unlink:${source.instanceId}`,
|
|
73
|
+
label: `Unlink from ${title}`,
|
|
74
|
+
onSelect: () => rebind({ kind: "fixed", symbol }),
|
|
75
|
+
}]
|
|
76
|
+
: [];
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import { testRender } from "../../renderers/opentui/test-utils";
|
|
3
3
|
import { AppContext, createInitialState } from "../../state/app/context";
|
|
4
|
-
import { cloneLayout, createDefaultConfig,
|
|
4
|
+
import { cloneLayout, createDefaultConfig, createPaneInstance, type LayoutConfig } from "../../types/config";
|
|
5
5
|
import type { AppNotificationRequest } from "../../types/plugin";
|
|
6
6
|
import { StatusBar } from "./status-bar";
|
|
7
|
+
import { getDockedPaneIds } from "../../plugins/pane-manager";
|
|
7
8
|
import { setSharedRegistryForTests } from "../../plugins/registry";
|
|
8
9
|
import { act, useEffect, useState } from "react";
|
|
9
10
|
import { TransientLayoutProvider, useTransientLayout } from "./transient-layout";
|
|
@@ -185,34 +186,35 @@ describe("StatusBar", () => {
|
|
|
185
186
|
expect(actions).toContainEqual({ type: "REORDER_LAYOUT", fromIndex: 0, toIndex: 2 });
|
|
186
187
|
});
|
|
187
188
|
|
|
188
|
-
test("
|
|
189
|
-
const config = createDefaultConfig("/tmp/gloomberb-test");
|
|
190
|
-
const floatingLayout =
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
189
|
+
test("offers to tidy three floating windows and tiles them on click", async () => {
|
|
190
|
+
const config = createDefaultConfig("/tmp/gloomberb-tidy-test");
|
|
191
|
+
const floatingLayout: LayoutConfig = {
|
|
192
|
+
dockRoot: null,
|
|
193
|
+
instances: Array.from({ length: 3 }, (_, index) => createPaneInstance("chat", { instanceId: `chat-${index}` })),
|
|
194
|
+
floating: Array.from({ length: 3 }, (_, index) => ({
|
|
195
|
+
instanceId: `chat-${index}`,
|
|
196
|
+
x: index * 40,
|
|
197
|
+
y: 0,
|
|
198
|
+
width: 40,
|
|
199
|
+
height: 20,
|
|
200
|
+
zIndex: 50 + index,
|
|
201
|
+
})),
|
|
202
|
+
detached: [],
|
|
203
|
+
};
|
|
194
204
|
const state = {
|
|
195
205
|
...createInitialState({
|
|
196
206
|
...config,
|
|
197
207
|
layout: floatingLayout,
|
|
198
|
-
layouts: [
|
|
199
|
-
{ name: "Default", layout: cloneLayout(floatingLayout) },
|
|
200
|
-
{ name: "Research", layout: cloneLayout(floatingLayout) },
|
|
201
|
-
],
|
|
208
|
+
layouts: [{ name: "Default", layout: cloneLayout(floatingLayout) }],
|
|
202
209
|
}),
|
|
203
210
|
statusBarVisible: true,
|
|
204
|
-
gridlockTipVisible: true,
|
|
205
211
|
};
|
|
206
|
-
|
|
207
212
|
const actions: Array<{ type: string }> = [];
|
|
208
|
-
let updatedLayout
|
|
213
|
+
let updatedLayout: LayoutConfig | null = null;
|
|
209
214
|
const notifications: AppNotificationRequest[] = [];
|
|
210
215
|
|
|
211
216
|
setSharedRegistryForTests({
|
|
212
|
-
panes: new Map([
|
|
213
|
-
["portfolio-list", {}],
|
|
214
|
-
[TICKER_RESEARCH_PANE_ID, {}],
|
|
215
|
-
]),
|
|
217
|
+
panes: new Map([["chat", { name: "Chat" }]]),
|
|
216
218
|
getLayoutFn: () => state.config.layout,
|
|
217
219
|
getTermSizeFn: () => ({ width: 120, height: 40 }),
|
|
218
220
|
updateLayoutFn: (layout: LayoutConfig) => { updatedLayout = layout; },
|
|
@@ -228,76 +230,75 @@ describe("StatusBar", () => {
|
|
|
228
230
|
);
|
|
229
231
|
|
|
230
232
|
await testSetup.renderOnce();
|
|
231
|
-
|
|
232
233
|
const frame = testSetup.captureCharFrame();
|
|
233
|
-
|
|
234
|
-
expect(frame).toContain("Gridlock All");
|
|
235
|
-
|
|
236
|
-
const buttonX = frame.split("\n")[0]?.indexOf("Gridlock All") ?? -1;
|
|
234
|
+
const buttonX = frame.split("\n")[0]?.indexOf("Tidy Windows") ?? -1;
|
|
237
235
|
expect(buttonX).toBeGreaterThanOrEqual(0);
|
|
238
236
|
|
|
239
237
|
await testSetup.mockMouse.click(buttonX + 1, 0);
|
|
240
238
|
await testSetup.renderOnce();
|
|
241
239
|
|
|
242
240
|
expect(updatedLayout?.floating).toHaveLength(0);
|
|
243
|
-
expect(notifications).toHaveLength(1);
|
|
244
241
|
expect(notifications[0]).toMatchObject({
|
|
245
|
-
body: "
|
|
242
|
+
body: "Windows tidied",
|
|
246
243
|
type: "success",
|
|
247
244
|
action: { label: "Revert" },
|
|
248
245
|
});
|
|
249
|
-
expect(actions).toContainEqual({ type: "DISMISS_GRIDLOCK_TIP" });
|
|
250
|
-
|
|
251
246
|
notifications[0]!.action!.onClick();
|
|
252
247
|
expect(actions).toContainEqual({ type: "UNDO_LAYOUT" });
|
|
253
248
|
});
|
|
254
249
|
|
|
255
|
-
test("
|
|
256
|
-
const config = createDefaultConfig("/tmp/gloomberb-test");
|
|
250
|
+
test("tidies covered windows instead of leaving them floating", async () => {
|
|
251
|
+
const config = createDefaultConfig("/tmp/gloomberb-covered-test");
|
|
252
|
+
const floatingLayout: LayoutConfig = {
|
|
253
|
+
dockRoot: null,
|
|
254
|
+
instances: Array.from({ length: 3 }, (_, index) => createPaneInstance("chat", { instanceId: `chat-${index}` })),
|
|
255
|
+
floating: Array.from({ length: 3 }, (_, index) => ({
|
|
256
|
+
instanceId: `chat-${index}`,
|
|
257
|
+
x: 10,
|
|
258
|
+
y: 4,
|
|
259
|
+
width: 50,
|
|
260
|
+
height: 20,
|
|
261
|
+
zIndex: 50 + index,
|
|
262
|
+
})),
|
|
263
|
+
detached: [],
|
|
264
|
+
};
|
|
257
265
|
const state = {
|
|
258
|
-
...createInitialState(
|
|
266
|
+
...createInitialState({
|
|
267
|
+
...config,
|
|
268
|
+
layout: floatingLayout,
|
|
269
|
+
layouts: [{ name: "Default", layout: cloneLayout(floatingLayout) }],
|
|
270
|
+
}),
|
|
259
271
|
statusBarVisible: true,
|
|
260
|
-
gridlockTipVisible: true,
|
|
261
|
-
gridlockTipSequence: 1,
|
|
262
272
|
};
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
const originalSetTimeout = globalThis.setTimeout;
|
|
266
|
-
const originalClearTimeout = globalThis.clearTimeout;
|
|
267
|
-
|
|
268
|
-
globalThis.setTimeout = ((callback: Parameters<typeof setTimeout>[0], delay?: number) => {
|
|
269
|
-
timers.push({ callback: typeof callback === "function" ? callback : null, delay });
|
|
270
|
-
return 1 as unknown as ReturnType<typeof setTimeout>;
|
|
271
|
-
}) as typeof setTimeout;
|
|
272
|
-
globalThis.clearTimeout = (() => {}) as typeof clearTimeout;
|
|
273
|
+
let updatedLayout: LayoutConfig | null = null;
|
|
274
|
+
const notifications: AppNotificationRequest[] = [];
|
|
273
275
|
|
|
274
276
|
setSharedRegistryForTests({
|
|
275
|
-
panes: new Map(),
|
|
277
|
+
panes: new Map([["chat", { name: "Chat" }]]),
|
|
276
278
|
getLayoutFn: () => state.config.layout,
|
|
277
279
|
getTermSizeFn: () => ({ width: 120, height: 40 }),
|
|
278
|
-
updateLayoutFn: () => {},
|
|
279
|
-
notify: () => {},
|
|
280
|
+
updateLayoutFn: (layout: LayoutConfig) => { updatedLayout = layout; },
|
|
281
|
+
notify: (notification: AppNotificationRequest) => { notifications.push(notification); },
|
|
280
282
|
Slot: () => null,
|
|
281
283
|
} as any);
|
|
282
284
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
);
|
|
285
|
+
testSetup = await testRender(
|
|
286
|
+
<AppContext value={{ state, dispatch: () => {} }}>
|
|
287
|
+
<StatusBar />
|
|
288
|
+
</AppContext>,
|
|
289
|
+
{ width: 120, height: 1 },
|
|
290
|
+
);
|
|
290
291
|
|
|
291
|
-
|
|
292
|
+
await testSetup.renderOnce();
|
|
293
|
+
const frame = testSetup.captureCharFrame();
|
|
294
|
+
const tidyX = frame.split("\n")[0]?.indexOf("Tidy Windows") ?? -1;
|
|
295
|
+
expect(tidyX).toBeGreaterThanOrEqual(0);
|
|
292
296
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
gridlockTimer?.callback?.();
|
|
297
|
+
await testSetup.mockMouse.click(tidyX + 1, 0);
|
|
298
|
+
await testSetup.renderOnce();
|
|
296
299
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
globalThis.clearTimeout = originalClearTimeout;
|
|
301
|
-
}
|
|
300
|
+
expect(updatedLayout?.floating).toHaveLength(0);
|
|
301
|
+
expect(getDockedPaneIds(updatedLayout!)).toHaveLength(3);
|
|
302
|
+
expect(notifications[0]?.body).toBe("Windows tidied");
|
|
302
303
|
});
|
|
303
304
|
});
|