gloomberb 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -54
- package/package.json +14 -13
- package/patches/{@opentui%2Fcore@0.1.90.patch → @opentui%2Fcore@0.3.2.patch} +175 -90
- package/src/api-client/chat.ts +8 -0
- package/src/api-client/index.test.ts +30 -0
- package/src/api-client/index.ts +4 -0
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/types.ts +1 -0
- package/src/app/global-shortcuts.test.tsx +54 -4
- package/src/app/global-shortcuts.ts +11 -5
- package/src/capabilities/factories.ts +4 -0
- package/src/components/chart/comparison/data.test.ts +54 -0
- package/src/components/chart/comparison/data.ts +47 -24
- package/src/components/chart/core/projection.ts +51 -27
- package/src/components/command-bar/action-runtime.ts +1 -0
- package/src/components/command-bar/layout-items/current-layout.test.ts +115 -0
- package/src/components/command-bar/layout-items/current-layout.ts +33 -0
- package/src/components/command-bar/list/view.tsx +3 -1
- package/src/components/command-bar/pane-templates/items.ts +4 -1
- package/src/components/command-bar/panel/body.tsx +1 -0
- package/src/components/command-bar/routes/root/results.ts +0 -30
- package/src/components/command-bar/routes/root/runtime.ts +2 -20
- package/src/components/command-bar/routes/ticker-search/actions.ts +42 -4
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -1
- package/src/components/command-bar/routing/launch-request.ts +14 -7
- package/src/components/command-bar/routing/list-state.ts +1 -3
- package/src/components/command-bar/surface/index.test.tsx +76 -4
- package/src/components/command-bar/surface/index.tsx +0 -2
- package/src/components/command-bar/surface/pane.test.tsx +1 -0
- package/src/components/command-bar/surface/test-harness.tsx +1 -0
- package/src/components/command-bar/view-model.ts +3 -3
- package/src/components/data-table/stack-view.test.tsx +14 -11
- package/src/components/data-table/stack-view.tsx +1 -2
- package/src/components/data-table/view.test.tsx +24 -18
- package/src/components/data-table/view.tsx +313 -83
- package/src/components/feed-data-table/stack-view.tsx +6 -8
- package/src/components/layout/pane/footer/index.test.tsx +6 -0
- package/src/components/layout/pane/footer/index.tsx +14 -2
- package/src/components/layout/pane/title.ts +5 -1
- package/src/components/layout/shell/index.test.tsx +120 -2
- package/src/components/layout/shell/index.tsx +2 -0
- package/src/components/layout/shell/menu.ts +1 -7
- package/src/components/layout/shell/pane/actions.ts +8 -0
- package/src/components/layout/shell/pane/management-shortcuts.ts +12 -2
- package/src/components/layout/shell/shortcuts.ts +14 -6
- package/src/components/layout/shell/window-mode/index.ts +8 -3
- package/src/components/layout/window-edit/presentation.ts +4 -4
- package/src/components/onboarding/onboarding-steps.tsx +3 -2
- package/src/components/pane-settings-dialog/field-dialogs.tsx +0 -2
- package/src/components/pane-settings-dialog/tui.tsx +1 -1
- package/src/components/ticker/list-table-view.tsx +17 -25
- package/src/components/ui/choice-dialog.tsx +12 -2
- package/src/components/ui/multi-select/dialog.tsx +17 -5
- package/src/components/ui/remote-image.tsx +1 -14
- package/src/components/ui/shortcut-hint.tsx +14 -2
- package/src/components/ui/table-layout.test.ts +2 -2
- package/src/components/ui/table-layout.ts +8 -2
- package/src/components/ui/tabs.tsx +115 -86
- package/src/components/ui/ui.test.tsx +83 -4
- package/src/core/state/app/types.ts +12 -4
- package/src/market-data/coordinator/auxiliary.ts +29 -1
- package/src/market-data/coordinator/index.ts +17 -1
- package/src/market-data/hooks.tsx +17 -1
- package/src/market-data/selectors.ts +4 -0
- package/src/plugins/builtin/account-management/form-components.tsx +0 -4
- package/src/plugins/builtin/account-management/model.test.ts +54 -0
- package/src/plugins/builtin/account-management/model.ts +13 -3
- package/src/plugins/builtin/account-management/pane.tsx +5 -11
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +5 -3
- package/src/plugins/builtin/analytics/index.tsx +0 -3
- package/src/plugins/builtin/analytics/view.tsx +6 -5
- package/src/plugins/builtin/broker-manager/index.tsx +6 -6
- package/src/plugins/builtin/buildout/pane/index.tsx +6 -6
- package/src/plugins/builtin/changelog/index.tsx +40 -27
- package/src/plugins/builtin/changelog/model.test.ts +77 -0
- package/src/plugins/builtin/changelog/model.ts +98 -0
- package/src/plugins/builtin/chat/channel-labels.ts +53 -0
- package/src/plugins/builtin/chat/channels.test.ts +28 -0
- package/src/plugins/builtin/chat/channels.ts +36 -19
- package/src/plugins/builtin/chat/chat.test.tsx +264 -5
- package/src/plugins/builtin/chat/content/channel-navigation.ts +50 -11
- package/src/plugins/builtin/chat/content/composer-runtime.test.tsx +144 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +151 -20
- package/src/plugins/builtin/chat/content/composer.tsx +23 -2
- package/src/plugins/builtin/chat/content/index.tsx +66 -4
- package/src/plugins/builtin/chat/content/layout-metrics.ts +3 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +27 -1
- package/src/plugins/builtin/chat/content/transcript.tsx +8 -0
- package/src/plugins/builtin/chat/content/types.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +41 -0
- package/src/plugins/builtin/chat/controller/messages.ts +18 -0
- package/src/plugins/builtin/chat/controller/notifications.ts +10 -4
- package/src/plugins/builtin/chat/controller/utils.ts +6 -3
- package/src/plugins/builtin/chat/controller/view.ts +1 -1
- package/src/plugins/builtin/chat/controller.test.ts +189 -0
- package/src/plugins/builtin/chat/edit-window.ts +24 -0
- package/src/plugins/builtin/chat/layout.ts +94 -31
- package/src/plugins/builtin/chat/message/action-chip.tsx +1 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +53 -20
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +16 -10
- package/src/plugins/builtin/chat/message/render-state.ts +5 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +49 -20
- package/src/plugins/builtin/chat/message/types.ts +2 -0
- package/src/plugins/builtin/chat/pane.tsx +35 -7
- package/src/plugins/builtin/chat/sidebar.test.tsx +72 -0
- package/src/plugins/builtin/chat/status-widget.tsx +29 -11
- package/src/plugins/builtin/chat/test-harness.tsx +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +7 -11
- package/src/plugins/builtin/congress-trades/pane.tsx +14 -22
- package/src/plugins/builtin/earnings/index.tsx +8 -7
- package/src/plugins/builtin/econ/index.tsx +6 -8
- package/src/plugins/builtin/help/components.tsx +1 -1
- package/src/plugins/builtin/help/index.test.tsx +105 -30
- package/src/plugins/builtin/help/index.tsx +326 -164
- package/src/plugins/builtin/help/shortcut-model.ts +5 -6
- package/src/plugins/builtin/holders/pane.tsx +7 -6
- package/src/plugins/builtin/market-movers/index.tsx +6 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +7 -6
- package/src/plugins/builtin/options/view.tsx +16 -8
- package/src/plugins/builtin/portfolio-list/column-values.ts +304 -1
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +93 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +7 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +25 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +281 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +27 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +4 -2
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +10 -68
- package/src/plugins/builtin/research/analyst-pane.tsx +1 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +358 -7
- package/src/plugins/builtin/research/index.test.ts +30 -1
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +6 -8
- package/src/plugins/builtin/sec/filing-documents.ts +66 -0
- package/src/plugins/builtin/sec/index.tsx +132 -20
- package/src/plugins/builtin/sectors/index.tsx +13 -23
- package/src/plugins/builtin/shared/ticker-request.ts +1 -0
- package/src/plugins/builtin/substack/api/auth.ts +185 -0
- package/src/plugins/builtin/substack/api/cache.ts +78 -0
- package/src/plugins/builtin/substack/api/loaders.ts +146 -0
- package/src/plugins/builtin/substack/api/store.ts +186 -0
- package/src/plugins/builtin/substack/api/types.ts +40 -0
- package/src/plugins/builtin/substack/api.test.ts +298 -0
- package/src/plugins/builtin/substack/article-detail.tsx +359 -0
- package/src/plugins/builtin/substack/article-stack.tsx +129 -0
- package/src/plugins/builtin/substack/content.ts +478 -0
- package/src/plugins/builtin/substack/feed-tabs.tsx +41 -0
- package/src/plugins/builtin/substack/index.tsx +49 -0
- package/src/plugins/builtin/substack/login-view.tsx +126 -0
- package/src/plugins/builtin/substack/model.test.ts +155 -0
- package/src/plugins/builtin/substack/normalize.ts +343 -0
- package/src/plugins/builtin/substack/pane-footer.ts +71 -0
- package/src/plugins/builtin/substack/pane-state.ts +170 -0
- package/src/plugins/builtin/substack/pane.tsx +500 -0
- package/src/plugins/builtin/substack/table.ts +111 -0
- package/src/plugins/builtin/substack/types.ts +71 -0
- package/src/plugins/builtin/substack/utils.ts +131 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +26 -25
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +6 -5
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +10 -8
- package/src/plugins/builtin/ticker-detail/index.test.tsx +16 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/pane.tsx +13 -1
- package/src/plugins/builtin/world-indices/index.tsx +9 -6
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/pane-manager/layout-state.ts +11 -0
- package/src/plugins/pane-manager.ts +1 -1
- package/src/plugins/prediction-markets/pane.tsx +10 -9
- package/src/plugins/registry/index.ts +3 -0
- package/src/plugins/runtime/context.tsx +1 -0
- package/src/plugins/runtime/index.tsx +4 -0
- package/src/react/use-throttled-commit-value.ts +116 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +42 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +3 -2
- package/src/renderers/electrobun/view/data-table/row.tsx +2 -2
- package/src/renderers/electrobun/view/host/input.tsx +23 -2
- package/src/renderers/electrobun/view/http-fetch.ts +10 -3
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +4 -0
- package/src/sources/provider-router/cache.ts +1 -0
- package/src/sources/provider-router/document-routes.ts +45 -0
- package/src/sources/provider-router/index.ts +4 -0
- package/src/sources/sec-edgar/content.ts +9 -3
- package/src/sources/sec-edgar.test.ts +108 -1
- package/src/sources/sec-edgar.ts +105 -1
- package/src/sources/yahoo-finance.ts +4 -0
- package/src/state/app/context/index.test.tsx +16 -0
- package/src/test-support/plugin-runtime.ts +1 -0
- package/src/tickers/search/index.ts +1 -0
- package/src/types/data-provider.ts +11 -0
- package/src/ui/host.tsx +1 -0
- package/src/version.ts +1 -1
|
@@ -62,7 +62,12 @@ export function useAppGlobalShortcuts({
|
|
|
62
62
|
if (!isDetachedWindow && event.name === "`" && !state.commandBarOpen) {
|
|
63
63
|
event.preventDefault();
|
|
64
64
|
event.stopPropagation();
|
|
65
|
-
dispatch({
|
|
65
|
+
dispatch({
|
|
66
|
+
type: "SET_COMMAND_BAR",
|
|
67
|
+
open: true,
|
|
68
|
+
query: "",
|
|
69
|
+
launch: { kind: "ticker-search", query: "" },
|
|
70
|
+
});
|
|
66
71
|
return;
|
|
67
72
|
}
|
|
68
73
|
if (!isDetachedWindow && /^[1-9]$/.test(event.name ?? "") && event.ctrl && (state.config.layouts ?? []).length > 1) {
|
|
@@ -96,18 +101,19 @@ export function useAppGlobalShortcuts({
|
|
|
96
101
|
|
|
97
102
|
if (state.inputCaptured) return;
|
|
98
103
|
|
|
99
|
-
|
|
104
|
+
const hasShortcutModifier = event.ctrl || event.meta || event.super || event.alt;
|
|
105
|
+
if (!hasShortcutModifier && !isDetachedWindow && event.name === "q") {
|
|
100
106
|
rendererHost.requestExit();
|
|
101
|
-
} else if (event.name === "r") {
|
|
107
|
+
} else if (!hasShortcutModifier && event.name === "r") {
|
|
102
108
|
if (focusedTickerSymbol) {
|
|
103
109
|
const ticker = state.tickers.get(focusedTickerSymbol);
|
|
104
110
|
if (ticker) refreshTicker(ticker.metadata.ticker, ticker.metadata.exchange, ticker, 0);
|
|
105
111
|
}
|
|
106
|
-
} else if (event.name === "R" || (event.name === "r" && event.shift)) {
|
|
112
|
+
} else if (!hasShortcutModifier && (event.name === "R" || (event.name === "r" && event.shift))) {
|
|
107
113
|
for (const ticker of state.tickers.values()) {
|
|
108
114
|
refreshTicker(ticker.metadata.ticker, ticker.metadata.exchange, ticker, 1);
|
|
109
115
|
}
|
|
110
|
-
} else if (event.name === "u" && state.updateAvailable && !state.updateProgress && !state.updateCheckInProgress && canSelfUpdate(state.updateAvailable)) {
|
|
116
|
+
} else if (!hasShortcutModifier && event.name === "u" && state.updateAvailable && !state.updateProgress && !state.updateCheckInProgress && canSelfUpdate(state.updateAvailable)) {
|
|
111
117
|
startUpdate(state.updateAvailable);
|
|
112
118
|
} else {
|
|
113
119
|
const disabledPlugins = new Set(state.config.disabledPlugins || []);
|
|
@@ -52,6 +52,10 @@ export function assetDataProvider(provider: AssetDataProvider): AssetDataCapabil
|
|
|
52
52
|
if (!provider.getEarningsCalendar) throw new Error(`${provider.name} does not provide earnings calendar.`);
|
|
53
53
|
return provider.getEarningsCalendar(input.symbols ?? [], input.context);
|
|
54
54
|
}, "query"),
|
|
55
|
+
getSecFilingDocuments: op((input: any) => {
|
|
56
|
+
if (!provider.getSecFilingDocuments) throw new Error(`${provider.name} does not provide SEC filing documents.`);
|
|
57
|
+
return provider.getSecFilingDocuments(input.filing);
|
|
58
|
+
}, "query"),
|
|
55
59
|
getSecFilingContent: op((input: any) => {
|
|
56
60
|
if (!provider.getSecFilingContent) throw new Error(`${provider.name} does not provide SEC filing content.`);
|
|
57
61
|
return provider.getSecFilingContent(input.filing);
|
|
@@ -57,6 +57,60 @@ describe("projectComparisonChartData", () => {
|
|
|
57
57
|
expect(projection.series[1]?.points.map((point) => point.rawValue)).toEqual([null, null, 20, 22]);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
test("carries the latest series value across shared timestamps from other markets", () => {
|
|
61
|
+
const projection = projectComparisonChartData([
|
|
62
|
+
makeSeries("DAX", "EUR", [
|
|
63
|
+
["2024-01-02T08:00:00.000Z", 100],
|
|
64
|
+
["2024-01-02T08:15:00.000Z", 101],
|
|
65
|
+
["2024-01-03T08:00:00.000Z", 102],
|
|
66
|
+
]),
|
|
67
|
+
makeSeries("AAPL", "USD", [
|
|
68
|
+
["2024-01-02T14:30:00.000Z", 200],
|
|
69
|
+
["2024-01-02T14:45:00.000Z", 201],
|
|
70
|
+
]),
|
|
71
|
+
], 8, {
|
|
72
|
+
panOffset: 0,
|
|
73
|
+
zoomLevel: 1,
|
|
74
|
+
renderMode: "line",
|
|
75
|
+
}, "price");
|
|
76
|
+
|
|
77
|
+
expect(projection.dates.map((date) => date.toISOString())).toEqual([
|
|
78
|
+
"2024-01-02T08:00:00.000Z",
|
|
79
|
+
"2024-01-02T08:15:00.000Z",
|
|
80
|
+
"2024-01-02T14:30:00.000Z",
|
|
81
|
+
"2024-01-02T14:45:00.000Z",
|
|
82
|
+
"2024-01-03T08:00:00.000Z",
|
|
83
|
+
]);
|
|
84
|
+
expect(projection.series[0]?.points.map((point) => point.rawValue)).toEqual([100, 101, 101, 101, 102]);
|
|
85
|
+
expect(projection.series[1]?.points.map((point) => point.rawValue)).toEqual([null, null, 200, 201, 201]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("uses the same alternating close bucket selection as stock chart projection", () => {
|
|
89
|
+
const projection = projectComparisonChartData([
|
|
90
|
+
makeSeries("DAX", "EUR", [
|
|
91
|
+
["2024-01-02T08:00:00.000Z", 100],
|
|
92
|
+
["2024-01-02T08:15:00.000Z", 105],
|
|
93
|
+
["2024-01-02T14:30:00.000Z", 101],
|
|
94
|
+
["2024-01-02T14:45:00.000Z", 99],
|
|
95
|
+
]),
|
|
96
|
+
makeSeries("AAPL", "USD", [
|
|
97
|
+
["2024-01-02T14:30:00.000Z", 200],
|
|
98
|
+
["2024-01-02T14:45:00.000Z", 201],
|
|
99
|
+
]),
|
|
100
|
+
], 2, {
|
|
101
|
+
panOffset: 0,
|
|
102
|
+
zoomLevel: 1,
|
|
103
|
+
renderMode: "line",
|
|
104
|
+
}, "price");
|
|
105
|
+
|
|
106
|
+
expect(projection.dates.map((date) => date.toISOString())).toEqual([
|
|
107
|
+
"2024-01-02T08:15:00.000Z",
|
|
108
|
+
"2024-01-02T14:45:00.000Z",
|
|
109
|
+
]);
|
|
110
|
+
expect(projection.series[0]?.points.map((point) => point.rawValue)).toEqual([105, 99]);
|
|
111
|
+
expect(projection.series[1]?.points.map((point) => point.rawValue)).toEqual([null, 200]);
|
|
112
|
+
});
|
|
113
|
+
|
|
60
114
|
test("normalizes percent mode from each series first visible point", () => {
|
|
61
115
|
const projection = projectComparisonChartData([
|
|
62
116
|
makeSeries("AAPL", "USD", [
|
|
@@ -7,6 +7,11 @@ import type {
|
|
|
7
7
|
} from "../core/types";
|
|
8
8
|
import { buildVisibleDateWindowFromRange } from "../core/date-window";
|
|
9
9
|
import { getVisiblePointCount } from "../core/viewport";
|
|
10
|
+
import {
|
|
11
|
+
buildIndexProjectionBuckets,
|
|
12
|
+
selectRepresentativeCloseValue,
|
|
13
|
+
type IndexProjectionBucket,
|
|
14
|
+
} from "../core/projection";
|
|
10
15
|
|
|
11
16
|
export interface ComparisonProjectedPoint {
|
|
12
17
|
date: Date;
|
|
@@ -38,6 +43,14 @@ interface ComparisonAxisModeResolution {
|
|
|
38
43
|
warning: string | null;
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
type ComparisonWindowViewState =
|
|
47
|
+
Pick<ComparisonChartViewState, "panOffset" | "zoomLevel">
|
|
48
|
+
& Partial<Pick<ComparisonChartViewState, "dateWindow">>;
|
|
49
|
+
|
|
50
|
+
type ComparisonProjectionViewState =
|
|
51
|
+
ComparisonWindowViewState
|
|
52
|
+
& Pick<ComparisonChartViewState, "renderMode">;
|
|
53
|
+
|
|
41
54
|
export interface ComparisonChartProjection {
|
|
42
55
|
dates: Date[];
|
|
43
56
|
series: ComparisonProjectedSeries[];
|
|
@@ -92,7 +105,7 @@ export function getMaxComparisonPanOffset(
|
|
|
92
105
|
|
|
93
106
|
export function getVisibleComparisonWindow(
|
|
94
107
|
series: ComparisonChartSeries[],
|
|
95
|
-
viewState:
|
|
108
|
+
viewState: ComparisonWindowViewState,
|
|
96
109
|
): ComparisonVisibleWindow {
|
|
97
110
|
const dates = getUniqueSortedDates(series);
|
|
98
111
|
|
|
@@ -138,10 +151,7 @@ function transformRawValue(rawValue: number | null, axisMode: ChartAxisMode, bas
|
|
|
138
151
|
}
|
|
139
152
|
|
|
140
153
|
function resolveRepresentativeValue(values: number[], index: number): number {
|
|
141
|
-
|
|
142
|
-
return index % 2 === 0
|
|
143
|
-
? Math.max(...values)
|
|
144
|
-
: Math.min(...values);
|
|
154
|
+
return selectRepresentativeCloseValue(values, index, (value) => value);
|
|
145
155
|
}
|
|
146
156
|
|
|
147
157
|
function buildSeriesMap(points: PricePoint[]): Map<number, number> {
|
|
@@ -155,6 +165,28 @@ function buildSeriesMap(points: PricePoint[]): Map<number, number> {
|
|
|
155
165
|
return map;
|
|
156
166
|
}
|
|
157
167
|
|
|
168
|
+
function buildFilledRawValuesByDate(
|
|
169
|
+
rawByDate: ReadonlyMap<number, number>,
|
|
170
|
+
dates: readonly Date[],
|
|
171
|
+
): Array<number | null> {
|
|
172
|
+
let latestRawValue: number | null = null;
|
|
173
|
+
return dates.map((date) => {
|
|
174
|
+
const timestamp = date.getTime();
|
|
175
|
+
if (rawByDate.has(timestamp)) {
|
|
176
|
+
latestRawValue = rawByDate.get(timestamp)!;
|
|
177
|
+
return latestRawValue;
|
|
178
|
+
}
|
|
179
|
+
return latestRawValue;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getBucketDate(
|
|
184
|
+
dates: readonly Date[],
|
|
185
|
+
bucket: IndexProjectionBucket,
|
|
186
|
+
): Date {
|
|
187
|
+
return dates[Math.max(bucket.end - 1, 0)] ?? dates[dates.length - 1] ?? new Date(0);
|
|
188
|
+
}
|
|
189
|
+
|
|
158
190
|
function resolveComparisonAxisMode(
|
|
159
191
|
requestedAxisMode: ChartAxisMode,
|
|
160
192
|
series: ComparisonChartSeries[],
|
|
@@ -194,7 +226,7 @@ function resolveComparisonAxisMode(
|
|
|
194
226
|
export function projectComparisonChartData(
|
|
195
227
|
series: ComparisonChartSeries[],
|
|
196
228
|
chartWidth: number,
|
|
197
|
-
viewState:
|
|
229
|
+
viewState: ComparisonProjectionViewState,
|
|
198
230
|
requestedAxisMode: ChartAxisMode,
|
|
199
231
|
): ComparisonChartProjection {
|
|
200
232
|
const normalizedSeries = series.map((entry) => ({ ...entry, points: normalizeSeriesPoints(entry.points) }));
|
|
@@ -221,31 +253,26 @@ export function projectComparisonChartData(
|
|
|
221
253
|
warning: axisResolution.warning,
|
|
222
254
|
};
|
|
223
255
|
}
|
|
224
|
-
const
|
|
225
|
-
const bucketSize = window.dates.length > 0 ? window.dates.length / bucketCount : 1;
|
|
256
|
+
const buckets = buildIndexProjectionBuckets(window.dates.length, chartWidth);
|
|
226
257
|
|
|
227
258
|
const projectedSeries = normalizedSeries.map((entry) => {
|
|
228
259
|
const rawByDate = buildSeriesMap(entry.points);
|
|
229
|
-
const
|
|
230
|
-
|
|
260
|
+
const filledRawValues = buildFilledRawValuesByDate(rawByDate, window.dates);
|
|
261
|
+
const visibleRawValues = filledRawValues
|
|
231
262
|
.filter((value): value is number => value !== null);
|
|
232
263
|
const baseValue = visibleRawValues[0] ?? null;
|
|
233
264
|
const latestRawValue = visibleRawValues[visibleRawValues.length - 1] ?? null;
|
|
234
265
|
|
|
235
|
-
const points =
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
const bucketDates = window.dates.slice(start, end);
|
|
239
|
-
const date = bucketDates[bucketDates.length - 1] ?? window.dates[window.dates.length - 1] ?? new Date(0);
|
|
240
|
-
const bucketRawValues = bucketDates
|
|
241
|
-
.map((bucketDate) => rawByDate.get(bucketDate.getTime()) ?? null)
|
|
266
|
+
const points = buckets.map((bucket) => {
|
|
267
|
+
const bucketRawValues = filledRawValues
|
|
268
|
+
.slice(bucket.start, bucket.end)
|
|
242
269
|
.filter((value): value is number => value !== null);
|
|
243
270
|
const rawValue = bucketRawValues.length > 0
|
|
244
|
-
? resolveRepresentativeValue(bucketRawValues, index)
|
|
271
|
+
? resolveRepresentativeValue(bucketRawValues, bucket.index)
|
|
245
272
|
: null;
|
|
246
273
|
|
|
247
274
|
return {
|
|
248
|
-
date,
|
|
275
|
+
date: getBucketDate(window.dates, bucket),
|
|
249
276
|
rawValue,
|
|
250
277
|
value: transformRawValue(rawValue, axisResolution.effectiveAxisMode, baseValue),
|
|
251
278
|
};
|
|
@@ -264,11 +291,7 @@ export function projectComparisonChartData(
|
|
|
264
291
|
});
|
|
265
292
|
|
|
266
293
|
return {
|
|
267
|
-
dates:
|
|
268
|
-
const start = Math.floor(index * bucketSize);
|
|
269
|
-
const end = Math.min(window.dates.length, Math.max(Math.floor((index + 1) * bucketSize), start + 1));
|
|
270
|
-
return window.dates[Math.max(end - 1, 0)] ?? new Date(0);
|
|
271
|
-
}),
|
|
294
|
+
dates: buckets.map((bucket) => getBucketDate(window.dates, bucket)),
|
|
272
295
|
series: projectedSeries,
|
|
273
296
|
requestedMode,
|
|
274
297
|
effectiveMode: requestedMode,
|
|
@@ -24,6 +24,12 @@ export interface ProjectChartDataOptions {
|
|
|
24
24
|
sourceIndexOffset?: number;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export interface IndexProjectionBucket {
|
|
28
|
+
index: number;
|
|
29
|
+
start: number;
|
|
30
|
+
end: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
interface StableOhlcProjectionOptionsInput {
|
|
28
34
|
pointCount: number;
|
|
29
35
|
sourceIndexOffset: number;
|
|
@@ -131,6 +137,44 @@ function aggregateOhlcBucket(bucket: readonly ProjectedChartPoint[]): ProjectedC
|
|
|
131
137
|
};
|
|
132
138
|
}
|
|
133
139
|
|
|
140
|
+
export function buildIndexProjectionBuckets(
|
|
141
|
+
itemCount: number,
|
|
142
|
+
targetWidth: number,
|
|
143
|
+
): IndexProjectionBucket[] {
|
|
144
|
+
const normalizedItemCount = Math.max(Math.floor(itemCount), 0);
|
|
145
|
+
if (normalizedItemCount === 0) return [];
|
|
146
|
+
|
|
147
|
+
const bucketCount = Math.min(normalizedItemCount, Math.max(Math.floor(targetWidth), 1));
|
|
148
|
+
const bucketSize = normalizedItemCount / bucketCount;
|
|
149
|
+
|
|
150
|
+
return Array.from({ length: bucketCount }, (_, index) => {
|
|
151
|
+
const start = Math.floor(index * bucketSize);
|
|
152
|
+
const end = Math.min(
|
|
153
|
+
normalizedItemCount,
|
|
154
|
+
Math.max(Math.floor((index + 1) * bucketSize), start + 1),
|
|
155
|
+
);
|
|
156
|
+
return { index, start, end };
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function selectRepresentativeCloseValue<T>(
|
|
161
|
+
bucket: readonly T[],
|
|
162
|
+
index: number,
|
|
163
|
+
getClose: (item: T) => number,
|
|
164
|
+
): T {
|
|
165
|
+
let best = bucket[0] as T;
|
|
166
|
+
if (index % 2 === 0) {
|
|
167
|
+
for (const item of bucket) {
|
|
168
|
+
if (getClose(item) > getClose(best)) best = item;
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
for (const item of bucket) {
|
|
172
|
+
if (getClose(item) < getClose(best)) best = item;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return best;
|
|
176
|
+
}
|
|
177
|
+
|
|
134
178
|
function bucketOhlcBySourceIndex(
|
|
135
179
|
normalizedPoints: readonly ProjectedChartPoint[],
|
|
136
180
|
bucketSize: number,
|
|
@@ -202,33 +246,13 @@ function projectCloseSeries(
|
|
|
202
246
|
targetWidth: number,
|
|
203
247
|
): ProjectedChartPoint[] {
|
|
204
248
|
if (points.length === 0) return [];
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const end = Math.min(points.length, Math.max(Math.floor((i + 1) * bucketSize), start + 1));
|
|
213
|
-
const bucket = points.slice(start, end);
|
|
214
|
-
|
|
215
|
-
if (bucket.length === 0) continue;
|
|
216
|
-
|
|
217
|
-
// Alternate bucket extremes to preserve line/area shape after downsampling.
|
|
218
|
-
let best = bucket[0]!;
|
|
219
|
-
if (i % 2 === 0) {
|
|
220
|
-
for (const point of bucket) {
|
|
221
|
-
if (point.close > best.close) best = point;
|
|
222
|
-
}
|
|
223
|
-
} else {
|
|
224
|
-
for (const point of bucket) {
|
|
225
|
-
if (point.close < best.close) best = point;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
result.push(normalizePoint(best));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return result;
|
|
249
|
+
return buildIndexProjectionBuckets(points.length, targetWidth).map((bucket) => (
|
|
250
|
+
normalizePoint(selectRepresentativeCloseValue(
|
|
251
|
+
points.slice(bucket.start, bucket.end),
|
|
252
|
+
bucket.index,
|
|
253
|
+
(point) => point.close,
|
|
254
|
+
))
|
|
255
|
+
));
|
|
232
256
|
}
|
|
233
257
|
|
|
234
258
|
/**
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createInitialState, type AppAction, type AppState } from "../../../state/app/context";
|
|
3
|
+
import { cloneLayout, createDefaultConfig, type LayoutConfig } from "../../../types/config";
|
|
4
|
+
import type { PluginRegistry } from "../../../plugins/registry";
|
|
5
|
+
import type { CommandBarRoute } from "../workflow/types";
|
|
6
|
+
import type { LayoutItemsContext } from "./types";
|
|
7
|
+
import { buildCurrentLayoutItems } from "./current-layout";
|
|
8
|
+
|
|
9
|
+
type InlineConfirmOptions = Parameters<LayoutItemsContext["openInlineConfirm"]>[0];
|
|
10
|
+
|
|
11
|
+
function createFloatingLayoutFixture(): { layout: LayoutConfig; state: AppState } {
|
|
12
|
+
const config = createDefaultConfig("/tmp/gloomberb-layout-actions-test");
|
|
13
|
+
const mainPane = config.layout.instances.find((instance) => instance.instanceId === "portfolio-list:main");
|
|
14
|
+
const firstDetailPane = config.layout.instances.find((instance) => instance.instanceId === "ticker-detail:main");
|
|
15
|
+
if (!mainPane || !firstDetailPane) throw new Error("missing default panes");
|
|
16
|
+
|
|
17
|
+
const secondDetailPane = {
|
|
18
|
+
...firstDetailPane,
|
|
19
|
+
instanceId: "ticker-detail:secondary",
|
|
20
|
+
};
|
|
21
|
+
const layout: LayoutConfig = {
|
|
22
|
+
dockRoot: { kind: "pane", instanceId: "portfolio-list:main" },
|
|
23
|
+
instances: [{ ...mainPane }, { ...firstDetailPane }, secondDetailPane],
|
|
24
|
+
floating: [
|
|
25
|
+
{ instanceId: "ticker-detail:main", x: 4, y: 2, width: 30, height: 8 },
|
|
26
|
+
{ instanceId: "ticker-detail:secondary", x: 8, y: 3, width: 30, height: 8 },
|
|
27
|
+
],
|
|
28
|
+
detached: [],
|
|
29
|
+
};
|
|
30
|
+
const state = createInitialState({
|
|
31
|
+
...config,
|
|
32
|
+
layout: cloneLayout(layout),
|
|
33
|
+
layouts: [{ name: "Default", layout: cloneLayout(layout) }],
|
|
34
|
+
});
|
|
35
|
+
return { layout, state };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createLayoutItemsContext(
|
|
39
|
+
options: {
|
|
40
|
+
confirmDangerousActions?: boolean;
|
|
41
|
+
layouts: LayoutConfig[];
|
|
42
|
+
confirmations: InlineConfirmOptions[];
|
|
43
|
+
},
|
|
44
|
+
): LayoutItemsContext {
|
|
45
|
+
const { layout, state } = createFloatingLayoutFixture();
|
|
46
|
+
return {
|
|
47
|
+
closeAll: () => {},
|
|
48
|
+
currentLayout: layout,
|
|
49
|
+
dispatch: (_action: AppAction) => {},
|
|
50
|
+
duplicatePane: () => {},
|
|
51
|
+
focusedPaneId: state.focusedPaneId,
|
|
52
|
+
notifyGridlockRevert: () => {},
|
|
53
|
+
openBuiltInWorkflow: () => {},
|
|
54
|
+
openInlineConfirm: (confirmOptions) => {
|
|
55
|
+
options.confirmations.push(confirmOptions);
|
|
56
|
+
},
|
|
57
|
+
persistLayoutChange: (nextLayout) => {
|
|
58
|
+
options.layouts.push(nextLayout);
|
|
59
|
+
},
|
|
60
|
+
pluginRegistry: {
|
|
61
|
+
getTermSizeFn: () => ({ width: 80, height: 24 }),
|
|
62
|
+
} as PluginRegistry,
|
|
63
|
+
pushRoute: (_route: CommandBarRoute) => {},
|
|
64
|
+
state,
|
|
65
|
+
...(options.confirmDangerousActions === undefined ? {} : { confirmDangerousActions: options.confirmDangerousActions }),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe("buildCurrentLayoutItems", () => {
|
|
70
|
+
test("closes all floating panes from the current layout", () => {
|
|
71
|
+
const layouts: LayoutConfig[] = [];
|
|
72
|
+
const confirmations: InlineConfirmOptions[] = [];
|
|
73
|
+
const items = buildCurrentLayoutItems(createLayoutItemsContext({ layouts, confirmations }));
|
|
74
|
+
|
|
75
|
+
const item = items.find((entry) => entry.id === "layout-close-all-floating");
|
|
76
|
+
expect(item).toMatchObject({
|
|
77
|
+
label: "Close All Floating Panes",
|
|
78
|
+
disabled: false,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
item?.action();
|
|
82
|
+
|
|
83
|
+
expect(confirmations).toEqual([]);
|
|
84
|
+
expect(layouts).toHaveLength(1);
|
|
85
|
+
expect(layouts[0]?.floating).toEqual([]);
|
|
86
|
+
expect(layouts[0]?.instances.map((instance) => instance.instanceId)).toEqual(["portfolio-list:main"]);
|
|
87
|
+
expect(layouts[0]?.dockRoot).toEqual({ kind: "pane", instanceId: "portfolio-list:main" });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("confirms before closing all floating panes when dangerous actions are guarded", () => {
|
|
91
|
+
const layouts: LayoutConfig[] = [];
|
|
92
|
+
const confirmations: InlineConfirmOptions[] = [];
|
|
93
|
+
const items = buildCurrentLayoutItems(createLayoutItemsContext({
|
|
94
|
+
confirmDangerousActions: true,
|
|
95
|
+
layouts,
|
|
96
|
+
confirmations,
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
items.find((entry) => entry.id === "layout-close-all-floating")?.action();
|
|
100
|
+
|
|
101
|
+
expect(layouts).toEqual([]);
|
|
102
|
+
expect(confirmations).toHaveLength(1);
|
|
103
|
+
expect(confirmations[0]).toMatchObject({
|
|
104
|
+
confirmId: "layout-close-all-floating",
|
|
105
|
+
title: "Close All Floating Panes",
|
|
106
|
+
confirmLabel: "Close Floating Panes",
|
|
107
|
+
tone: "danger",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
confirmations[0]?.onConfirm();
|
|
111
|
+
|
|
112
|
+
expect(layouts).toHaveLength(1);
|
|
113
|
+
expect(layouts[0]?.floating).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
gridlockAllPanes,
|
|
3
|
+
removeFloatingPanes,
|
|
3
4
|
} from "../../../plugins/pane-manager";
|
|
4
5
|
import {
|
|
5
6
|
DEFAULT_LAYOUT,
|
|
@@ -21,6 +22,8 @@ export function buildCurrentLayoutItems({
|
|
|
21
22
|
state,
|
|
22
23
|
}: LayoutItemsContext): ResultItem[] {
|
|
23
24
|
const layoutHistory = state.layoutHistory[state.config.activeLayoutIndex];
|
|
25
|
+
const floatingPaneCount = currentLayout.floating.length;
|
|
26
|
+
const floatingPaneLabel = floatingPaneCount === 1 ? "floating pane" : "floating panes";
|
|
24
27
|
|
|
25
28
|
return [
|
|
26
29
|
{
|
|
@@ -89,6 +92,36 @@ export function buildCurrentLayoutItems({
|
|
|
89
92
|
closeAll({ revertThemePreview: false });
|
|
90
93
|
},
|
|
91
94
|
},
|
|
95
|
+
{
|
|
96
|
+
id: "layout-close-all-floating",
|
|
97
|
+
label: "Close All Floating Panes",
|
|
98
|
+
detail: floatingPaneCount > 0
|
|
99
|
+
? `Remove ${floatingPaneCount} ${floatingPaneLabel} from the current layout`
|
|
100
|
+
: "No floating panes in the current layout",
|
|
101
|
+
category: "Current Layout",
|
|
102
|
+
kind: "action",
|
|
103
|
+
disabled: floatingPaneCount === 0,
|
|
104
|
+
action: confirmDangerousActions
|
|
105
|
+
? () => {
|
|
106
|
+
if (floatingPaneCount === 0) return;
|
|
107
|
+
openInlineConfirm({
|
|
108
|
+
confirmId: "layout-close-all-floating",
|
|
109
|
+
title: "Close All Floating Panes",
|
|
110
|
+
body: [`Close ${floatingPaneCount} ${floatingPaneLabel}?`],
|
|
111
|
+
confirmLabel: "Close Floating Panes",
|
|
112
|
+
cancelLabel: "Back",
|
|
113
|
+
tone: "danger",
|
|
114
|
+
onConfirm: () => {
|
|
115
|
+
persistLayoutChange(removeFloatingPanes(currentLayout));
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
: () => {
|
|
120
|
+
if (floatingPaneCount === 0) return;
|
|
121
|
+
persistLayoutChange(removeFloatingPanes(currentLayout));
|
|
122
|
+
closeAll({ revertThemePreview: false });
|
|
123
|
+
},
|
|
124
|
+
},
|
|
92
125
|
{
|
|
93
126
|
id: "layout-rename",
|
|
94
127
|
label: "Rename Layout",
|
|
@@ -19,6 +19,7 @@ export type CommandBarListScrollEvent = {
|
|
|
19
19
|
|
|
20
20
|
interface CommandBarListHeaderProps {
|
|
21
21
|
kind: ListScreenState["kind"];
|
|
22
|
+
title: string;
|
|
22
23
|
query: string;
|
|
23
24
|
queryDisplayWidth: number;
|
|
24
25
|
nativePaneChrome: boolean;
|
|
@@ -36,6 +37,7 @@ interface CommandBarListHeaderProps {
|
|
|
36
37
|
|
|
37
38
|
export const CommandBarListHeader = memo(function CommandBarListHeader({
|
|
38
39
|
kind,
|
|
40
|
+
title,
|
|
39
41
|
query,
|
|
40
42
|
queryDisplayWidth,
|
|
41
43
|
nativePaneChrome,
|
|
@@ -66,7 +68,7 @@ export const CommandBarListHeader = memo(function CommandBarListHeader({
|
|
|
66
68
|
value={query}
|
|
67
69
|
onInput={onQueryChange}
|
|
68
70
|
onChange={onQueryChange}
|
|
69
|
-
placeholder={kind === "root" ? "Search" : "Filter"}
|
|
71
|
+
placeholder={kind === "root" ? "Search commands" : title === "Security Description" ? "Search tickers" : "Filter"}
|
|
70
72
|
focused
|
|
71
73
|
width={nativePaneChrome ? "100%" : queryDisplayWidth}
|
|
72
74
|
backgroundColor={nativePaneChrome ? "transparent" : paletteBg}
|
|
@@ -141,7 +141,10 @@ export function buildPaneTemplateItem(options: {
|
|
|
141
141
|
const arg = options.createOptions?.arg;
|
|
142
142
|
|
|
143
143
|
const action = () => {
|
|
144
|
-
if (
|
|
144
|
+
if (
|
|
145
|
+
options.template.shortcut
|
|
146
|
+
&& (options.shortcutExecution || getPaneTemplateArgKind(options.template) === "ticker")
|
|
147
|
+
) {
|
|
145
148
|
options.runPaneTemplateShortcut(options.template, arg);
|
|
146
149
|
return;
|
|
147
150
|
}
|
|
@@ -130,6 +130,7 @@ export function CommandBarPanelBody({
|
|
|
130
130
|
{(visibleListState || currentRoute?.kind === "picker") && visibleListState && (
|
|
131
131
|
<CommandBarListHeader
|
|
132
132
|
kind={visibleListState.kind}
|
|
133
|
+
title={visibleListState.title}
|
|
133
134
|
query={visibleListState.query}
|
|
134
135
|
queryDisplayWidth={queryDisplayWidth}
|
|
135
136
|
nativePaneChrome={nativePaneChrome}
|
|
@@ -6,12 +6,10 @@ import type {
|
|
|
6
6
|
} from "../../../../types/plugin";
|
|
7
7
|
import type { TickerRecord } from "../../../../types/ticker";
|
|
8
8
|
import { fuzzyFilter } from "../../../../utils/fuzzy-search";
|
|
9
|
-
import type { TickerSearchCandidate } from "../../../../tickers/search";
|
|
10
9
|
import { matchPrefix, type Command } from "../../commands/registry";
|
|
11
10
|
import { isCollectionCommand } from "../../helpers";
|
|
12
11
|
import type { ResultItem } from "../../list/model";
|
|
13
12
|
import type { parseRootShortcutIntent } from "./shortcuts";
|
|
14
|
-
import { createQuickLookTickerCandidates } from "../ticker-search/results";
|
|
15
13
|
import type { CommandBarRoute } from "../../workflow/types";
|
|
16
14
|
import { createRootCommandItemBuilder } from "./command-items";
|
|
17
15
|
import { buildRootShortcutItem } from "./shortcut-items";
|
|
@@ -55,7 +53,6 @@ export interface RootResultModelOptions {
|
|
|
55
53
|
getAvailablePaneShortcutTemplates: (query: string) => PaneTemplateDef[];
|
|
56
54
|
hasPaneSettings: (paneId: string) => boolean;
|
|
57
55
|
localTickerSearchResultItems: (query?: string, options?: { category?: string; limit?: number }) => ResultItem[];
|
|
58
|
-
mapTickerSearchCandidateToResultItem: (candidate: TickerSearchCandidate) => ResultItem;
|
|
59
56
|
nonShortcutPaneTemplateItems: (filterQuery?: string) => ResultItem[];
|
|
60
57
|
openModeRoute: (screen: "ticker-search" | "plugins" | "layout", initialQuery?: string) => void;
|
|
61
58
|
paneShortcutItems: (options?: PaneShortcutItemsOptions) => ResultItem[];
|
|
@@ -86,7 +83,6 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
|
|
|
86
83
|
getAvailablePaneShortcutTemplates,
|
|
87
84
|
hasPaneSettings,
|
|
88
85
|
localTickerSearchResultItems,
|
|
89
|
-
mapTickerSearchCandidateToResultItem,
|
|
90
86
|
nonShortcutPaneTemplateItems,
|
|
91
87
|
openModeRoute,
|
|
92
88
|
paneShortcutItems,
|
|
@@ -180,30 +176,6 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
|
|
|
180
176
|
const item = commandToItem(match.command);
|
|
181
177
|
if (item) items.push(item);
|
|
182
178
|
} else if (!rootQuery) {
|
|
183
|
-
const maxDefaultTickers = 5;
|
|
184
|
-
const recentSymbols = state.recentTickers.slice(0, maxDefaultTickers);
|
|
185
|
-
const recentTickers = recentSymbols
|
|
186
|
-
.map((symbol) => state.tickers.get(symbol))
|
|
187
|
-
.filter((ticker): ticker is NonNullable<typeof ticker> => (
|
|
188
|
-
ticker != null && createQuickLookTickerCandidates([ticker]).length > 0
|
|
189
|
-
));
|
|
190
|
-
if (recentTickers.length < maxDefaultTickers) {
|
|
191
|
-
const seen = new Set(recentSymbols);
|
|
192
|
-
for (const ticker of state.tickers.values()) {
|
|
193
|
-
if (recentTickers.length >= maxDefaultTickers) break;
|
|
194
|
-
if (createQuickLookTickerCandidates([ticker]).length === 0) continue;
|
|
195
|
-
if (!seen.has(ticker.metadata.ticker)) recentTickers.push(ticker);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
items.push(...recentTickers.flatMap((ticker) => {
|
|
199
|
-
const candidate = createQuickLookTickerCandidates([ticker])[0];
|
|
200
|
-
return candidate
|
|
201
|
-
? [{
|
|
202
|
-
...mapTickerSearchCandidateToResultItem(candidate),
|
|
203
|
-
category: "Tickers",
|
|
204
|
-
}]
|
|
205
|
-
: [];
|
|
206
|
-
}));
|
|
207
179
|
items.push(...paneShortcutItems());
|
|
208
180
|
for (const command of availableCommands) {
|
|
209
181
|
const item = commandToItem(command);
|
|
@@ -212,12 +184,10 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
|
|
|
212
184
|
items.push(...tickerActionItems());
|
|
213
185
|
items.push(...pluginCommandItems());
|
|
214
186
|
} else {
|
|
215
|
-
const tickerItems = localTickerSearchResultItems(undefined, { category: "Tickers" });
|
|
216
187
|
const commandItems = availableCommands
|
|
217
188
|
.map((command) => commandToItem(command))
|
|
218
189
|
.filter((item): item is ResultItem => item !== null);
|
|
219
190
|
const allItems = [
|
|
220
|
-
...tickerItems,
|
|
221
191
|
...commandItems,
|
|
222
192
|
...buildLayoutItems("", { confirmDangerousActions: true }),
|
|
223
193
|
...buildPaneSettingItems(state.focusedPaneId, rootQuery),
|