gloomberb 0.9.6 → 0.9.8
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/package.json +1 -1
- package/src/api-client/auth.ts +20 -0
- package/src/api-client/index.ts +48 -0
- package/src/api-client/types.ts +45 -0
- package/src/app/runtime/desktop-deeplink.test.ts +87 -0
- package/src/app/runtime/desktop-deeplink.ts +496 -0
- package/src/app.tsx +34 -0
- package/src/components/chart/bar-chart-renderer.test.ts +87 -12
- package/src/components/chart/bar-chart-renderer.ts +111 -55
- package/src/components/chart/static/bar-chart-surface.tsx +104 -20
- package/src/components/chart/stock-chart/footer.test.ts +65 -0
- package/src/components/chart/stock-chart/footer.ts +68 -13
- package/src/components/chart/stock-chart/resolved.tsx +18 -4
- package/src/components/command-bar/pane-templates/items.ts +11 -3
- package/src/components/command-bar/routes/root/results.ts +1 -1
- package/src/components/command-bar/surface/index.test.tsx +52 -0
- package/src/components/command-bar/workflow/ops.ts +81 -20
- package/src/components/index.ts +1 -0
- package/src/components/toggle-list.tsx +29 -73
- package/src/components/ui/button.tsx +4 -1
- package/src/components/ui/checkbox.tsx +76 -0
- package/src/components/ui/choice-dialog.tsx +15 -3
- package/src/components/ui/frame.tsx +1 -1
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/message-composer.tsx +4 -0
- package/src/components/ui/native-select.tsx +4 -2
- package/src/market-data/coordinator/events.test.ts +49 -0
- package/src/market-data/coordinator/events.ts +25 -4
- package/src/market-data/coordinator/index.test.ts +223 -0
- package/src/market-data/coordinator/index.ts +40 -3
- package/src/market-data/coordinator/quotes.ts +13 -5
- package/src/market-data/hooks.tsx +17 -13
- package/src/market-data/market/freshness.ts +58 -1
- package/src/market-data/quotes/freshness.test.ts +49 -1
- package/src/market-data/quotes/freshness.ts +8 -0
- package/src/market-data/quotes/resolution.test.ts +185 -0
- package/src/market-data/quotes/resolution.ts +94 -8
- package/src/market-data/request-types.test.ts +61 -1
- package/src/market-data/request-types.ts +42 -3
- package/src/pane-settings.test.ts +1 -0
- package/src/plugins/builtin/account-management/footer.ts +2 -5
- package/src/plugins/builtin/account-management/form-components.tsx +260 -24
- package/src/plugins/builtin/account-management/keyboard.ts +32 -2
- package/src/plugins/builtin/account-management/model.test.ts +99 -4
- package/src/plugins/builtin/account-management/model.ts +263 -3
- package/src/plugins/builtin/account-management/pane.tsx +784 -155
- package/src/plugins/builtin/chat/chat.test.tsx +125 -0
- package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -21
- package/src/plugins/builtin/chat/content/composer-cursor.ts +29 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +22 -24
- package/src/plugins/builtin/chat/content/composer.tsx +84 -1
- package/src/plugins/builtin/chat/content/index.tsx +154 -16
- package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -1
- package/src/plugins/builtin/chat/content/mentions.test.ts +74 -0
- package/src/plugins/builtin/chat/content/mentions.ts +88 -0
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +264 -0
- package/src/plugins/builtin/chat/content/shortcuts.ts +47 -1
- package/src/plugins/builtin/chat/content/snapshot.ts +7 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +0 -4
- package/src/plugins/builtin/chat/message/profile-popover.test.tsx +36 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +124 -20
- package/src/plugins/builtin/chat/sidebar.test.tsx +69 -2
- package/src/plugins/builtin/chat/sidebar.tsx +26 -2
- package/src/plugins/builtin/chat/test-harness.tsx +21 -11
- package/src/plugins/builtin/cloud/plugin.tsx +7 -0
- package/src/plugins/builtin/correlation/index.tsx +4 -1
- package/src/plugins/builtin/correlation/relationship/controls.tsx +8 -2
- package/src/plugins/builtin/correlation/relationship/model.ts +22 -2
- package/src/plugins/builtin/correlation/relationship/pane.tsx +9 -3
- package/src/plugins/builtin/portfolio-list/index.test.ts +102 -1
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
- package/src/plugins/builtin/portfolio-list/pane/data.test.ts +54 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +62 -1
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +414 -8
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +24 -32
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +131 -22
- package/src/plugins/builtin/portfolio-list/settings.ts +36 -1
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +43 -7
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +415 -64
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +23 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +8 -1
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +3 -1
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +95 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +2 -0
- package/src/plugins/ibkr/gateway/market-data.ts +12 -5
- package/src/plugins/ibkr/gateway/service/index.test.ts +31 -0
- package/src/plugins/registry/context.ts +15 -0
- package/src/plugins/registry/index.ts +31 -0
- package/src/renderers/electrobun/bun/index.ts +42 -0
- package/src/renderers/electrobun/shared/protocol.ts +5 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +29 -0
- package/src/renderers/electrobun/view/desktop/controls.tsx +135 -4
- package/src/renderers/electrobun/view/desktop/list-view.tsx +38 -25
- package/src/renderers/electrobun/view/desktop-deeplink-bridge.ts +10 -0
- package/src/renderers/electrobun/view/host/box.tsx +0 -2
- package/src/renderers/electrobun/view/host/scroll-box.tsx +1 -2
- package/src/renderers/electrobun/view/host/text.tsx +1 -2
- package/src/renderers/electrobun/view/main.tsx +3 -0
- package/src/renderers/electrobun/view/ui-host.tsx +2 -0
- package/src/sources/provider-router/batches.ts +11 -3
- package/src/sources/provider-router/cached-financials.test.ts +123 -0
- package/src/sources/provider-router/financial-routes.ts +55 -5
- package/src/sources/provider-router/financials.test.ts +49 -0
- package/src/sources/provider-router/financials.ts +40 -3
- package/src/sources/provider-router/index.test.ts +147 -0
- package/src/sources/provider-router/primary.ts +17 -3
- package/src/sources/provider-router/streaming.ts +6 -1
- package/src/sources/sec-edgar.test.ts +42 -0
- package/src/sources/sec-edgar.ts +1 -2
- package/src/sources/yahoo-finance/mappers.test.ts +29 -0
- package/src/sources/yahoo-finance/mappers.ts +2 -1
- package/src/sources/yahoo-finance/requests.ts +2 -1
- package/src/sources/yahoo-finance/snapshots.ts +13 -2
- package/src/sources/yahoo-finance.ts +6 -2
- package/src/state/hooks/quote-streaming.test.ts +14 -1
- package/src/state/hooks/quote-streaming.ts +5 -4
- package/src/sync/controller.ts +296 -0
- package/src/sync/core-contributors.test.ts +251 -0
- package/src/sync/core-contributors.ts +400 -0
- package/src/sync/profile-analytics.ts +39 -0
- package/src/sync/react.ts +53 -0
- package/src/sync/types.ts +77 -0
- package/src/types/config.ts +1 -0
- package/src/types/desktop-deeplink.ts +7 -0
- package/src/types/plugin.ts +3 -0
- package/src/ui/host.tsx +14 -1
- package/src/ui/index.tsx +1 -0
- package/src/utils/exchanges.test.ts +11 -1
- package/src/utils/exchanges.ts +5 -1
- package/src/version.ts +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useMemo, type Dispatch, type MutableRefObject, type SetStateAction } from "react";
|
|
2
|
-
import { formatCompact } from "../../../utils/format";
|
|
2
|
+
import { formatCompact, formatPercentRaw } from "../../../utils/format";
|
|
3
3
|
import { formatMarketPriceWithCurrency } from "../../../market-data/market/format";
|
|
4
4
|
import { usePaneFooter, type PaneFooterSegment, type PaneHint } from "../../layout/pane/footer";
|
|
5
|
+
import { priceColor } from "../../../theme/colors";
|
|
5
6
|
import type { PricePoint } from "../../../types/financials";
|
|
6
7
|
import type { ProjectedChartPoint } from "../core/data";
|
|
7
8
|
import type { DateWindowRange } from "../core/controller";
|
|
@@ -24,8 +25,53 @@ import {
|
|
|
24
25
|
type StockChartViewportState,
|
|
25
26
|
} from "./viewport";
|
|
26
27
|
|
|
27
|
-
interface
|
|
28
|
+
interface StockChartOhlcReadout extends ProjectedChartPoint {
|
|
29
|
+
changePercent: number | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function summarizeOhlcWindow(points: readonly ProjectedChartPoint[]): StockChartOhlcReadout | null {
|
|
33
|
+
const first = points[0];
|
|
34
|
+
const last = points.at(-1);
|
|
35
|
+
if (!first || !last) return null;
|
|
36
|
+
|
|
37
|
+
let high = first.high;
|
|
38
|
+
let low = first.low;
|
|
39
|
+
let volume = 0;
|
|
40
|
+
for (const point of points) {
|
|
41
|
+
high = Math.max(high, point.high);
|
|
42
|
+
low = Math.min(low, point.low);
|
|
43
|
+
volume += point.volume;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
date: last.date,
|
|
48
|
+
open: first.open,
|
|
49
|
+
high,
|
|
50
|
+
low,
|
|
51
|
+
close: last.close,
|
|
52
|
+
volume,
|
|
53
|
+
changePercent: Number.isFinite(first.open) && first.open !== 0
|
|
54
|
+
? ((last.close - first.open) / first.open) * 100
|
|
55
|
+
: null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveStockChartFooterOhlcReadout({
|
|
60
|
+
activePoint,
|
|
61
|
+
hasDisplayCursor,
|
|
62
|
+
points,
|
|
63
|
+
}: {
|
|
28
64
|
activePoint: ProjectedChartPoint | null;
|
|
65
|
+
hasDisplayCursor: boolean;
|
|
66
|
+
points: readonly ProjectedChartPoint[];
|
|
67
|
+
}): StockChartOhlcReadout | null {
|
|
68
|
+
if (hasDisplayCursor) {
|
|
69
|
+
return activePoint ? { ...activePoint, changePercent: null } : null;
|
|
70
|
+
}
|
|
71
|
+
return summarizeOhlcWindow(points);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface StockChartFooterOptions {
|
|
29
75
|
activePreset: TimeRange | null;
|
|
30
76
|
baseDateBounds: DateWindowRange | null;
|
|
31
77
|
boundsHistoryDates: Date[];
|
|
@@ -37,6 +83,7 @@ interface StockChartFooterOptions {
|
|
|
37
83
|
history: PricePoint[];
|
|
38
84
|
manualMinimumSpanMs: number | null;
|
|
39
85
|
navigableDateWindow: DateWindowRange | null;
|
|
86
|
+
ohlcReadout: StockChartOhlcReadout | null;
|
|
40
87
|
pendingAutoWindowRef: MutableRefObject<DateWindowRange | null>;
|
|
41
88
|
pendingCanonicalResetRef: MutableRefObject<number>;
|
|
42
89
|
persistRenderMode: (mode: ChartRenderMode) => void;
|
|
@@ -50,7 +97,6 @@ interface StockChartFooterOptions {
|
|
|
50
97
|
setRenderedAutoView: Dispatch<SetStateAction<AutoRenderedView | null>>;
|
|
51
98
|
setResolution: (resolution: ChartResolution) => void;
|
|
52
99
|
setViewState: Dispatch<SetStateAction<StockChartViewportState>>;
|
|
53
|
-
showOhlcSummary: boolean;
|
|
54
100
|
updateDisplayCursorTarget: (next: DisplayCursorState, motionKind: ChartCursorMotionKind) => void;
|
|
55
101
|
visibleDateWindow: DateWindowRange | null;
|
|
56
102
|
visiblePriceRange: number | undefined;
|
|
@@ -58,7 +104,6 @@ interface StockChartFooterOptions {
|
|
|
58
104
|
}
|
|
59
105
|
|
|
60
106
|
export function useStockChartFooter({
|
|
61
|
-
activePoint,
|
|
62
107
|
activePreset,
|
|
63
108
|
baseDateBounds,
|
|
64
109
|
boundsHistoryDates,
|
|
@@ -70,6 +115,7 @@ export function useStockChartFooter({
|
|
|
70
115
|
history,
|
|
71
116
|
manualMinimumSpanMs,
|
|
72
117
|
navigableDateWindow,
|
|
118
|
+
ohlcReadout,
|
|
73
119
|
pendingAutoWindowRef,
|
|
74
120
|
pendingCanonicalResetRef,
|
|
75
121
|
persistRenderMode,
|
|
@@ -83,7 +129,6 @@ export function useStockChartFooter({
|
|
|
83
129
|
setRenderedAutoView,
|
|
84
130
|
setResolution,
|
|
85
131
|
setViewState,
|
|
86
|
-
showOhlcSummary,
|
|
87
132
|
updateDisplayCursorTarget,
|
|
88
133
|
visibleDateWindow,
|
|
89
134
|
visiblePriceRange,
|
|
@@ -196,7 +241,7 @@ export function useStockChartFooter({
|
|
|
196
241
|
]);
|
|
197
242
|
|
|
198
243
|
const chartFooterInfo = useMemo<PaneFooterSegment[]>(() => {
|
|
199
|
-
if (compact || !
|
|
244
|
+
if (compact || !ohlcReadout) return [];
|
|
200
245
|
|
|
201
246
|
const formatPrice = (value: number) => formatMarketPriceWithCurrency(value, chartCurrency, {
|
|
202
247
|
assetCategory: chartAssetCategory,
|
|
@@ -210,16 +255,27 @@ export function useStockChartFooter({
|
|
|
210
255
|
? []
|
|
211
256
|
: [
|
|
212
257
|
{ text: "O", tone: "label" as const },
|
|
213
|
-
{ text: formatPrice(
|
|
258
|
+
{ text: formatPrice(ohlcReadout.open), tone: "value" as const },
|
|
214
259
|
]),
|
|
215
260
|
{ text: "H", tone: "label" },
|
|
216
|
-
{ text: formatPrice(
|
|
261
|
+
{ text: formatPrice(ohlcReadout.high), tone: "value" },
|
|
217
262
|
{ text: "L", tone: "label" },
|
|
218
|
-
{ text: formatPrice(
|
|
263
|
+
{ text: formatPrice(ohlcReadout.low), tone: "value" },
|
|
219
264
|
{ text: "C", tone: "label" },
|
|
220
|
-
{ text: formatPrice(
|
|
265
|
+
{ text: formatPrice(ohlcReadout.close), tone: "value" },
|
|
266
|
+
...(ohlcReadout.changePercent === null
|
|
267
|
+
? []
|
|
268
|
+
: [
|
|
269
|
+
{ text: "%", tone: "label" as const },
|
|
270
|
+
{
|
|
271
|
+
text: formatPercentRaw(ohlcReadout.changePercent),
|
|
272
|
+
tone: "value" as const,
|
|
273
|
+
color: priceColor(ohlcReadout.changePercent),
|
|
274
|
+
bold: true,
|
|
275
|
+
},
|
|
276
|
+
]),
|
|
221
277
|
{ text: "V", tone: "label" },
|
|
222
|
-
{ text: formatCompact(
|
|
278
|
+
{ text: formatCompact(ohlcReadout.volume), tone: "value" },
|
|
223
279
|
];
|
|
224
280
|
|
|
225
281
|
return [{
|
|
@@ -227,12 +283,11 @@ export function useStockChartFooter({
|
|
|
227
283
|
parts,
|
|
228
284
|
}];
|
|
229
285
|
}, [
|
|
230
|
-
activePoint,
|
|
231
286
|
chartAssetCategory,
|
|
232
287
|
chartCurrency,
|
|
233
288
|
compact,
|
|
289
|
+
ohlcReadout,
|
|
234
290
|
projectionMode,
|
|
235
|
-
showOhlcSummary,
|
|
236
291
|
visiblePriceRange,
|
|
237
292
|
]);
|
|
238
293
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { memo, type ComponentProps } from "react";
|
|
2
|
-
import { useStockChartFooter } from "./footer";
|
|
1
|
+
import { memo, useMemo, type ComponentProps } from "react";
|
|
2
|
+
import { resolveStockChartFooterOhlcReadout, useStockChartFooter } from "./footer";
|
|
3
3
|
import { useStockChartKeyboardShortcuts } from "./keyboard";
|
|
4
4
|
import { useStockChartControlRenderInvalidation } from "./rendering/invalidation";
|
|
5
5
|
import { useStockChartRenderOutput } from "./rendering/output";
|
|
@@ -139,9 +139,23 @@ function useResolvedStockChartControls(
|
|
|
139
139
|
} = runtime;
|
|
140
140
|
const { output, status } = render;
|
|
141
141
|
const manualMinimumSpanMs = getChartResolutionStepMs(dataRuntime.renderedResolution);
|
|
142
|
+
const ohlcReadout = useMemo(
|
|
143
|
+
() => output.showOhlcSummary
|
|
144
|
+
? resolveStockChartFooterOhlcReadout({
|
|
145
|
+
activePoint: output.activePoint,
|
|
146
|
+
hasDisplayCursor: output.hasDisplayCursor,
|
|
147
|
+
points: projectionModel.projection.points,
|
|
148
|
+
})
|
|
149
|
+
: null,
|
|
150
|
+
[
|
|
151
|
+
output.activePoint,
|
|
152
|
+
output.hasDisplayCursor,
|
|
153
|
+
output.showOhlcSummary,
|
|
154
|
+
projectionModel.projection.points,
|
|
155
|
+
],
|
|
156
|
+
);
|
|
142
157
|
|
|
143
158
|
useStockChartFooter({
|
|
144
|
-
activePoint: output.activePoint,
|
|
145
159
|
activePreset: viewportRuntime.activePreset,
|
|
146
160
|
baseDateBounds: dataRuntime.baseDateBounds,
|
|
147
161
|
boundsHistoryDates: dataRuntime.boundsHistoryDates,
|
|
@@ -153,6 +167,7 @@ function useResolvedStockChartControls(
|
|
|
153
167
|
history: dataRuntime.history,
|
|
154
168
|
manualMinimumSpanMs,
|
|
155
169
|
navigableDateWindow: dataRuntime.navigableDateWindow,
|
|
170
|
+
ohlcReadout,
|
|
156
171
|
pendingAutoWindowRef: interactionRuntime.pendingAutoWindowRef,
|
|
157
172
|
pendingCanonicalResetRef: interactionRuntime.pendingCanonicalResetRef,
|
|
158
173
|
persistRenderMode: viewportRuntime.persistRenderMode,
|
|
@@ -166,7 +181,6 @@ function useResolvedStockChartControls(
|
|
|
166
181
|
setRenderedAutoView: dataRuntime.setRenderedAutoView,
|
|
167
182
|
setResolution: viewportRuntime.setResolution,
|
|
168
183
|
setViewState: settings.setViewState,
|
|
169
|
-
showOhlcSummary: output.showOhlcSummary,
|
|
170
184
|
updateDisplayCursorTarget: interactionRuntime.updateDisplayCursorTarget,
|
|
171
185
|
visibleDateWindow: dataRuntime.visibleDateWindow,
|
|
172
186
|
visiblePriceRange: output.visiblePriceRange,
|
|
@@ -139,6 +139,14 @@ export function buildPaneTemplateItem(options: {
|
|
|
139
139
|
.join(" ")
|
|
140
140
|
: null;
|
|
141
141
|
const arg = options.createOptions?.arg;
|
|
142
|
+
const searchText = [
|
|
143
|
+
options.template.keywords?.join(" ") || "",
|
|
144
|
+
displayLabel,
|
|
145
|
+
options.template.label,
|
|
146
|
+
options.template.paneId,
|
|
147
|
+
shortcutLabel || "",
|
|
148
|
+
pluginName || "",
|
|
149
|
+
].filter(Boolean).join(" ");
|
|
142
150
|
|
|
143
151
|
const action = () => {
|
|
144
152
|
if (
|
|
@@ -163,7 +171,7 @@ export function buildPaneTemplateItem(options: {
|
|
|
163
171
|
kind: "action",
|
|
164
172
|
right: options.showShortcut ? options.template.shortcut?.prefix : undefined,
|
|
165
173
|
shortcutQuery: options.template.shortcut?.prefix,
|
|
166
|
-
searchText
|
|
174
|
+
searchText,
|
|
167
175
|
action,
|
|
168
176
|
};
|
|
169
177
|
}
|
|
@@ -187,7 +195,7 @@ export function buildPaneShortcutItems(options: {
|
|
|
187
195
|
}));
|
|
188
196
|
|
|
189
197
|
return options.filterQuery
|
|
190
|
-
? fuzzyFilter(items, options.filterQuery, (item) => `${item.label} ${item.
|
|
198
|
+
? fuzzyFilter(items, options.filterQuery, (item) => `${item.label} ${item.searchText || ""} ${item.detail} ${item.right || ""}`)
|
|
191
199
|
: items;
|
|
192
200
|
}
|
|
193
201
|
|
|
@@ -201,6 +209,6 @@ export function buildNonShortcutPaneTemplateItems(options: {
|
|
|
201
209
|
.map((template) => options.createItem(template, { category: "Panes" }));
|
|
202
210
|
|
|
203
211
|
return options.filterQuery
|
|
204
|
-
? fuzzyFilter(items, options.filterQuery, (item) => `${item.label} ${item.
|
|
212
|
+
? fuzzyFilter(items, options.filterQuery, (item) => `${item.label} ${item.searchText || ""} ${item.detail} ${item.right || ""}`)
|
|
205
213
|
: items;
|
|
206
214
|
}
|
|
@@ -196,7 +196,7 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
|
|
|
196
196
|
...tickerActionItems(),
|
|
197
197
|
...pluginCommandItems(),
|
|
198
198
|
];
|
|
199
|
-
items.push(...fuzzyFilter(allItems, rootQuery, (item) => `${item.label} ${item.
|
|
199
|
+
items.push(...fuzzyFilter(allItems, rootQuery, (item) => `${item.label} ${item.searchText || ""} ${item.detail} ${item.right || ""}`));
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
return { items, initialIdx };
|
|
@@ -6,6 +6,7 @@ import type { CommandDef, CommandShortcutArgContext, PaneTemplateCreateOptions,
|
|
|
6
6
|
import {
|
|
7
7
|
CommandBarHarness,
|
|
8
8
|
createCommandBarTestControls,
|
|
9
|
+
emitKeypress,
|
|
9
10
|
expectSingleBackControl,
|
|
10
11
|
makeDataProvider,
|
|
11
12
|
makeTicker,
|
|
@@ -26,6 +27,11 @@ type MutableCommandRegistry = {
|
|
|
26
27
|
commands: ReadonlyMap<string, CommandDef>;
|
|
27
28
|
};
|
|
28
29
|
|
|
30
|
+
type MutablePaneRegistry = {
|
|
31
|
+
panes: ReadonlyMap<string, unknown>;
|
|
32
|
+
paneTemplates: ReadonlyMap<string, unknown>;
|
|
33
|
+
};
|
|
34
|
+
|
|
29
35
|
const DEFAULT_ALERT_OPTIONS = [
|
|
30
36
|
{ label: "Above", value: "above" },
|
|
31
37
|
{ label: "Below", value: "below" },
|
|
@@ -70,6 +76,10 @@ function registerAlertCommand(
|
|
|
70
76
|
});
|
|
71
77
|
}
|
|
72
78
|
|
|
79
|
+
function mutablePaneRegistryMap(map: ReadonlyMap<string, unknown>): Map<string, unknown> {
|
|
80
|
+
return map as Map<string, unknown>;
|
|
81
|
+
}
|
|
82
|
+
|
|
73
83
|
describe("CommandBar", () => {
|
|
74
84
|
test("renders the default layout with opencode-style chrome", async () => {
|
|
75
85
|
testSetup = await testRender(<CommandBarHarness query="" selectedTicker="AAPL" />, {
|
|
@@ -129,6 +139,48 @@ describe("CommandBar", () => {
|
|
|
129
139
|
expect(testSetup.captureCharFrame()).not.toContain("Commands");
|
|
130
140
|
});
|
|
131
141
|
|
|
142
|
+
test("shows one account management result when searching profile", async () => {
|
|
143
|
+
const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
|
|
144
|
+
|
|
145
|
+
testSetup = await testRender(<CommandBarHarness
|
|
146
|
+
query="profile"
|
|
147
|
+
live
|
|
148
|
+
configurePluginRegistry={(pluginRegistry) => {
|
|
149
|
+
const registry = pluginRegistry as MutablePaneRegistry;
|
|
150
|
+
mutablePaneRegistryMap(registry.panes).set("account-management", {
|
|
151
|
+
id: "account-management",
|
|
152
|
+
name: "Account Management",
|
|
153
|
+
component: () => null,
|
|
154
|
+
defaultPosition: "right",
|
|
155
|
+
defaultMode: "floating",
|
|
156
|
+
});
|
|
157
|
+
mutablePaneRegistryMap(registry.paneTemplates).set("account-management-pane", {
|
|
158
|
+
id: "account-management-pane",
|
|
159
|
+
paneId: "account-management",
|
|
160
|
+
label: "Account Management",
|
|
161
|
+
description: "Edit your Gloom Cloud profile, password, and public portfolio sharing settings",
|
|
162
|
+
keywords: ["account", "profile", "cloud", "acm", "password", "settings"],
|
|
163
|
+
shortcut: { prefix: "ACM" },
|
|
164
|
+
});
|
|
165
|
+
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
166
|
+
created.push({ templateId, options });
|
|
167
|
+
};
|
|
168
|
+
}}
|
|
169
|
+
/>, {
|
|
170
|
+
width: 80,
|
|
171
|
+
height: 24,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
await testSetup.renderOnce();
|
|
175
|
+
const frame = await waitForFrameToContain("Account Management");
|
|
176
|
+
expect(frame).not.toMatch(/\n\s*Profile\s*(?:\n|$)/);
|
|
177
|
+
expect(frame.indexOf("Account Management")).toBeLessThan(frame.indexOf("Add Broker Account"));
|
|
178
|
+
|
|
179
|
+
await emitKeypress(testSetup, { name: "return", sequence: "\r" });
|
|
180
|
+
|
|
181
|
+
expect(created).toEqual([{ templateId: "account-management-pane", options: undefined }]);
|
|
182
|
+
});
|
|
183
|
+
|
|
132
184
|
test("shows theme picker rows and commits a filtered light theme", async () => {
|
|
133
185
|
testSetup = await testRender(<CommandBarHarness query="TH light" live />, {
|
|
134
186
|
width: 80,
|
|
@@ -6,11 +6,22 @@ import { formatTickerListInput } from "../../../tickers/list";
|
|
|
6
6
|
import { updatePaneInstance, setPaneSettings } from "../../../pane-settings";
|
|
7
7
|
import { TICKER_RESEARCH_PANE_ID } from "../../../types/config";
|
|
8
8
|
import { cleanPortfolioPaneSettings, resolvePortfolioPaneCollectionId } from "../../../plugins/builtin/portfolio-list/settings";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_RELATIONSHIP_SECOND_SYMBOL,
|
|
11
|
+
RELATIONSHIP_GRAPH_PANE_ID,
|
|
12
|
+
buildRelationshipGraphPaneTitle,
|
|
13
|
+
} from "../../../plugins/builtin/correlation/relationship/model";
|
|
9
14
|
import {
|
|
10
15
|
buildComparisonChartPaneTitle,
|
|
11
16
|
COMPARISON_CHART_PANE_ID,
|
|
12
17
|
MIN_COMPARISON_CHART_SYMBOLS,
|
|
13
18
|
} from "../../../plugins/builtin/comparison-chart";
|
|
19
|
+
import {
|
|
20
|
+
FUNDAMENTAL_GRAPH_PANE_ID,
|
|
21
|
+
graphKindFromSettings,
|
|
22
|
+
graphShortcutForKind,
|
|
23
|
+
graphTemplateTitle,
|
|
24
|
+
} from "../../../plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings";
|
|
14
25
|
import { buildQuoteMonitorPaneTitle } from "../../../plugins/builtin/ticker-detail/settings";
|
|
15
26
|
import { getPaneTemplateDisplayLabel } from "../pane-templates/items";
|
|
16
27
|
import {
|
|
@@ -54,6 +65,30 @@ interface ApplyPaneSettingDeps extends SharedWorkflowDeps {
|
|
|
54
65
|
persistLayout: (layout: LayoutConfig, options?: { pushHistory?: boolean }) => void;
|
|
55
66
|
}
|
|
56
67
|
|
|
68
|
+
function updateTickerListPane(
|
|
69
|
+
layout: LayoutConfig,
|
|
70
|
+
targetId: string,
|
|
71
|
+
options: {
|
|
72
|
+
title: string;
|
|
73
|
+
symbols: readonly string[];
|
|
74
|
+
primarySymbol?: string;
|
|
75
|
+
settings?: Record<string, unknown>;
|
|
76
|
+
},
|
|
77
|
+
): LayoutConfig {
|
|
78
|
+
const symbols = [...options.symbols];
|
|
79
|
+
return updatePaneInstance(layout, targetId, (instance) => ({
|
|
80
|
+
...instance,
|
|
81
|
+
title: options.title,
|
|
82
|
+
...(options.primarySymbol ? { binding: { kind: "fixed", symbol: options.primarySymbol } as PaneBinding } : {}),
|
|
83
|
+
settings: {
|
|
84
|
+
...(instance.settings ?? {}),
|
|
85
|
+
symbols,
|
|
86
|
+
symbolsText: formatTickerListInput(symbols),
|
|
87
|
+
...(options.settings ?? {}),
|
|
88
|
+
},
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
|
|
57
92
|
async function resolvePaneTemplateOptions(
|
|
58
93
|
template: PaneTemplateDef,
|
|
59
94
|
options: PaneTemplateCreateOptions | undefined,
|
|
@@ -180,19 +215,50 @@ export async function applyPaneSettingFieldValue(
|
|
|
180
215
|
const rawQuery = typeof value === "string" ? value.trim() : "";
|
|
181
216
|
const symbols = await resolveTickerListInput(rawQuery, null, deps);
|
|
182
217
|
const primarySymbol = symbols[0]!;
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
const nextLayout = updatePaneInstance(state.config.layout, targetId, (instance) => ({
|
|
186
|
-
...instance,
|
|
218
|
+
const nextLayout = updateTickerListPane(state.config.layout, targetId, {
|
|
187
219
|
title: buildQuoteMonitorPaneTitle(symbols),
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
220
|
+
symbols,
|
|
221
|
+
primarySymbol,
|
|
222
|
+
settings: { symbol: primarySymbol },
|
|
223
|
+
});
|
|
224
|
+
deps.persistLayout(nextLayout, { pushHistory: shouldPushHistory });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (descriptor.pane.paneId === FUNDAMENTAL_GRAPH_PANE_ID && field.key === "symbolsText") {
|
|
229
|
+
const rawInput = typeof value === "string" ? value : "";
|
|
230
|
+
const symbols = await resolveTickerListInput(
|
|
231
|
+
rawInput,
|
|
232
|
+
descriptor.context.activeCollectionId,
|
|
233
|
+
deps,
|
|
234
|
+
);
|
|
235
|
+
const primarySymbol = symbols[0]!;
|
|
236
|
+
const chartKind = graphKindFromSettings(descriptor.context.settings, "fundamental");
|
|
237
|
+
const nextLayout = updateTickerListPane(state.config.layout, targetId, {
|
|
238
|
+
title: graphTemplateTitle(graphShortcutForKind(chartKind), symbols),
|
|
239
|
+
symbols,
|
|
240
|
+
primarySymbol,
|
|
241
|
+
});
|
|
242
|
+
deps.persistLayout(nextLayout, { pushHistory: shouldPushHistory });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (descriptor.pane.paneId === RELATIONSHIP_GRAPH_PANE_ID && field.key === "symbolsText") {
|
|
247
|
+
const rawInput = typeof value === "string" ? value : "";
|
|
248
|
+
const symbols = await resolveTickerListInput(
|
|
249
|
+
rawInput,
|
|
250
|
+
descriptor.context.activeCollectionId,
|
|
251
|
+
deps,
|
|
252
|
+
);
|
|
253
|
+
if (symbols.length > 2) {
|
|
254
|
+
throw new Error("Enter one or two tickers.");
|
|
255
|
+
}
|
|
256
|
+
const pair: [string, string] = [symbols[0]!, symbols[1] ?? DEFAULT_RELATIONSHIP_SECOND_SYMBOL];
|
|
257
|
+
const nextLayout = updateTickerListPane(state.config.layout, targetId, {
|
|
258
|
+
title: buildRelationshipGraphPaneTitle(pair),
|
|
259
|
+
symbols: pair,
|
|
260
|
+
primarySymbol: pair[0],
|
|
261
|
+
});
|
|
196
262
|
deps.persistLayout(nextLayout, { pushHistory: shouldPushHistory });
|
|
197
263
|
return;
|
|
198
264
|
}
|
|
@@ -207,15 +273,10 @@ export async function applyPaneSettingFieldValue(
|
|
|
207
273
|
if (symbols.length < MIN_COMPARISON_CHART_SYMBOLS) {
|
|
208
274
|
throw new Error(`Enter at least ${MIN_COMPARISON_CHART_SYMBOLS} tickers.`);
|
|
209
275
|
}
|
|
210
|
-
const nextLayout =
|
|
211
|
-
...instance,
|
|
276
|
+
const nextLayout = updateTickerListPane(state.config.layout, targetId, {
|
|
212
277
|
title: buildComparisonChartPaneTitle(symbols),
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
symbols,
|
|
216
|
-
symbolsText: formatTickerListInput(symbols),
|
|
217
|
-
},
|
|
218
|
-
}));
|
|
278
|
+
symbols,
|
|
279
|
+
});
|
|
219
280
|
deps.persistLayout(nextLayout, { pushHistory: shouldPushHistory });
|
|
220
281
|
return;
|
|
221
282
|
}
|
package/src/components/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ export { PaneFooterScope, usePaneFooter } from "./layout/pane/footer";
|
|
|
25
25
|
export type { PaneFooterSegment, PaneHint } from "./layout/pane/footer";
|
|
26
26
|
export { useExternalLinkFooter } from "./use-external-link-footer";
|
|
27
27
|
export { Button } from "./ui/button";
|
|
28
|
+
export { Checkbox } from "./ui/checkbox";
|
|
28
29
|
export { ConfirmDialog } from "./ui/confirm-dialog";
|
|
29
30
|
export { ChoiceDialog } from "./ui/choice-dialog";
|
|
30
31
|
export type { ChoiceDialogChoice } from "./ui/choice-dialog";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Box,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Box, useUiHost } from "../ui";
|
|
2
|
+
import { colors } from "../theme/colors";
|
|
3
|
+
import { Checkbox } from "./ui/checkbox";
|
|
4
4
|
import { ListView, type ListRowState, type ListViewItem } from "./ui/list-view";
|
|
5
5
|
|
|
6
6
|
interface ToggleListItem {
|
|
@@ -36,23 +36,14 @@ function DesktopToggleRow({
|
|
|
36
36
|
state,
|
|
37
37
|
rowIdPrefix,
|
|
38
38
|
enabled,
|
|
39
|
+
onPress,
|
|
39
40
|
}: {
|
|
40
41
|
item: ListViewItem;
|
|
41
42
|
state: ListRowState;
|
|
42
43
|
rowIdPrefix?: string;
|
|
43
44
|
enabled: boolean;
|
|
45
|
+
onPress: () => void;
|
|
44
46
|
}) {
|
|
45
|
-
const checkboxBorder = enabled
|
|
46
|
-
? colors.borderFocused
|
|
47
|
-
: state.selected
|
|
48
|
-
? colors.textMuted
|
|
49
|
-
: colors.border;
|
|
50
|
-
const textColor = state.disabled
|
|
51
|
-
? colors.textMuted
|
|
52
|
-
: state.selected
|
|
53
|
-
? colors.text
|
|
54
|
-
: colors.textDim;
|
|
55
|
-
|
|
56
47
|
return (
|
|
57
48
|
<Box
|
|
58
49
|
id={rowIdPrefix ? `${rowIdPrefix}:${item.id}` : undefined}
|
|
@@ -67,48 +58,15 @@ function DesktopToggleRow({
|
|
|
67
58
|
opacity: state.disabled ? 0.55 : 1,
|
|
68
59
|
}}
|
|
69
60
|
>
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
border: `1px solid ${checkboxBorder}`,
|
|
80
|
-
borderRadius: 4,
|
|
81
|
-
boxShadow: enabled
|
|
82
|
-
? `inset 0 1px 0 ${blendHex(colors.borderFocused, colors.textBright, 0.28)}`
|
|
83
|
-
: `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.05)}`,
|
|
84
|
-
}}
|
|
85
|
-
>
|
|
86
|
-
{enabled && (
|
|
87
|
-
<Text
|
|
88
|
-
fg={colors.bg}
|
|
89
|
-
attributes={TextAttributes.BOLD}
|
|
90
|
-
style={{ fontSize: 12, lineHeight: "14px", fontWeight: 800 }}
|
|
91
|
-
>
|
|
92
|
-
{"✓"}
|
|
93
|
-
</Text>
|
|
94
|
-
)}
|
|
95
|
-
</Box>
|
|
96
|
-
<Box minWidth={0} flexShrink={1}>
|
|
97
|
-
<Text
|
|
98
|
-
fg={textColor}
|
|
99
|
-
attributes={state.selected ? TextAttributes.BOLD : 0}
|
|
100
|
-
style={{
|
|
101
|
-
display: "block",
|
|
102
|
-
overflow: "hidden",
|
|
103
|
-
textOverflow: "ellipsis",
|
|
104
|
-
whiteSpace: "nowrap",
|
|
105
|
-
fontWeight: state.selected ? 700 : 500,
|
|
106
|
-
lineHeight: "16px",
|
|
107
|
-
}}
|
|
108
|
-
>
|
|
109
|
-
{item.label}
|
|
110
|
-
</Text>
|
|
111
|
-
</Box>
|
|
61
|
+
<Checkbox
|
|
62
|
+
label={item.label}
|
|
63
|
+
checked={enabled}
|
|
64
|
+
disabled={state.disabled}
|
|
65
|
+
active={state.selected}
|
|
66
|
+
width="100%"
|
|
67
|
+
variant="desktop"
|
|
68
|
+
onChange={onPress}
|
|
69
|
+
/>
|
|
112
70
|
</Box>
|
|
113
71
|
);
|
|
114
72
|
}
|
|
@@ -161,6 +119,13 @@ export function ToggleList({
|
|
|
161
119
|
}}
|
|
162
120
|
renderRow={(item, state, index) => {
|
|
163
121
|
const toggleItem = items.find((entry) => entry.id === item.id);
|
|
122
|
+
const activate = (event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
|
|
123
|
+
event?.stopPropagation?.();
|
|
124
|
+
event?.preventDefault?.();
|
|
125
|
+
if (state.disabled) return;
|
|
126
|
+
onSelect?.(index);
|
|
127
|
+
onToggle?.(item.id);
|
|
128
|
+
};
|
|
164
129
|
if (isDesktopWeb) {
|
|
165
130
|
return (
|
|
166
131
|
<DesktopToggleRow
|
|
@@ -168,33 +133,24 @@ export function ToggleList({
|
|
|
168
133
|
state={state}
|
|
169
134
|
rowIdPrefix={rowIdPrefix}
|
|
170
135
|
enabled={toggleItem?.enabled === true}
|
|
136
|
+
onPress={() => activate()}
|
|
171
137
|
/>
|
|
172
138
|
);
|
|
173
139
|
}
|
|
174
140
|
|
|
175
|
-
const checked = toggleItem?.enabled ? "\u2713" : " ";
|
|
176
|
-
const activate = (event: any) => {
|
|
177
|
-
event.stopPropagation?.();
|
|
178
|
-
if (state.disabled) return;
|
|
179
|
-
onSelect?.(index);
|
|
180
|
-
onToggle?.(item.id);
|
|
181
|
-
};
|
|
182
141
|
return (
|
|
183
142
|
<Box
|
|
184
143
|
id={rowIdPrefix ? `${rowIdPrefix}:${item.id}` : undefined}
|
|
185
144
|
flexDirection="row"
|
|
186
145
|
onMouseDown={activate}
|
|
187
146
|
>
|
|
188
|
-
<
|
|
189
|
-
{
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
>
|
|
196
|
-
{`[${checked}] ${item.label}`}
|
|
197
|
-
</Text>
|
|
147
|
+
<Checkbox
|
|
148
|
+
label={item.label}
|
|
149
|
+
checked={toggleItem?.enabled === true}
|
|
150
|
+
disabled={state.disabled}
|
|
151
|
+
active={state.selected}
|
|
152
|
+
onChange={() => activate()}
|
|
153
|
+
/>
|
|
198
154
|
</Box>
|
|
199
155
|
);
|
|
200
156
|
}}
|
|
@@ -14,6 +14,7 @@ export interface ButtonProps {
|
|
|
14
14
|
active?: boolean;
|
|
15
15
|
shortcut?: string;
|
|
16
16
|
width?: number;
|
|
17
|
+
height?: number | string;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
function resolveButtonColors(variant: ButtonVariant, active: boolean, disabled: boolean) {
|
|
@@ -45,6 +46,7 @@ export function Button({
|
|
|
45
46
|
active = false,
|
|
46
47
|
shortcut,
|
|
47
48
|
width,
|
|
49
|
+
height,
|
|
48
50
|
}: ButtonProps) {
|
|
49
51
|
useRemoteUiNode({
|
|
50
52
|
role: "button",
|
|
@@ -68,6 +70,7 @@ export function Button({
|
|
|
68
70
|
active={active}
|
|
69
71
|
shortcut={shortcut}
|
|
70
72
|
width={width}
|
|
73
|
+
height={height}
|
|
71
74
|
/>
|
|
72
75
|
);
|
|
73
76
|
}
|
|
@@ -77,7 +80,7 @@ export function Button({
|
|
|
77
80
|
return (
|
|
78
81
|
<Box
|
|
79
82
|
width={width}
|
|
80
|
-
height={1}
|
|
83
|
+
height={height ?? 1}
|
|
81
84
|
flexDirection="row"
|
|
82
85
|
backgroundColor={palette.bg}
|
|
83
86
|
onMouseDown={() => {
|