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
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Box, Span, Text, useRendererHost, useUiCapabilities } from "../../ui";
|
|
1
|
+
import { Box, Span, Text, useContextMenu, useRendererHost, useUiCapabilities } from "../../ui";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { t } from "../../i18n";
|
|
4
4
|
import { useShortcut, useViewport } from "../../react/input";
|
|
5
|
-
import { useAppDispatch, useAppSelector } from "../../state/app/context";
|
|
5
|
+
import { resolveTickerForPane, useAppDispatch, useAppSelector } from "../../state/app/context";
|
|
6
6
|
import type { DesktopWindowBridge } from "../../types/desktop-window";
|
|
7
7
|
import { findPaneInstance } from "../../types/config";
|
|
8
8
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
@@ -20,6 +20,13 @@ import {
|
|
|
20
20
|
recordDoubleEscapeClose,
|
|
21
21
|
resetDoubleEscapeClose,
|
|
22
22
|
} from "../../utils/double-escape-close";
|
|
23
|
+
import { createShare, openLiveShareUrl } from "../../shares/api";
|
|
24
|
+
import { buildPaneSharePayload } from "../../shares/pane";
|
|
25
|
+
import type { ContextMenuItem } from "../../types/context-menu";
|
|
26
|
+
import {
|
|
27
|
+
PANE_MANAGEMENT_ACCELERATORS,
|
|
28
|
+
resolvePaneManagementShortcut,
|
|
29
|
+
} from "./shell/shortcuts";
|
|
23
30
|
|
|
24
31
|
interface DetachedPaneShellProps {
|
|
25
32
|
pluginRegistry: PluginRegistry;
|
|
@@ -35,6 +42,7 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
35
42
|
const colors = useThemeColors();
|
|
36
43
|
const dispatch = useAppDispatch();
|
|
37
44
|
const rendererHost = useRendererHost();
|
|
45
|
+
const { showContextMenu } = useContextMenu();
|
|
38
46
|
const config = useAppSelector((state) => state.config);
|
|
39
47
|
const paneState = useAppSelector((state) => state.paneState);
|
|
40
48
|
const inputCaptured = useAppSelector((state) => state.inputCaptured);
|
|
@@ -46,6 +54,7 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
46
54
|
const {
|
|
47
55
|
cellHeightPx = 18,
|
|
48
56
|
nativePaneChrome,
|
|
57
|
+
publicSharing,
|
|
49
58
|
titleBarOverlay,
|
|
50
59
|
nativeWindowChrome = titleBarOverlay,
|
|
51
60
|
windowControls,
|
|
@@ -55,13 +64,21 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
55
64
|
const instance = useAppSelector((state) => findPaneInstance(state.config.layout, desktopWindowBridge.paneId) ?? null);
|
|
56
65
|
const paneDef = instance ? pluginRegistry.panes.get(instance.paneId) ?? null : null;
|
|
57
66
|
const hasPaneSettings = !!instance && pluginRegistry.hasPaneSettings(instance.instanceId);
|
|
58
|
-
const quickSettings = instance ? pluginRegistry.resolvePaneQuickSettings(instance.instanceId) : [];
|
|
59
67
|
const titleState = useMemo(
|
|
60
|
-
() => ({ config, paneState }) as Parameters<typeof
|
|
68
|
+
() => ({ config, paneState }) as Parameters<typeof resolveTickerForPane>[0],
|
|
61
69
|
[config, paneState],
|
|
62
70
|
);
|
|
71
|
+
const sharePayload = useMemo(() => instance && publicSharing
|
|
72
|
+
? buildPaneSharePayload(
|
|
73
|
+
pluginRegistry,
|
|
74
|
+
instance,
|
|
75
|
+
paneState[instance.instanceId] ?? {},
|
|
76
|
+
resolveTickerForPane(titleState, instance.instanceId),
|
|
77
|
+
)
|
|
78
|
+
: null, [instance, paneState, pluginRegistry, publicSharing, titleState]);
|
|
79
|
+
const quickSettings = instance ? pluginRegistry.resolvePaneQuickSettings(instance.instanceId) : [];
|
|
63
80
|
const title = instance && paneDef
|
|
64
|
-
? getPaneDisplayTitle(titleState, instance, paneDef)
|
|
81
|
+
? getPaneDisplayTitle(titleState, instance, paneDef, pluginRegistry.panes)
|
|
65
82
|
: "Detached Pane";
|
|
66
83
|
const focused = windowFocused;
|
|
67
84
|
|
|
@@ -121,10 +138,56 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
121
138
|
if (nativeWindowChrome) void rendererHost.startWindowDrag?.();
|
|
122
139
|
}, [focusPane, nativeWindowChrome, rendererHost]);
|
|
123
140
|
|
|
124
|
-
const
|
|
141
|
+
const sharePane = useCallback(async () => {
|
|
142
|
+
if (!sharePayload) return;
|
|
143
|
+
try {
|
|
144
|
+
const { id } = await createShare(sharePayload);
|
|
145
|
+
await rendererHost.copyText(openLiveShareUrl(id));
|
|
146
|
+
pluginRegistry.notify({ body: "Share link copied to clipboard", type: "success" });
|
|
147
|
+
} catch (error) {
|
|
148
|
+
pluginRegistry.notify({
|
|
149
|
+
body: error instanceof Error ? error.message : "Could not share this pane.",
|
|
150
|
+
type: "error",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}, [pluginRegistry, rendererHost, sharePayload]);
|
|
154
|
+
|
|
155
|
+
useShortcut((event) => {
|
|
156
|
+
if (resolvePaneManagementShortcut(event) !== "share" || !sharePayload) return;
|
|
157
|
+
if (inputCaptured && event.ctrl && !event.meta && !event.super) return;
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
event.stopPropagation();
|
|
160
|
+
void sharePane();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const openActions = useCallback((event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
|
|
125
164
|
stopMouse(event);
|
|
126
|
-
|
|
127
|
-
|
|
165
|
+
focusPane();
|
|
166
|
+
const items: ContextMenuItem[] = [];
|
|
167
|
+
if (hasPaneSettings) {
|
|
168
|
+
items.push({
|
|
169
|
+
id: "settings",
|
|
170
|
+
label: "Settings",
|
|
171
|
+
onSelect: () => pluginRegistry.openPaneSettingsFn(desktopWindowBridge.paneId),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (sharePayload) items.push({
|
|
175
|
+
id: "share-pane",
|
|
176
|
+
label: "Share Pane",
|
|
177
|
+
accelerator: PANE_MANAGEMENT_ACCELERATORS.share,
|
|
178
|
+
onSelect: sharePane,
|
|
179
|
+
});
|
|
180
|
+
void showContextMenu({
|
|
181
|
+
kind: "pane",
|
|
182
|
+
paneId: desktopWindowBridge.paneId,
|
|
183
|
+
paneType: instance?.paneId ?? "",
|
|
184
|
+
title,
|
|
185
|
+
floating: true,
|
|
186
|
+
}, items, event).then((shown) => {
|
|
187
|
+
if (!shown && hasPaneSettings) pluginRegistry.openPaneSettingsFn(desktopWindowBridge.paneId);
|
|
188
|
+
else if (!shown && sharePayload) void sharePane();
|
|
189
|
+
});
|
|
190
|
+
}, [desktopWindowBridge.paneId, focusPane, hasPaneSettings, instance?.paneId, pluginRegistry, sharePane, sharePayload, showContextMenu, title]);
|
|
128
191
|
const toggleQuickSetting = useCallback((key: string, event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
|
|
129
192
|
stopMouse(event);
|
|
130
193
|
focusPane();
|
|
@@ -230,14 +293,15 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
230
293
|
</Box>
|
|
231
294
|
))}
|
|
232
295
|
<Box flexGrow={1} minWidth={0} />
|
|
233
|
-
{hasPaneSettings && (
|
|
296
|
+
{(hasPaneSettings || sharePayload) && (
|
|
234
297
|
<Text
|
|
235
298
|
fg={paneTitleText(focused, true, colors)}
|
|
236
299
|
selectable={false}
|
|
237
300
|
className="electrobun-webkit-app-region-no-drag"
|
|
238
301
|
data-gloom-role="pane-action"
|
|
239
302
|
data-gloom-interactive="true"
|
|
240
|
-
|
|
303
|
+
aria-label="Pane actions"
|
|
304
|
+
onMouseDown={openActions}
|
|
241
305
|
>
|
|
242
306
|
{" ... "}
|
|
243
307
|
</Text>
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { resolveCollectionForPane, resolveTickerForPane, type AppState } from "../../../state/app/context";
|
|
2
|
-
import { t } from "../../../i18n";
|
|
3
|
-
import {
|
|
2
|
+
import { t, tf } from "../../../i18n";
|
|
3
|
+
import {
|
|
4
|
+
findPaneInstance,
|
|
5
|
+
TICKER_RESEARCH_PANE_ID,
|
|
6
|
+
type PaneInstanceConfig,
|
|
7
|
+
} from "../../../types/config";
|
|
4
8
|
import type { PaneDef } from "../../../types/plugin";
|
|
5
9
|
|
|
6
|
-
|
|
10
|
+
/** Single-cell link glyph: emoji link icons render double-width in terminals. */
|
|
11
|
+
const LINK_GLYPH = "\u29c9";
|
|
12
|
+
|
|
13
|
+
function getBasePaneDisplayTitle(
|
|
7
14
|
state: Pick<AppState, "config" | "paneState">,
|
|
8
15
|
instance: PaneInstanceConfig,
|
|
9
16
|
paneDef: PaneDef,
|
|
@@ -38,6 +45,27 @@ export function getPaneDisplayTitle(
|
|
|
38
45
|
?? t(paneDef.name);
|
|
39
46
|
}
|
|
40
47
|
|
|
48
|
+
// A source pane owns the cursor symbol; echoing it in its own title would just repeat the row.
|
|
49
|
+
if (paneDef.tickerSource) return t(paneDef.name);
|
|
50
|
+
|
|
41
51
|
const ticker = resolveTickerForPane(state as AppState, instance.instanceId);
|
|
42
52
|
return ticker ? `${t(paneDef.name)}: ${ticker}` : t(paneDef.name);
|
|
43
53
|
}
|
|
54
|
+
|
|
55
|
+
export function getPaneDisplayTitle(
|
|
56
|
+
state: Pick<AppState, "config" | "paneState">,
|
|
57
|
+
instance: PaneInstanceConfig,
|
|
58
|
+
paneDef: PaneDef,
|
|
59
|
+
panes?: ReadonlyMap<string, PaneDef>,
|
|
60
|
+
): string {
|
|
61
|
+
const title = getBasePaneDisplayTitle(state, instance, paneDef);
|
|
62
|
+
if (instance.paneId !== TICKER_RESEARCH_PANE_ID || instance.binding?.kind !== "follow" || !panes) {
|
|
63
|
+
return title;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const source = findPaneInstance(state.config.layout, instance.binding.sourceInstanceId);
|
|
67
|
+
const sourceDef = source ? panes.get(source.paneId) : null;
|
|
68
|
+
if (!source || !sourceDef) return title;
|
|
69
|
+
const sourceTitle = getBasePaneDisplayTitle(state, source, sourceDef);
|
|
70
|
+
return `${title} ${LINK_GLYPH} ${tf("Linked to {source}", { source: sourceTitle })}`;
|
|
71
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback
|
|
1
|
+
import { useCallback } from "react";
|
|
2
2
|
import {
|
|
3
3
|
floatAtRect,
|
|
4
4
|
getRememberedFloatingRect,
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
type LayoutBounds,
|
|
11
11
|
type ResolvedPane,
|
|
12
12
|
} from "../../../plugins/pane-manager";
|
|
13
|
-
import type { AppAction } from "../../../state/app/context";
|
|
14
13
|
import type { LayoutConfig } from "../../../types/config";
|
|
15
14
|
import {
|
|
16
15
|
finalizePaneDragRelease,
|
|
@@ -32,7 +31,6 @@ interface UseShellActiveDragOptions {
|
|
|
32
31
|
appHeaderHeight: number;
|
|
33
32
|
bounds: LayoutBounds;
|
|
34
33
|
contentHeight: number;
|
|
35
|
-
dispatch: Dispatch<AppAction>;
|
|
36
34
|
dockGeometryOptions: DockGeometryOptions;
|
|
37
35
|
dockLeafLayouts: DockLeafLayout[];
|
|
38
36
|
dragRuntime: ShellDragRuntimeState;
|
|
@@ -52,7 +50,6 @@ export function useShellActiveDrag({
|
|
|
52
50
|
appHeaderHeight,
|
|
53
51
|
bounds,
|
|
54
52
|
contentHeight,
|
|
55
|
-
dispatch,
|
|
56
53
|
dockGeometryOptions,
|
|
57
54
|
dockLeafLayouts,
|
|
58
55
|
dragRuntime,
|
|
@@ -168,16 +165,13 @@ export function useShellActiveDrag({
|
|
|
168
165
|
? getRememberedFloatingRect(baseLayout, drag.paneId, width, contentHeight, pane?.def)
|
|
169
166
|
: drag.origRect;
|
|
170
167
|
const releaseRect = resolvePaneDragFloatingRect(drag, baseRect, preciseX, preciseShellY, width, contentHeight);
|
|
171
|
-
const
|
|
168
|
+
const nextLayout = finalizePaneDragRelease(baseLayout, drag.paneId, releaseRect, dockPreviewRef.current);
|
|
172
169
|
if (windowMode) {
|
|
173
|
-
updateWindowModePreviewLayout(
|
|
170
|
+
updateWindowModePreviewLayout(nextLayout, drag.paneId);
|
|
174
171
|
} else {
|
|
175
|
-
persistLayout(
|
|
172
|
+
persistLayout(nextLayout);
|
|
176
173
|
}
|
|
177
174
|
focusPane(drag.paneId);
|
|
178
|
-
if (!windowMode && releaseResult.shouldShowGridlockTip) {
|
|
179
|
-
dispatch({ type: "SHOW_GRIDLOCK_TIP" });
|
|
180
|
-
}
|
|
181
175
|
updateDockPreview(null);
|
|
182
176
|
setDragCursor(null);
|
|
183
177
|
updateDragFloatingRect(null);
|
|
@@ -201,7 +195,6 @@ export function useShellActiveDrag({
|
|
|
201
195
|
appHeaderHeight,
|
|
202
196
|
bounds,
|
|
203
197
|
contentHeight,
|
|
204
|
-
dispatch,
|
|
205
198
|
dockGeometryOptions,
|
|
206
199
|
dividerPreviewRef,
|
|
207
200
|
dockLeafLayouts,
|
|
@@ -46,11 +46,6 @@ export type DragPreview =
|
|
|
46
46
|
rect: FloatingRect;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
export interface PaneDragReleaseResult {
|
|
50
|
-
nextLayout: LayoutConfig;
|
|
51
|
-
shouldShowGridlockTip: boolean;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
49
|
export interface PaneDragRectState {
|
|
55
50
|
mode: "docked" | "floating";
|
|
56
51
|
startX: number;
|
|
@@ -272,25 +267,14 @@ export function finalizePaneDragRelease(
|
|
|
272
267
|
paneId: string,
|
|
273
268
|
previewRect: FloatingRect,
|
|
274
269
|
dockPreview: DragPreview | null,
|
|
275
|
-
):
|
|
270
|
+
): LayoutConfig {
|
|
276
271
|
if (dockPreview?.kind === "dock") {
|
|
277
|
-
return
|
|
278
|
-
nextLayout: applyDrop(layout, paneId, dockPreview.target),
|
|
279
|
-
shouldShowGridlockTip: false,
|
|
280
|
-
};
|
|
272
|
+
return applyDrop(layout, paneId, dockPreview.target);
|
|
281
273
|
}
|
|
282
|
-
|
|
283
274
|
if (dockPreview?.kind === "snap") {
|
|
284
|
-
return
|
|
285
|
-
nextLayout: floatAtRect(layout, paneId, dockPreview.rect),
|
|
286
|
-
shouldShowGridlockTip: true,
|
|
287
|
-
};
|
|
275
|
+
return floatAtRect(layout, paneId, dockPreview.rect);
|
|
288
276
|
}
|
|
289
|
-
|
|
290
|
-
return {
|
|
291
|
-
nextLayout: floatAtRect(layout, paneId, previewRect),
|
|
292
|
-
shouldShowGridlockTip: false,
|
|
293
|
-
};
|
|
277
|
+
return floatAtRect(layout, paneId, previewRect);
|
|
294
278
|
}
|
|
295
279
|
|
|
296
280
|
export function makeSnapGuides(width: number, height: number): SnapGuide[] {
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
type LayoutBounds,
|
|
16
16
|
type ResolvedPane,
|
|
17
17
|
} from "../../../../plugins/pane-manager";
|
|
18
|
-
import type { AppAction } from "../../../../state/app/context";
|
|
19
18
|
import type { LayoutConfig } from "../../../../types/config";
|
|
20
19
|
import {
|
|
21
20
|
constrainFloatingRectToBounds,
|
|
@@ -165,7 +164,6 @@ interface UseShellPointerRuntimeOptions {
|
|
|
165
164
|
bounds: LayoutBounds;
|
|
166
165
|
closePaneMenu: () => void;
|
|
167
166
|
contentHeight: number;
|
|
168
|
-
dispatch: Dispatch<AppAction>;
|
|
169
167
|
dockGeometryOptions: DockGeometryOptions;
|
|
170
168
|
dockDividerLayouts: DockDividerLayout[];
|
|
171
169
|
dockLeafLayouts: DockLeafLayout[];
|
|
@@ -193,6 +191,7 @@ interface UseShellPointerRuntimeOptions {
|
|
|
193
191
|
visibleLayout: LayoutConfig;
|
|
194
192
|
width: number;
|
|
195
193
|
windowMode: WindowEditState | null;
|
|
194
|
+
commandBarOpen: boolean;
|
|
196
195
|
}
|
|
197
196
|
|
|
198
197
|
export function useShellPointerRuntime({
|
|
@@ -200,7 +199,6 @@ export function useShellPointerRuntime({
|
|
|
200
199
|
bounds,
|
|
201
200
|
closePaneMenu,
|
|
202
201
|
contentHeight,
|
|
203
|
-
dispatch,
|
|
204
202
|
dockGeometryOptions,
|
|
205
203
|
dockDividerLayouts,
|
|
206
204
|
dockLeafLayouts,
|
|
@@ -224,12 +222,12 @@ export function useShellPointerRuntime({
|
|
|
224
222
|
visibleLayout,
|
|
225
223
|
width,
|
|
226
224
|
windowMode,
|
|
225
|
+
commandBarOpen,
|
|
227
226
|
}: UseShellPointerRuntimeOptions) {
|
|
228
227
|
const handleActiveDrag = useShellActiveDrag({
|
|
229
228
|
appHeaderHeight,
|
|
230
229
|
bounds,
|
|
231
230
|
contentHeight,
|
|
232
|
-
dispatch,
|
|
233
231
|
dockGeometryOptions,
|
|
234
232
|
dockLeafLayouts,
|
|
235
233
|
focusPane,
|
|
@@ -282,6 +280,7 @@ export function useShellPointerRuntime({
|
|
|
282
280
|
setMenuState,
|
|
283
281
|
transientFocusActive,
|
|
284
282
|
windowMode,
|
|
283
|
+
commandBarOpen,
|
|
285
284
|
});
|
|
286
285
|
|
|
287
286
|
return {
|
|
@@ -62,6 +62,7 @@ function createShellPluginRegistry(options?: {
|
|
|
62
62
|
hasPaneSettings: (paneId: string) => paneId === "portfolio-list:main",
|
|
63
63
|
openPaneSettingsFn: () => {},
|
|
64
64
|
openCommandBar: () => {},
|
|
65
|
+
showPane: () => {},
|
|
65
66
|
openWindowMode: () => {},
|
|
66
67
|
openWindowModeFn: () => {},
|
|
67
68
|
updateLayoutFn: () => {},
|
|
@@ -356,7 +357,8 @@ describe("Shell", () => {
|
|
|
356
357
|
expect(resolvePaneManagementShortcut({ ...base, name: "D", key: "D" })).toBe("toggle-floating");
|
|
357
358
|
expect(resolvePaneManagementShortcut({ ...base, name: "o", key: "o" })).toBe("pop-out");
|
|
358
359
|
expect(resolvePaneManagementShortcut({ ...base, name: "c", key: "c" })).toBe("copy-screenshot");
|
|
359
|
-
expect(resolvePaneManagementShortcut({ ...base, name: "
|
|
360
|
+
expect(resolvePaneManagementShortcut({ ...base, name: "s", key: "s" })).toBe("share");
|
|
361
|
+
expect(resolvePaneManagementShortcut({ ...base, name: "l", key: "l" })).toBe("layout-gallery");
|
|
360
362
|
expect(resolvePaneManagementShortcut({ ...base, name: "f", key: "f" })).toBe("toggle-fullscreen");
|
|
361
363
|
expect(resolvePaneManagementShortcut({ ...base, name: "g", key: "g" })).toBe("gridlock-all");
|
|
362
364
|
expect(resolvePaneManagementShortcut({ ...base, name: "m", key: "m" })).toBe("window-mode");
|
|
@@ -366,6 +368,18 @@ describe("Shell", () => {
|
|
|
366
368
|
expect(resolvePaneManagementShortcut({ ...base, name: "d", key: "d", meta: false, super: false })).toBeNull();
|
|
367
369
|
});
|
|
368
370
|
|
|
371
|
+
test("opens the layout browser from the primary Shift-L shortcut", async () => {
|
|
372
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-layout-browser-shortcut-test");
|
|
373
|
+
const opened: string[] = [];
|
|
374
|
+
const registry = createShellPluginRegistry();
|
|
375
|
+
registry.showPane = (paneId) => opened.push(paneId);
|
|
376
|
+
await renderShellForWindowModeTest(createInitialState(config), { registry });
|
|
377
|
+
|
|
378
|
+
await emitKeypress({ name: "l", ctrl: true, shift: true });
|
|
379
|
+
|
|
380
|
+
expect(opened).toEqual(["layout-marketplace"]);
|
|
381
|
+
});
|
|
382
|
+
|
|
369
383
|
test("toggles the focused pane fullscreen without persisting layout", async () => {
|
|
370
384
|
const config = createDefaultConfig("/tmp/gloomberb-shell-fullscreen-shortcut-test");
|
|
371
385
|
const mainPane = requireLayoutInstance(config, "portfolio-list:main");
|
|
@@ -478,8 +492,8 @@ describe("Shell", () => {
|
|
|
478
492
|
|
|
479
493
|
const frame = testSetup!.captureCharFrame();
|
|
480
494
|
const rows = frame.split("\n");
|
|
481
|
-
expect(rows[2]?.indexOf("
|
|
482
|
-
expect(rows[5]?.indexOf("
|
|
495
|
+
expect(rows[2]?.indexOf(":: Main Portfolio") ?? -1).toBeLessThan(0);
|
|
496
|
+
expect(rows[5]?.indexOf(":: Main Portfolio")).toBeGreaterThanOrEqual(14);
|
|
483
497
|
|
|
484
498
|
await act(async () => {
|
|
485
499
|
await testSetup!.mockMouse.release(16, 6);
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
syncConfigActiveLayoutState,
|
|
22
22
|
useAppDispatch,
|
|
23
23
|
useAppSelector,
|
|
24
|
+
useAppStateRef,
|
|
24
25
|
} from "../../../state/app/context";
|
|
25
26
|
import {
|
|
26
27
|
selectCommandBarOpen,
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
menuForPane,
|
|
38
39
|
menuItemsForFallback,
|
|
39
40
|
} from "./menu";
|
|
41
|
+
import { tickerLinkMenuItems } from "./ticker-link-menu";
|
|
40
42
|
import {
|
|
41
43
|
makeSnapGuides,
|
|
42
44
|
resolveExternalDockPreview,
|
|
@@ -67,6 +69,9 @@ import {
|
|
|
67
69
|
resolveShellCursorOcclusionRects,
|
|
68
70
|
useShellCursorOcclusionGuard,
|
|
69
71
|
} from "./cursor-occlusion";
|
|
72
|
+
import { createShare, openLiveShareUrl } from "../../../shares/api";
|
|
73
|
+
import { buildPaneSharePayload } from "../../../shares/pane";
|
|
74
|
+
import type { SharePayload } from "../../../shares/payload";
|
|
70
75
|
|
|
71
76
|
export { resolveAppHeaderHeightCells } from "./chrome";
|
|
72
77
|
export { buildNativeWindowState } from "./native/window-state";
|
|
@@ -100,13 +105,14 @@ export function Shell({
|
|
|
100
105
|
const previousFocusedPaneId = useAppSelector((state) => state.previousFocusedPaneId);
|
|
101
106
|
const activePanel = useAppSelector((state) => state.activePanel);
|
|
102
107
|
const commandBarOpen = useAppSelector(selectCommandBarOpen);
|
|
108
|
+
const stateRef = useAppStateRef();
|
|
103
109
|
const inputCaptured = useAppSelector((state) => state.inputCaptured);
|
|
104
110
|
const statusBarVisible = useAppSelector(selectStatusBarVisible);
|
|
105
111
|
const rendererHost = useRendererHost();
|
|
106
112
|
const { setTransientLayout } = useTransientLayout();
|
|
107
113
|
const uiKind = useUiHost().kind;
|
|
108
114
|
const shortcutDisplayMode = getShortcutDisplayMode(uiKind);
|
|
109
|
-
const { nativePaneChrome = false, nativeContextMenu, precisePointer, titleBarOverlay, cellHeightPx } = useUiCapabilities();
|
|
115
|
+
const { nativePaneChrome = false, nativeContextMenu, precisePointer, publicSharing, titleBarOverlay, cellHeightPx } = useUiCapabilities();
|
|
110
116
|
const { showContextMenu } = useContextMenu();
|
|
111
117
|
const { width, height } = useViewport();
|
|
112
118
|
const shellRef = useRef<BoxRenderable | null>(null);
|
|
@@ -119,8 +125,9 @@ export function Shell({
|
|
|
119
125
|
const dialogOpen = useDialogState((dialog) => dialog.isOpen);
|
|
120
126
|
const [hoveredPaneId, setHoveredPaneId] = useState<string | null>(null);
|
|
121
127
|
const setHoveredPaneIfChanged = useCallback((paneId: string | null) => {
|
|
128
|
+
if (commandBarOpen) return;
|
|
122
129
|
setHoveredPaneId((current) => (current === paneId ? current : paneId));
|
|
123
|
-
}, []);
|
|
130
|
+
}, [commandBarOpen]);
|
|
124
131
|
const [menuState, setMenuState] = useState<ActionMenuState | null>(null);
|
|
125
132
|
const [transientFocusLayoutState, setTransientFocusLayoutState] = useState<TransientFocusLayoutState | null>(null);
|
|
126
133
|
const transientFocusLayoutStateRef = useRef<TransientFocusLayoutState | null>(null);
|
|
@@ -131,6 +138,9 @@ export function Shell({
|
|
|
131
138
|
setHoveredMenuItemId(null);
|
|
132
139
|
}, []);
|
|
133
140
|
const overlayOpen = commandBarOpen || dialogOpen || !!menuState;
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (commandBarOpen) setHoveredPaneId(null);
|
|
143
|
+
}, [commandBarOpen]);
|
|
134
144
|
|
|
135
145
|
const dragRuntime = useShellDragRuntimeState({
|
|
136
146
|
contentHeight,
|
|
@@ -165,13 +175,14 @@ export function Shell({
|
|
|
165
175
|
dispatch(hasFocusTarget
|
|
166
176
|
? { type: "UPDATE_LAYOUT", layout: nextLayout, focusedPaneId: options.focusedPaneId ?? null }
|
|
167
177
|
: { type: "UPDATE_LAYOUT", layout: nextLayout });
|
|
178
|
+
const currentState = stateRef.current;
|
|
168
179
|
scheduleConfigSave(syncConfigActiveLayoutState(
|
|
169
|
-
{ ...config, layout: nextLayout },
|
|
170
|
-
paneState,
|
|
171
|
-
hasFocusTarget ? (options.focusedPaneId ?? null) : focusedPaneId,
|
|
172
|
-
activePanel,
|
|
180
|
+
{ ...currentState.config, layout: nextLayout },
|
|
181
|
+
currentState.paneState,
|
|
182
|
+
hasFocusTarget ? (options.focusedPaneId ?? null) : currentState.focusedPaneId,
|
|
183
|
+
currentState.activePanel,
|
|
173
184
|
));
|
|
174
|
-
}, [
|
|
185
|
+
}, [dispatch, stateRef]);
|
|
175
186
|
|
|
176
187
|
const focusPane = useCallback((paneId: string) => {
|
|
177
188
|
dispatch({ type: "FOCUS_PANE", paneId });
|
|
@@ -253,7 +264,6 @@ export function Shell({
|
|
|
253
264
|
gridlockVisiblePanes,
|
|
254
265
|
handleFloatingClose,
|
|
255
266
|
openFocusedPaneSettings,
|
|
256
|
-
openLayoutMenu,
|
|
257
267
|
openPaneSettings,
|
|
258
268
|
popOutFocusedPane,
|
|
259
269
|
toggleFocusedPaneFloating,
|
|
@@ -272,6 +282,9 @@ export function Shell({
|
|
|
272
282
|
visibleLayout,
|
|
273
283
|
width,
|
|
274
284
|
});
|
|
285
|
+
const openLayoutGallery = useCallback(() => {
|
|
286
|
+
pluginRegistry.showPane("layout-marketplace");
|
|
287
|
+
}, [pluginRegistry]);
|
|
275
288
|
const setTransientFocusLayout = useCallback((next: TransientFocusLayoutState | null) => {
|
|
276
289
|
transientFocusLayoutStateRef.current = next;
|
|
277
290
|
setTransientFocusLayoutState(next);
|
|
@@ -369,24 +382,6 @@ export function Shell({
|
|
|
369
382
|
transientFocusLayoutState,
|
|
370
383
|
]);
|
|
371
384
|
|
|
372
|
-
useShellPaneManagementShortcuts({
|
|
373
|
-
cancelActiveDrag,
|
|
374
|
-
closeAllFloatingPanes,
|
|
375
|
-
closeFocusedPane,
|
|
376
|
-
copyFocusedPaneScreenshot,
|
|
377
|
-
focusedPaneId,
|
|
378
|
-
gridlockVisiblePanes,
|
|
379
|
-
hasActiveDrag,
|
|
380
|
-
inputCaptured,
|
|
381
|
-
openFocusedPaneSettings,
|
|
382
|
-
openLayoutMenu,
|
|
383
|
-
overlayOpen,
|
|
384
|
-
popOutFocusedPane,
|
|
385
|
-
startWindowMode,
|
|
386
|
-
toggleFocusedPaneFullscreen,
|
|
387
|
-
toggleFocusedPaneFloating,
|
|
388
|
-
});
|
|
389
|
-
|
|
390
385
|
const dockLeafLayouts = useMemo(() => getDockLeafLayouts(activeLayout, bounds, dockGeometryOptions), [activeLayout, bounds, dockGeometryOptions]);
|
|
391
386
|
const dockDividerLayouts = useMemo(() => getDockDividerLayouts(activeLayout, bounds, dockGeometryOptions), [activeLayout, bounds, dockGeometryOptions]);
|
|
392
387
|
const snapGuides = useMemo(() => makeSnapGuides(width, contentHeight), [contentHeight, width]);
|
|
@@ -423,8 +418,8 @@ export function Shell({
|
|
|
423
418
|
[config, paneState],
|
|
424
419
|
);
|
|
425
420
|
const getPaneTitle = useCallback(
|
|
426
|
-
(pane: ResolvedPane): string => getPaneDisplayTitle(titleState, pane.instance, pane.def),
|
|
427
|
-
[titleState],
|
|
421
|
+
(pane: ResolvedPane): string => getPaneDisplayTitle(titleState, pane.instance, pane.def, pluginRegistry.panes),
|
|
422
|
+
[pluginRegistry.panes, titleState],
|
|
428
423
|
);
|
|
429
424
|
const handlePaneQuickSetting = useCallback((paneId: string, key: string, event: any) => {
|
|
430
425
|
event?.preventDefault?.();
|
|
@@ -447,6 +442,51 @@ export function Shell({
|
|
|
447
442
|
onMouseDown: (event) => handlePaneQuickSetting(paneId, setting.key, event),
|
|
448
443
|
}))
|
|
449
444
|
), [config, handlePaneQuickSetting, pluginRegistry]);
|
|
445
|
+
const sharePane = useCallback(async (payload: Extract<SharePayload, { kind: "pane" }>) => {
|
|
446
|
+
try {
|
|
447
|
+
const { id } = await createShare(payload);
|
|
448
|
+
await rendererHost.copyText(openLiveShareUrl(id));
|
|
449
|
+
pluginRegistry.notify({ body: "Share link copied to clipboard", type: "success" });
|
|
450
|
+
} catch (error) {
|
|
451
|
+
pluginRegistry.notify({
|
|
452
|
+
body: error instanceof Error ? error.message : "Could not share this pane.",
|
|
453
|
+
type: "error",
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}, [pluginRegistry, rendererHost]);
|
|
457
|
+
const shareFocusedPane = useCallback(() => {
|
|
458
|
+
if (!publicSharing || !focusedPaneId) return false;
|
|
459
|
+
const pane = paneMap.get(focusedPaneId);
|
|
460
|
+
if (!pane) return false;
|
|
461
|
+
const payload = buildPaneSharePayload(
|
|
462
|
+
pluginRegistry,
|
|
463
|
+
pane.instance,
|
|
464
|
+
paneState[focusedPaneId] ?? {},
|
|
465
|
+
resolveTickerForPane(titleState, focusedPaneId),
|
|
466
|
+
);
|
|
467
|
+
if (!payload) return false;
|
|
468
|
+
void sharePane(payload);
|
|
469
|
+
return true;
|
|
470
|
+
}, [focusedPaneId, paneMap, paneState, pluginRegistry, publicSharing, sharePane, titleState]);
|
|
471
|
+
|
|
472
|
+
useShellPaneManagementShortcuts({
|
|
473
|
+
cancelActiveDrag,
|
|
474
|
+
closeAllFloatingPanes,
|
|
475
|
+
closeFocusedPane,
|
|
476
|
+
copyFocusedPaneScreenshot,
|
|
477
|
+
focusedPaneId,
|
|
478
|
+
gridlockVisiblePanes,
|
|
479
|
+
hasActiveDrag,
|
|
480
|
+
inputCaptured,
|
|
481
|
+
openFocusedPaneSettings,
|
|
482
|
+
openLayoutGallery,
|
|
483
|
+
overlayOpen,
|
|
484
|
+
popOutFocusedPane,
|
|
485
|
+
shareFocusedPane,
|
|
486
|
+
startWindowMode,
|
|
487
|
+
toggleFocusedPaneFullscreen,
|
|
488
|
+
toggleFocusedPaneFloating,
|
|
489
|
+
});
|
|
450
490
|
|
|
451
491
|
const openPaneMenu = useCallback((paneId: string, rect: LayoutBounds, event?: { preventDefault?: () => void; stopPropagation?: () => void }) => {
|
|
452
492
|
const pane = paneMap.get(paneId);
|
|
@@ -459,6 +499,14 @@ export function Shell({
|
|
|
459
499
|
title: getPaneTitle(pane),
|
|
460
500
|
floating: !!pane.floating,
|
|
461
501
|
};
|
|
502
|
+
const sharePayload = publicSharing
|
|
503
|
+
? buildPaneSharePayload(
|
|
504
|
+
pluginRegistry,
|
|
505
|
+
pane.instance,
|
|
506
|
+
paneState[paneId] ?? {},
|
|
507
|
+
resolveTickerForPane(titleState, paneId),
|
|
508
|
+
)
|
|
509
|
+
: null;
|
|
462
510
|
const items = menuForPane(
|
|
463
511
|
pane,
|
|
464
512
|
visibleLayout,
|
|
@@ -470,6 +518,14 @@ export function Shell({
|
|
|
470
518
|
openPaneSettings,
|
|
471
519
|
desktopWindowBridge,
|
|
472
520
|
nativePaneChrome && rendererHost.copyPngImage ? copyPaneScreenshot : undefined,
|
|
521
|
+
sharePayload ? () => sharePane(sharePayload) : undefined,
|
|
522
|
+
tickerLinkMenuItems({
|
|
523
|
+
instance: pane.instance,
|
|
524
|
+
layout: visibleLayout,
|
|
525
|
+
panes: pluginRegistry.panes,
|
|
526
|
+
state: titleState,
|
|
527
|
+
persistLayout,
|
|
528
|
+
}),
|
|
473
529
|
);
|
|
474
530
|
void showContextMenu(context, items, event).then((shown) => {
|
|
475
531
|
if (shown) return;
|
|
@@ -493,7 +549,7 @@ export function Shell({
|
|
|
493
549
|
items: fallbackItems,
|
|
494
550
|
});
|
|
495
551
|
});
|
|
496
|
-
}, [contentHeight, copyPaneScreenshot, desktopWindowBridge, focusPane, getPaneTitle, nativePaneChrome, openPaneSettings, paneMap, persistLayout, pluginRegistry, rendererHost.copyPngImage, shortcutDisplayMode, showContextMenu, visibleLayout, width]);
|
|
552
|
+
}, [contentHeight, copyPaneScreenshot, desktopWindowBridge, focusPane, getPaneTitle, nativePaneChrome, openPaneSettings, paneMap, paneState, persistLayout, pluginRegistry, publicSharing, rendererHost.copyPngImage, sharePane, shortcutDisplayMode, showContextMenu, titleState, visibleLayout, width]);
|
|
497
553
|
|
|
498
554
|
const {
|
|
499
555
|
handleFloatingCloseMouseDown,
|
|
@@ -511,7 +567,6 @@ export function Shell({
|
|
|
511
567
|
bounds,
|
|
512
568
|
closePaneMenu,
|
|
513
569
|
contentHeight,
|
|
514
|
-
dispatch,
|
|
515
570
|
dockGeometryOptions,
|
|
516
571
|
dockDividerLayouts,
|
|
517
572
|
dockLeafLayouts,
|
|
@@ -535,6 +590,7 @@ export function Shell({
|
|
|
535
590
|
visibleLayout,
|
|
536
591
|
width,
|
|
537
592
|
windowMode,
|
|
593
|
+
commandBarOpen,
|
|
538
594
|
});
|
|
539
595
|
const windowModeDockResizePathKey = windowMode?.focus.kind === "dock-resize"
|
|
540
596
|
? windowMode.focus.pathKey
|
|
@@ -1,7 +1,43 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { displayWidth } from "../../../utils/format";
|
|
3
3
|
import { getLanguage, setLanguage, t } from "../../../i18n";
|
|
4
|
-
import { actionMenuWidth } from "./menu";
|
|
4
|
+
import { actionMenuWidth, menuForPane } from "./menu";
|
|
5
|
+
|
|
6
|
+
describe("pane action menu", () => {
|
|
7
|
+
test("offers the global share action for a portable pane", async () => {
|
|
8
|
+
let shared = false;
|
|
9
|
+
const items = menuForPane(
|
|
10
|
+
{
|
|
11
|
+
instance: { instanceId: "chart-1", paneId: "chart-composer" },
|
|
12
|
+
def: { defaultPosition: "right" },
|
|
13
|
+
floating: false,
|
|
14
|
+
} as any,
|
|
15
|
+
{
|
|
16
|
+
dockRoot: { kind: "pane", instanceId: "chart-1" },
|
|
17
|
+
instances: [{ instanceId: "chart-1", paneId: "chart-composer" }],
|
|
18
|
+
floating: [],
|
|
19
|
+
detached: [],
|
|
20
|
+
},
|
|
21
|
+
120,
|
|
22
|
+
40,
|
|
23
|
+
{
|
|
24
|
+
hasPaneSettings: () => false,
|
|
25
|
+
openWindowMode: () => {},
|
|
26
|
+
} as any,
|
|
27
|
+
() => {},
|
|
28
|
+
() => {},
|
|
29
|
+
() => {},
|
|
30
|
+
undefined,
|
|
31
|
+
undefined,
|
|
32
|
+
() => { shared = true; },
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const share = items.find((item) => item.type !== "divider" && item.id === "share-pane");
|
|
36
|
+
await share?.onSelect?.();
|
|
37
|
+
expect(shared).toBe(true);
|
|
38
|
+
expect(share?.accelerator).toBe("CmdOrCtrl+Shift+S");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
5
41
|
|
|
6
42
|
describe("action menu sizing", () => {
|
|
7
43
|
test("uses translated terminal display width", () => {
|