gloomberb 0.9.5 → 0.9.7
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 +49 -8
- package/package.json +2 -2
- package/src/app/global-shortcuts.test.tsx +20 -1
- package/src/app/global-shortcuts.ts +2 -0
- package/src/app/pane-runtime/index.tsx +11 -4
- package/src/app/pane-runtime/pane-settings-runtime.tsx +1 -1
- package/src/app.tsx +41 -17
- package/src/cli/commands/remote.ts +206 -0
- package/src/cli/index.ts +2 -0
- package/src/components/chart/core/keyboard.ts +10 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +4 -0
- package/src/components/chart/static/chart/surface.test.tsx +59 -4
- package/src/components/chart/static/chart/surface.tsx +4 -0
- package/src/components/chart/stock-chart/index.test.ts +7 -0
- package/src/components/command-bar/list/view.tsx +47 -0
- package/src/components/command-bar/multi-select-picker.tsx +7 -0
- package/src/components/command-bar/routing/navigation-state.ts +10 -1
- package/src/components/command-bar/surface/index.test.tsx +14 -0
- package/src/components/command-bar/theme-picker.tsx +91 -79
- package/src/components/command-bar/workflow/field-row.tsx +30 -5
- package/src/components/index.ts +7 -0
- package/src/components/layout/pane/footer/index.tsx +23 -0
- package/src/components/layout/shell/index.test.tsx +2 -0
- package/src/components/layout/shell/index.tsx +8 -3
- package/src/components/layout/shell/pane/actions.ts +21 -7
- package/src/components/metric-treemap/index.tsx +317 -0
- package/src/components/metric-treemap/layout.test.ts +77 -0
- package/src/components/metric-treemap/layout.ts +476 -0
- package/src/components/toggle-list.tsx +10 -0
- package/src/components/ui/button.tsx +12 -0
- package/src/components/ui/data-table/index.tsx +51 -0
- package/src/components/ui/fields.tsx +18 -0
- package/src/components/ui/list-view.tsx +83 -3
- package/src/components/ui/message-composer.tsx +22 -0
- package/src/components/ui/tabs.tsx +45 -0
- package/src/core/state/app/layout-reducer.ts +7 -1
- package/src/core/state/app/layout.ts +6 -0
- package/src/core/state/app/state.ts +23 -1
- package/src/core/state/app/types.ts +4 -2
- package/src/pane-settings.test.ts +1 -0
- package/src/plugins/builtin/chat/channels.ts +5 -18
- package/src/plugins/builtin/chat/chat.test.tsx +1 -7
- package/src/plugins/builtin/chat/status-widget.tsx +1 -7
- package/src/plugins/builtin/cloud/plugin.tsx +4 -13
- package/src/plugins/builtin/holders/treemap.tsx +28 -297
- package/src/plugins/builtin/holders/types.ts +0 -34
- package/src/plugins/builtin/market-heatmap/data.test.ts +121 -0
- package/src/plugins/builtin/market-heatmap/data.ts +296 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +320 -0
- package/src/plugins/builtin/plugin-groups.ts +2 -0
- package/src/plugins/builtin/portfolio-list/grid.tsx +197 -0
- package/src/plugins/builtin/portfolio-list/index.test.ts +52 -1
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +15 -1
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +149 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +67 -26
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +54 -4
- package/src/plugins/builtin/portfolio-list/settings.ts +36 -1
- package/src/plugins/ibkr/gateway/service/index.test.ts +22 -0
- package/src/plugins/ibkr/gateway/service/index.ts +6 -9
- package/src/plugins/ibkr/gateway/service/native-loader.ts +12 -0
- package/src/remote/app-host.tsx +77 -0
- package/src/remote/client.ts +34 -0
- package/src/remote/command-bar.ts +98 -0
- package/src/remote/controller-utils.ts +111 -0
- package/src/remote/controller.test.ts +365 -0
- package/src/remote/controller.ts +434 -0
- package/src/remote/endpoint.ts +41 -0
- package/src/remote/json-patch.test.ts +31 -0
- package/src/remote/json-patch.ts +89 -0
- package/src/remote/layout-helpers.ts +62 -0
- package/src/remote/resources.ts +186 -0
- package/src/remote/revision.ts +18 -0
- package/src/remote/schema.ts +143 -0
- package/src/remote/semantic-helpers.ts +161 -0
- package/src/remote/semantic-tree.tsx +112 -0
- package/src/remote/server.test.ts +50 -0
- package/src/remote/server.ts +121 -0
- package/src/remote/types.ts +119 -0
- package/src/renderers/electrobun/bun/index.ts +55 -1
- package/src/renderers/electrobun/shared/protocol.ts +11 -1
- package/src/renderers/electrobun/view/backend-rpc.ts +28 -1
- package/src/renderers/electrobun/view/build-assets.ts +1 -0
- package/src/renderers/electrobun/view/host/box.tsx +5 -0
- package/src/renderers/electrobun/view/host/mouse.test.ts +37 -0
- package/src/renderers/electrobun/view/host/mouse.ts +2 -0
- package/src/renderers/electrobun/view/main.tsx +5 -1
- package/src/renderers/electrobun/view/native-stubs/ibkr-native-loader.ts +3 -0
- package/src/renderers/opentui/chart-surface.test.tsx +101 -0
- package/src/renderers/opentui/chart-surface.tsx +15 -20
- package/src/renderers/opentui/start.tsx +32 -0
- package/src/sources/yahoo-finance/http.ts +31 -4
- package/src/state/app/context/index.test.tsx +47 -0
- package/src/state/app/context/index.tsx +48 -18
- package/src/types/config.ts +1 -0
- package/src/ui/host.tsx +1 -0
- package/src/ui/index.tsx +183 -7
- package/src/version.ts +1 -1
- package/src/plugins/builtin/holders/treemap-layout.ts +0 -231
|
@@ -253,6 +253,10 @@ export function StaticChartSurface({
|
|
|
253
253
|
flexDirection="column"
|
|
254
254
|
bitmaps={bitmap ? [bitmap] : null}
|
|
255
255
|
crosshair={canvasCrosshair}
|
|
256
|
+
onMouseMove={handleCursorEvent}
|
|
257
|
+
onMouseDown={handleCursorEvent}
|
|
258
|
+
onMouseOut={clearCursor}
|
|
259
|
+
data-gloom-remote-kind="static-chart"
|
|
256
260
|
>
|
|
257
261
|
{textResult.lines.map((line, index) => (
|
|
258
262
|
<Text key={index} content={line} />
|
|
@@ -159,6 +159,13 @@ describe("stock chart keyboard helpers", () => {
|
|
|
159
159
|
expect(resolveChartKeyboardKey({ name: "minus" })).toBe("zoom-out");
|
|
160
160
|
expect(resolveChartKeyboardKey({ name: "A" })).toBe("a");
|
|
161
161
|
});
|
|
162
|
+
|
|
163
|
+
test("ignores shortcut-modified keys so app shortcuts can handle them", () => {
|
|
164
|
+
expect(resolveChartKeyboardKey({ name: "1", ctrl: true })).toBe("");
|
|
165
|
+
expect(resolveChartKeyboardKey({ name: "2", meta: true })).toBe("");
|
|
166
|
+
expect(resolveChartKeyboardKey({ name: "3", super: true })).toBe("");
|
|
167
|
+
expect(resolveChartKeyboardKey({ name: "4", alt: true })).toBe("");
|
|
168
|
+
});
|
|
162
169
|
});
|
|
163
170
|
|
|
164
171
|
describe("stock chart viewport helpers", () => {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { Spinner } from "../../ui";
|
|
11
11
|
import type { CommandBarListRow, ListScreenState, ResultItem } from "./model";
|
|
12
12
|
import { getRowPresentation, truncateText } from "../view-model";
|
|
13
|
+
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
13
14
|
|
|
14
15
|
export type CommandBarListScrollEvent = {
|
|
15
16
|
stopPropagation: () => void;
|
|
@@ -70,6 +71,8 @@ export const CommandBarListHeader = memo(function CommandBarListHeader({
|
|
|
70
71
|
onChange={onQueryChange}
|
|
71
72
|
placeholder={kind === "root" ? "Search commands" : title === "Security Description" ? "Search tickers" : "Filter"}
|
|
72
73
|
focused
|
|
74
|
+
data-gloom-remote-scope="command-bar"
|
|
75
|
+
data-gloom-remote-surface="command-bar"
|
|
73
76
|
width={nativePaneChrome ? "100%" : queryDisplayWidth}
|
|
74
77
|
backgroundColor={nativePaneChrome ? "transparent" : paletteBg}
|
|
75
78
|
focusedBackgroundColor={nativePaneChrome ? "transparent" : paletteBg}
|
|
@@ -114,6 +117,9 @@ interface CommandBarListItemRowProps {
|
|
|
114
117
|
globalIdx: number;
|
|
115
118
|
isSelected: boolean;
|
|
116
119
|
isHovered: boolean;
|
|
120
|
+
listKind: ListScreenState["kind"];
|
|
121
|
+
listTitle: string;
|
|
122
|
+
listQuery: string;
|
|
117
123
|
contentPadding: number;
|
|
118
124
|
labelWidth: number;
|
|
119
125
|
trailingWidth: number;
|
|
@@ -135,6 +141,9 @@ const CommandBarListItemRow = memo(function CommandBarListItemRow({
|
|
|
135
141
|
globalIdx,
|
|
136
142
|
isSelected,
|
|
137
143
|
isHovered,
|
|
144
|
+
listKind,
|
|
145
|
+
listTitle,
|
|
146
|
+
listQuery,
|
|
138
147
|
contentPadding,
|
|
139
148
|
labelWidth,
|
|
140
149
|
trailingWidth,
|
|
@@ -153,6 +162,41 @@ const CommandBarListItemRow = memo(function CommandBarListItemRow({
|
|
|
153
162
|
const presentation = getRowPresentation(item, isSelected, trailingWidth > 0);
|
|
154
163
|
const label = truncateText(presentation.label, labelWidth);
|
|
155
164
|
const trailing = truncateText(presentation.trailing, trailingWidth);
|
|
165
|
+
const activate = () => onRowMouseDown({
|
|
166
|
+
preventDefault() {},
|
|
167
|
+
stopPropagation() {},
|
|
168
|
+
}, item, globalIdx);
|
|
169
|
+
useRemoteUiNode({
|
|
170
|
+
role: "command-bar-result",
|
|
171
|
+
label: item.label,
|
|
172
|
+
disabled: item.disabled === true,
|
|
173
|
+
actions: {
|
|
174
|
+
activate,
|
|
175
|
+
press: activate,
|
|
176
|
+
secondary: item.secondaryAction ? () => item.secondaryAction?.() : undefined,
|
|
177
|
+
},
|
|
178
|
+
metadata: {
|
|
179
|
+
index: globalIdx,
|
|
180
|
+
selected: isSelected,
|
|
181
|
+
hovered: isHovered,
|
|
182
|
+
listKind,
|
|
183
|
+
listTitle,
|
|
184
|
+
listQuery,
|
|
185
|
+
item: {
|
|
186
|
+
id: item.id,
|
|
187
|
+
label: item.label,
|
|
188
|
+
detail: item.detail,
|
|
189
|
+
category: item.category,
|
|
190
|
+
kind: item.kind,
|
|
191
|
+
right: item.right,
|
|
192
|
+
shortcutQuery: item.shortcutQuery,
|
|
193
|
+
searchText: item.searchText,
|
|
194
|
+
checked: item.checked,
|
|
195
|
+
current: item.current,
|
|
196
|
+
disabled: item.disabled === true,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
});
|
|
156
200
|
|
|
157
201
|
return (
|
|
158
202
|
<Box
|
|
@@ -283,6 +327,9 @@ export const CommandBarListBody = memo(function CommandBarListBody({
|
|
|
283
327
|
globalIdx={row.globalIdx}
|
|
284
328
|
isSelected={isSelected}
|
|
285
329
|
isHovered={isHovered}
|
|
330
|
+
listKind={visibleListState.kind}
|
|
331
|
+
listTitle={visibleListState.title}
|
|
332
|
+
listQuery={visibleListState.query}
|
|
286
333
|
contentPadding={contentPadding}
|
|
287
334
|
labelWidth={labelWidth}
|
|
288
335
|
trailingWidth={trailingWidth}
|
|
@@ -197,6 +197,13 @@ export function CommandBarMultiSelectBody({
|
|
|
197
197
|
onSelect={onSelect}
|
|
198
198
|
onToggle={onToggle}
|
|
199
199
|
bgColor={nativePaneChrome ? panelBg : paletteBg}
|
|
200
|
+
remoteLabel={route.title}
|
|
201
|
+
remoteScope="command-bar"
|
|
202
|
+
remoteMetadata={{
|
|
203
|
+
surface: "multi-select-picker",
|
|
204
|
+
routeKind: route.kind,
|
|
205
|
+
pickerId: route.pickerId,
|
|
206
|
+
}}
|
|
200
207
|
/>
|
|
201
208
|
<Box flexDirection="row" gap={1}>
|
|
202
209
|
<Button label="Done" variant="primary" onPress={onCommit} />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useRef, useState, type Dispatch, type RefObject, type SetStateAction } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState, type Dispatch, type RefObject, type SetStateAction } from "react";
|
|
2
2
|
import type { AppAction } from "../../../state/app/context";
|
|
3
3
|
import type { Command } from "../commands/registry";
|
|
4
4
|
import { resolveCommandBarMode } from "../view-model";
|
|
@@ -63,6 +63,15 @@ export function useCommandBarNavigationState({
|
|
|
63
63
|
const currentRouteRef = useRef<CommandBarRoute | null>(currentRoute);
|
|
64
64
|
currentRouteRef.current = currentRoute;
|
|
65
65
|
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (currentRouteRef.current) return;
|
|
68
|
+
if (initialQuery === rootQueryRef.current) return;
|
|
69
|
+
rootQueryRef.current = initialQuery;
|
|
70
|
+
setRootQueryValue(initialQuery);
|
|
71
|
+
setRootSelectedIdx(0);
|
|
72
|
+
setRootHoveredIdx(null);
|
|
73
|
+
}, [initialQuery]);
|
|
74
|
+
|
|
66
75
|
const closeAll = useCallback((options?: CloseCommandBarOptions) => {
|
|
67
76
|
if (options?.revertThemePreview !== false) {
|
|
68
77
|
restoreThemePreview();
|
|
@@ -129,6 +129,20 @@ describe("CommandBar", () => {
|
|
|
129
129
|
expect(testSetup.captureCharFrame()).not.toContain("Commands");
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
+
test("shows theme picker rows and commits a filtered light theme", async () => {
|
|
133
|
+
testSetup = await testRender(<CommandBarHarness query="TH light" live />, {
|
|
134
|
+
width: 80,
|
|
135
|
+
height: 24,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await testSetup.renderOnce();
|
|
139
|
+
expect(testSetup.captureCharFrame()).toContain("GitHub Light");
|
|
140
|
+
|
|
141
|
+
await clickFrameText("GitHub Light");
|
|
142
|
+
await waitForFrameToContain("theme:github-light");
|
|
143
|
+
expect(testSetup.captureCharFrame()).not.toContain("GitHub Light");
|
|
144
|
+
});
|
|
145
|
+
|
|
132
146
|
test("runs plugin command shortcuts from the root query", async () => {
|
|
133
147
|
const calls: string[] = [];
|
|
134
148
|
|
|
@@ -4,13 +4,14 @@ import {
|
|
|
4
4
|
useCallback,
|
|
5
5
|
useEffect,
|
|
6
6
|
useImperativeHandle,
|
|
7
|
-
useLayoutEffect,
|
|
8
7
|
useMemo,
|
|
9
8
|
useRef,
|
|
10
9
|
useState,
|
|
11
10
|
} from "react";
|
|
12
|
-
import { Box,
|
|
13
|
-
import { TextAttributes
|
|
11
|
+
import { Box, Text } from "../../ui";
|
|
12
|
+
import { TextAttributes } from "../../ui";
|
|
13
|
+
import { ListView, type ListViewItem } from "../ui";
|
|
14
|
+
import type { ListRowState } from "../ui/list-view";
|
|
14
15
|
import { getThemeIds, themes as themeRegistry } from "../../theme/themes";
|
|
15
16
|
import { truncateText } from "./view-model";
|
|
16
17
|
|
|
@@ -75,7 +76,6 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
|
|
|
75
76
|
onPreview,
|
|
76
77
|
onCommit,
|
|
77
78
|
}: ThemePickerProps, ref) {
|
|
78
|
-
const scrollRef = useRef<ScrollBoxRenderable | null>(null);
|
|
79
79
|
const previewTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
80
80
|
const pendingPreviewIdRef = useRef<string | null>(null);
|
|
81
81
|
const committedThemeIdRef = useRef(committedThemeId);
|
|
@@ -92,9 +92,20 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
|
|
|
92
92
|
const [selectedIndex, setSelectedIndex] = useState(() => (
|
|
93
93
|
Math.max(0, themes.findIndex((theme) => theme.id === committedThemeId))
|
|
94
94
|
));
|
|
95
|
-
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
|
96
95
|
const themesRef = useRef(themes);
|
|
97
96
|
const selectedIndexRef = useRef(selectedIndex);
|
|
97
|
+
const items = useMemo<ListViewItem[]>(() => themes.map((theme) => {
|
|
98
|
+
const current = theme.id === committedThemeId;
|
|
99
|
+
return {
|
|
100
|
+
id: theme.id,
|
|
101
|
+
label: theme.name,
|
|
102
|
+
detail: current ? "current" : "",
|
|
103
|
+
category: "Themes",
|
|
104
|
+
kind: "theme",
|
|
105
|
+
right: current ? "current" : "",
|
|
106
|
+
current,
|
|
107
|
+
};
|
|
108
|
+
}), [committedThemeId, themes]);
|
|
98
109
|
|
|
99
110
|
themesRef.current = themes;
|
|
100
111
|
selectedIndexRef.current = selectedIndex;
|
|
@@ -131,7 +142,6 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
|
|
|
131
142
|
if (nextIndex === selectedIndexRef.current) return false;
|
|
132
143
|
selectedIndexRef.current = nextIndex;
|
|
133
144
|
setSelectedIndex(nextIndex);
|
|
134
|
-
setHoveredIndex(null);
|
|
135
145
|
requestPreview(options[nextIndex]!.id);
|
|
136
146
|
return true;
|
|
137
147
|
}, [requestPreview]);
|
|
@@ -155,24 +165,10 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
|
|
|
155
165
|
const nextIndex = preferredIndex >= 0 ? preferredIndex : 0;
|
|
156
166
|
selectedIndexRef.current = nextIndex;
|
|
157
167
|
setSelectedIndex(nextIndex);
|
|
158
|
-
setHoveredIndex(null);
|
|
159
168
|
}, [committedThemeId, themes]);
|
|
160
169
|
|
|
161
170
|
useEffect(() => cancelPreview, [cancelPreview]);
|
|
162
171
|
|
|
163
|
-
useLayoutEffect(() => {
|
|
164
|
-
const scrollBox = scrollRef.current;
|
|
165
|
-
if (!scrollBox) return;
|
|
166
|
-
if (scrollBox.verticalScrollBar) scrollBox.verticalScrollBar.visible = false;
|
|
167
|
-
if (themes.length === 0) return;
|
|
168
|
-
const viewportHeight = Math.max(1, scrollBox.viewport?.height ?? height);
|
|
169
|
-
if (selectedIndex < scrollBox.scrollTop) {
|
|
170
|
-
scrollBox.scrollTo(selectedIndex);
|
|
171
|
-
} else if (selectedIndex >= scrollBox.scrollTop + viewportHeight) {
|
|
172
|
-
scrollBox.scrollTo(selectedIndex - viewportHeight + 1);
|
|
173
|
-
}
|
|
174
|
-
}, [height, selectedIndex, themes.length]);
|
|
175
|
-
|
|
176
172
|
const handleScroll = useCallback((event: ThemePickerScrollEvent) => {
|
|
177
173
|
event.stopPropagation();
|
|
178
174
|
event.preventDefault();
|
|
@@ -185,70 +181,86 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
|
|
|
185
181
|
}
|
|
186
182
|
}, [move]);
|
|
187
183
|
|
|
188
|
-
|
|
184
|
+
const handleSelect = useCallback((index: number) => {
|
|
185
|
+
const selected = themesRef.current[index];
|
|
186
|
+
if (!selected) return;
|
|
187
|
+
selectedIndexRef.current = index;
|
|
188
|
+
setSelectedIndex(index);
|
|
189
|
+
requestPreview(selected.id);
|
|
190
|
+
}, [requestPreview]);
|
|
191
|
+
|
|
192
|
+
const handleActivate = useCallback((item: ListViewItem, index: number) => {
|
|
193
|
+
const selected = themesRef.current[index] ?? themesRef.current.find((theme) => theme.id === item.id);
|
|
194
|
+
if (!selected) return;
|
|
195
|
+
selectedIndexRef.current = index;
|
|
196
|
+
setSelectedIndex(index);
|
|
197
|
+
cancelPreview();
|
|
198
|
+
onCommitRef.current(selected.id);
|
|
199
|
+
}, [cancelPreview]);
|
|
200
|
+
|
|
201
|
+
const renderRow = useCallback((item: ListViewItem, state: ListRowState) => {
|
|
202
|
+
const label = truncateText(item.label, labelWidth);
|
|
203
|
+
const trailing = item.current ? "current" : "";
|
|
189
204
|
return (
|
|
190
|
-
<Box
|
|
191
|
-
|
|
205
|
+
<Box
|
|
206
|
+
flexDirection="row"
|
|
207
|
+
height={1}
|
|
208
|
+
paddingX={contentPadding}
|
|
209
|
+
width="100%"
|
|
210
|
+
data-command-bar-row-selected={nativePaneChrome && state.selected ? "true" : undefined}
|
|
211
|
+
style={nativePaneChrome ? { borderRadius: 6 } : undefined}
|
|
212
|
+
>
|
|
213
|
+
<Box width={labelWidth}>
|
|
214
|
+
<Text
|
|
215
|
+
fg={state.selected ? paletteSelectedText : paletteText}
|
|
216
|
+
attributes={item.current ? TextAttributes.BOLD : undefined}
|
|
217
|
+
>
|
|
218
|
+
{label}
|
|
219
|
+
</Text>
|
|
220
|
+
</Box>
|
|
221
|
+
<Box width={trailingWidth}>
|
|
222
|
+
<Text fg={state.selected ? paletteSelectedText : paletteSubtleText}>
|
|
223
|
+
{truncateText(trailing, trailingWidth)}
|
|
224
|
+
</Text>
|
|
225
|
+
</Box>
|
|
192
226
|
</Box>
|
|
193
227
|
);
|
|
194
|
-
}
|
|
228
|
+
}, [
|
|
229
|
+
contentPadding,
|
|
230
|
+
labelWidth,
|
|
231
|
+
nativePaneChrome,
|
|
232
|
+
paletteSelectedText,
|
|
233
|
+
paletteSubtleText,
|
|
234
|
+
paletteText,
|
|
235
|
+
trailingWidth,
|
|
236
|
+
]);
|
|
195
237
|
|
|
196
238
|
return (
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
239
|
+
<ListView
|
|
240
|
+
items={items}
|
|
241
|
+
selectedIndex={selectedIndex}
|
|
200
242
|
height={height}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
{
|
|
204
|
-
|
|
205
|
-
{
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
onMouseOut={() => setHoveredIndex(null)}
|
|
224
|
-
onMouseDown={(event: any) => {
|
|
225
|
-
event.stopPropagation?.();
|
|
226
|
-
event.preventDefault?.();
|
|
227
|
-
selectedIndexRef.current = index;
|
|
228
|
-
setSelectedIndex(index);
|
|
229
|
-
cancelPreview();
|
|
230
|
-
onCommitRef.current(theme.id);
|
|
231
|
-
}}
|
|
232
|
-
{...(!nativePaneChrome ? { onMouseScroll: handleScroll } : {})}
|
|
233
|
-
data-command-bar-row-selected={nativePaneChrome && selected ? "true" : undefined}
|
|
234
|
-
style={nativePaneChrome ? { borderRadius: 6 } : undefined}
|
|
235
|
-
>
|
|
236
|
-
<Box width={labelWidth}>
|
|
237
|
-
<Text
|
|
238
|
-
fg={selected ? paletteSelectedText : paletteText}
|
|
239
|
-
attributes={current ? TextAttributes.BOLD : undefined}
|
|
240
|
-
>
|
|
241
|
-
{label}
|
|
242
|
-
</Text>
|
|
243
|
-
</Box>
|
|
244
|
-
<Box width={trailingWidth}>
|
|
245
|
-
<Text fg={selected ? paletteSelectedText : paletteSubtleText}>
|
|
246
|
-
{truncateText(trailing, trailingWidth)}
|
|
247
|
-
</Text>
|
|
248
|
-
</Box>
|
|
249
|
-
</Box>
|
|
250
|
-
);
|
|
251
|
-
})}
|
|
252
|
-
</ScrollBox>
|
|
243
|
+
scrollable
|
|
244
|
+
rowGap={0}
|
|
245
|
+
rowHeight={1}
|
|
246
|
+
surface="plain"
|
|
247
|
+
bgColor={nativePaneChrome ? panelBg : paletteBg}
|
|
248
|
+
selectedBgColor={paletteSelectedBg}
|
|
249
|
+
hoverBgColor={paletteHoverBg}
|
|
250
|
+
emptyMessage={truncateText("No themes match", queryDisplayWidth)}
|
|
251
|
+
showSelectedDescription={false}
|
|
252
|
+
onSelect={handleSelect}
|
|
253
|
+
onActivate={handleActivate}
|
|
254
|
+
onMouseScroll={!nativePaneChrome ? handleScroll : undefined}
|
|
255
|
+
renderRow={renderRow}
|
|
256
|
+
remoteLabel="Theme picker"
|
|
257
|
+
remoteScope="command-bar"
|
|
258
|
+
remoteItemKind="theme"
|
|
259
|
+
remoteItemCategory="Themes"
|
|
260
|
+
remoteMetadata={{
|
|
261
|
+
surface: "theme-picker",
|
|
262
|
+
filter: normalizedFilter,
|
|
263
|
+
}}
|
|
264
|
+
/>
|
|
253
265
|
);
|
|
254
266
|
}));
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isWorkflowTextField,
|
|
16
16
|
summarizeWorkflowFieldValue,
|
|
17
17
|
} from "../helpers";
|
|
18
|
+
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
18
19
|
import type {
|
|
19
20
|
CommandBarFieldValue,
|
|
20
21
|
CommandBarWorkflowField,
|
|
@@ -84,6 +85,34 @@ export function CommandBarWorkflowFieldRow({
|
|
|
84
85
|
onMoveFieldFocus(1);
|
|
85
86
|
}
|
|
86
87
|
};
|
|
88
|
+
const focusOrOpenField = () => {
|
|
89
|
+
onActiveTextareaSync(route);
|
|
90
|
+
onFieldFocus(field.id);
|
|
91
|
+
if (!isWorkflowTextField(field) && !useNativeSelect) {
|
|
92
|
+
onFieldPickerOpen(route, field);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
useRemoteUiNode({
|
|
97
|
+
role: "command-bar-field",
|
|
98
|
+
label: field.label,
|
|
99
|
+
disabled: route.pending,
|
|
100
|
+
actions: {
|
|
101
|
+
focus: focusOrOpenField,
|
|
102
|
+
open: !isWorkflowTextField(field) && !useNativeSelect ? focusOrOpenField : undefined,
|
|
103
|
+
submit: () => void onSubmit(route),
|
|
104
|
+
},
|
|
105
|
+
metadata: {
|
|
106
|
+
scope: "command-bar",
|
|
107
|
+
surface: "workflow",
|
|
108
|
+
routeTitle: route.title,
|
|
109
|
+
fieldId: field.id,
|
|
110
|
+
fieldType: field.type,
|
|
111
|
+
active,
|
|
112
|
+
value: summarizeWorkflowFieldValue(field, value),
|
|
113
|
+
description: fieldDescription,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
87
116
|
|
|
88
117
|
return (
|
|
89
118
|
<Box
|
|
@@ -93,11 +122,7 @@ export function CommandBarWorkflowFieldRow({
|
|
|
93
122
|
backgroundColor={fieldBg}
|
|
94
123
|
onMouseDown={(event: any) => {
|
|
95
124
|
event.stopPropagation?.();
|
|
96
|
-
|
|
97
|
-
onFieldFocus(field.id);
|
|
98
|
-
if (!isWorkflowTextField(field) && !useNativeSelect) {
|
|
99
|
-
onFieldPickerOpen(route, field);
|
|
100
|
-
}
|
|
125
|
+
focusOrOpenField();
|
|
101
126
|
}}
|
|
102
127
|
style={nativePaneChrome ? {
|
|
103
128
|
marginBottom: isLastField ? 8 : 10,
|
package/src/components/index.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
export { PriceSelectorDialog } from "./price-selector-dialog";
|
|
3
3
|
export { StaticChartSurface } from "./chart/static/chart/surface";
|
|
4
|
+
export {
|
|
5
|
+
buildMetricTreemapNavigationTiles,
|
|
6
|
+
findMetricTreemapNeighbor,
|
|
7
|
+
MetricTreemapSurface,
|
|
8
|
+
type MetricTreemapDirection,
|
|
9
|
+
type MetricTreemapItem,
|
|
10
|
+
} from "./metric-treemap";
|
|
4
11
|
export { SpeedometerGauge } from "./speedometer-gauge";
|
|
5
12
|
export type { SpeedometerSegment } from "./speedometer-gauge";
|
|
6
13
|
export { TickerListTableView } from "./ticker/list-table-view";
|
|
@@ -2,6 +2,7 @@ import { Box, Span, Text, TextAttributes, useUiCapabilities } from "../../../../
|
|
|
2
2
|
import { useRef } from "react";
|
|
3
3
|
import { colors, blendHex } from "../../../../theme/colors";
|
|
4
4
|
import { getShortcutHintWidth, ShortcutHint } from "../../../ui/shortcut-hint";
|
|
5
|
+
import { useRemoteUiNode } from "../../../../remote/semantic-tree";
|
|
5
6
|
import {
|
|
6
7
|
EMPTY_FOOTER,
|
|
7
8
|
hasPaneFooterContent,
|
|
@@ -50,6 +51,15 @@ function stopMouseEvent(event?: { stopPropagation?: () => void; preventDefault?:
|
|
|
50
51
|
|
|
51
52
|
function SegmentView({ segment }: { segment: PaneFooterSegment }) {
|
|
52
53
|
const interactive = !!segment.onPress && !segment.disabled;
|
|
54
|
+
useRemoteUiNode(interactive ? {
|
|
55
|
+
role: "pane-footer-segment",
|
|
56
|
+
label: segment.parts.map((part) => part.text).join(" "),
|
|
57
|
+
disabled: segment.disabled,
|
|
58
|
+
actions: {
|
|
59
|
+
press: () => segment.onPress?.(),
|
|
60
|
+
},
|
|
61
|
+
metadata: { id: segment.id },
|
|
62
|
+
} : null);
|
|
53
63
|
const attributes = segment.parts.some((part) => part.bold) || interactive ? TextAttributes.BOLD : 0;
|
|
54
64
|
const triggerMouseDownRef = useRef(false);
|
|
55
65
|
const startSegmentPress = (event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
|
|
@@ -93,6 +103,19 @@ function totalHintsWidth(hints: PaneHint[]): number {
|
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
function HintView({ hint, prefixSpace }: { hint: PaneHint; prefixSpace: boolean }) {
|
|
106
|
+
useRemoteUiNode({
|
|
107
|
+
role: "pane-hint",
|
|
108
|
+
label: `${hint.key}${hint.label}`,
|
|
109
|
+
disabled: hint.disabled,
|
|
110
|
+
actions: {
|
|
111
|
+
press: hint.onPress ? () => hint.onPress?.() : undefined,
|
|
112
|
+
},
|
|
113
|
+
metadata: {
|
|
114
|
+
id: hint.id,
|
|
115
|
+
key: hint.key,
|
|
116
|
+
label: hint.label,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
96
119
|
return (
|
|
97
120
|
<ShortcutHint
|
|
98
121
|
hotkey={hint.key}
|
|
@@ -1271,6 +1271,7 @@ describe("Shell", () => {
|
|
|
1271
1271
|
layouts: [{ name: "Default", layout: cloneLayout(mixedLayout) }],
|
|
1272
1272
|
}),
|
|
1273
1273
|
focusedPaneId: "ticker-detail:main",
|
|
1274
|
+
previousFocusedPaneId: "portfolio-list:main",
|
|
1274
1275
|
};
|
|
1275
1276
|
const actions: Array<any> = [];
|
|
1276
1277
|
|
|
@@ -1294,6 +1295,7 @@ describe("Shell", () => {
|
|
|
1294
1295
|
expect(updateLayout?.layout.instances.map((instance: { instanceId: string }) => instance.instanceId)).toEqual(["portfolio-list:main"]);
|
|
1295
1296
|
expect(updateLayout?.layout.floating).toEqual([]);
|
|
1296
1297
|
expect(updateLayout?.layout.dockRoot).toEqual({ kind: "pane", instanceId: "portfolio-list:main" });
|
|
1298
|
+
expect(updateLayout?.focusedPaneId).toBe("portfolio-list:main");
|
|
1297
1299
|
});
|
|
1298
1300
|
|
|
1299
1301
|
test("closes the focused floating pane with Cmd+W", async () => {
|
|
@@ -95,6 +95,7 @@ export function Shell({
|
|
|
95
95
|
const config = useAppSelector((state) => state.config);
|
|
96
96
|
const paneState = useAppSelector((state) => state.paneState);
|
|
97
97
|
const focusedPaneId = useAppSelector(selectFocusedPaneId);
|
|
98
|
+
const previousFocusedPaneId = useAppSelector((state) => state.previousFocusedPaneId);
|
|
98
99
|
const activePanel = useAppSelector((state) => state.activePanel);
|
|
99
100
|
const commandBarOpen = useAppSelector(selectCommandBarOpen);
|
|
100
101
|
const inputCaptured = useAppSelector((state) => state.inputCaptured);
|
|
@@ -154,15 +155,18 @@ export function Shell({
|
|
|
154
155
|
), [nativePaneChrome]);
|
|
155
156
|
const bounds = useMemo<LayoutBounds>(() => ({ x: 0, y: 0, width, height: contentHeight }), [contentHeight, width]);
|
|
156
157
|
|
|
157
|
-
const persistLayout = useCallback((nextLayout: LayoutConfig, options?: { pushHistory?: boolean }) => {
|
|
158
|
+
const persistLayout = useCallback((nextLayout: LayoutConfig, options?: { pushHistory?: boolean; focusedPaneId?: string | null }) => {
|
|
158
159
|
if (options?.pushHistory !== false) {
|
|
159
160
|
dispatch({ type: "PUSH_LAYOUT_HISTORY" });
|
|
160
161
|
}
|
|
161
|
-
|
|
162
|
+
const hasFocusTarget = !!options && Object.prototype.hasOwnProperty.call(options, "focusedPaneId");
|
|
163
|
+
dispatch(hasFocusTarget
|
|
164
|
+
? { type: "UPDATE_LAYOUT", layout: nextLayout, focusedPaneId: options.focusedPaneId ?? null }
|
|
165
|
+
: { type: "UPDATE_LAYOUT", layout: nextLayout });
|
|
162
166
|
scheduleConfigSave(syncConfigActiveLayoutState(
|
|
163
167
|
{ ...config, layout: nextLayout },
|
|
164
168
|
paneState,
|
|
165
|
-
focusedPaneId,
|
|
169
|
+
hasFocusTarget ? (options.focusedPaneId ?? null) : focusedPaneId,
|
|
166
170
|
activePanel,
|
|
167
171
|
));
|
|
168
172
|
}, [activePanel, config, dispatch, focusedPaneId, paneState]);
|
|
@@ -261,6 +265,7 @@ export function Shell({
|
|
|
261
265
|
nativePaneChrome,
|
|
262
266
|
paneMap,
|
|
263
267
|
persistLayout,
|
|
268
|
+
previousFocusedPaneId,
|
|
264
269
|
pluginRegistry,
|
|
265
270
|
rendererHost,
|
|
266
271
|
visibleLayout,
|
|
@@ -14,6 +14,15 @@ import type { LayoutConfig } from "../../../../types/config";
|
|
|
14
14
|
import type { RendererHost } from "../../../../ui";
|
|
15
15
|
import { capturePaneScreenshotPngBase64 } from "../../../../utils/dom-screenshot";
|
|
16
16
|
|
|
17
|
+
function removedFocusRestoreOptions(
|
|
18
|
+
layout: LayoutConfig,
|
|
19
|
+
currentFocusedPaneId: string | null,
|
|
20
|
+
paneId: string | null,
|
|
21
|
+
): { focusedPaneId: string } | undefined {
|
|
22
|
+
if (currentFocusedPaneId && isPaneInLayout(layout, currentFocusedPaneId)) return undefined;
|
|
23
|
+
return paneId && isPaneInLayout(layout, paneId) ? { focusedPaneId: paneId } : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
interface UseShellPaneActionsOptions {
|
|
18
27
|
closePaneMenu: () => void;
|
|
19
28
|
contentHeight: number;
|
|
@@ -22,7 +31,8 @@ interface UseShellPaneActionsOptions {
|
|
|
22
31
|
focusPane: (paneId: string) => void;
|
|
23
32
|
nativePaneChrome: boolean;
|
|
24
33
|
paneMap: Map<string, ResolvedPane>;
|
|
25
|
-
persistLayout: (nextLayout: LayoutConfig, options?: { pushHistory?: boolean }) => void;
|
|
34
|
+
persistLayout: (nextLayout: LayoutConfig, options?: { pushHistory?: boolean; focusedPaneId?: string | null }) => void;
|
|
35
|
+
previousFocusedPaneId: string | null;
|
|
26
36
|
pluginRegistry: PluginRegistry;
|
|
27
37
|
rendererHost: RendererHost;
|
|
28
38
|
visibleLayout: LayoutConfig;
|
|
@@ -38,6 +48,7 @@ export function useShellPaneActions({
|
|
|
38
48
|
nativePaneChrome,
|
|
39
49
|
paneMap,
|
|
40
50
|
persistLayout,
|
|
51
|
+
previousFocusedPaneId,
|
|
41
52
|
pluginRegistry,
|
|
42
53
|
rendererHost,
|
|
43
54
|
visibleLayout,
|
|
@@ -70,15 +81,17 @@ export function useShellPaneActions({
|
|
|
70
81
|
|
|
71
82
|
const closeFocusedPane = useCallback(() => {
|
|
72
83
|
if (!focusedPaneId || !isPaneInLayout(visibleLayout, focusedPaneId)) return false;
|
|
73
|
-
|
|
84
|
+
const nextLayout = removePane(visibleLayout, focusedPaneId);
|
|
85
|
+
persistLayout(nextLayout, removedFocusRestoreOptions(nextLayout, focusedPaneId, previousFocusedPaneId));
|
|
74
86
|
return true;
|
|
75
|
-
}, [focusedPaneId, persistLayout, visibleLayout]);
|
|
87
|
+
}, [focusedPaneId, persistLayout, previousFocusedPaneId, visibleLayout]);
|
|
76
88
|
|
|
77
89
|
const closeAllFloatingPanes = useCallback(() => {
|
|
78
90
|
if (visibleLayout.floating.length === 0) return false;
|
|
79
|
-
|
|
91
|
+
const nextLayout = removeFloatingPanes(visibleLayout);
|
|
92
|
+
persistLayout(nextLayout, removedFocusRestoreOptions(nextLayout, focusedPaneId, previousFocusedPaneId));
|
|
80
93
|
return true;
|
|
81
|
-
}, [persistLayout, visibleLayout]);
|
|
94
|
+
}, [focusedPaneId, persistLayout, previousFocusedPaneId, visibleLayout]);
|
|
82
95
|
|
|
83
96
|
const copyFocusedPaneScreenshot = useCallback(() => {
|
|
84
97
|
if (!focusedPaneId || !nativePaneChrome || !rendererHost.copyPngImage) return false;
|
|
@@ -118,8 +131,9 @@ export function useShellPaneActions({
|
|
|
118
131
|
}, [contentHeight, persistLayout, visibleLayout, width]);
|
|
119
132
|
|
|
120
133
|
const handleFloatingClose = useCallback((paneId: string) => {
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
const nextLayout = removePane(visibleLayout, paneId);
|
|
135
|
+
persistLayout(nextLayout, removedFocusRestoreOptions(nextLayout, focusedPaneId, previousFocusedPaneId));
|
|
136
|
+
}, [focusedPaneId, persistLayout, previousFocusedPaneId, visibleLayout]);
|
|
123
137
|
|
|
124
138
|
return {
|
|
125
139
|
closeAllFloatingPanes,
|