gloomberb 0.9.1 → 0.9.3
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 +23 -2
- package/bin/gloomberb +1 -1
- package/package.json +1 -1
- package/src/app.tsx +10 -7
- package/src/capabilities/registry.ts +1 -0
- package/src/capabilities/types.ts +23 -0
- package/src/cli/commands/api.ts +119 -0
- package/src/cli/commands/automation.ts +173 -0
- package/src/cli/commands/command-utils.ts +53 -0
- package/src/cli/commands/market.ts +527 -0
- package/src/cli/commands/overview.ts +222 -0
- package/src/cli/commands/search.ts +39 -0
- package/src/cli/commands/system.ts +553 -0
- package/src/cli/commands/ticker.ts +104 -12
- package/src/cli/context.ts +35 -2
- package/src/cli/entry.ts +51 -0
- package/src/cli/errors.ts +56 -5
- package/src/cli/index.ts +73 -11
- package/src/cli/options.test.ts +38 -0
- package/src/cli/options.ts +95 -0
- package/src/cli/pane-functions/index.ts +10 -1
- package/src/cli/registry.test.ts +40 -0
- package/src/cli/registry.ts +46 -5
- package/src/cli/result.test.ts +82 -0
- package/src/cli/result.ts +149 -0
- package/src/cli/types.ts +11 -0
- package/src/cli.test.ts +11 -11
- package/src/components/chart/comparison-stock-chart/helpers.ts +0 -32
- package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
- package/src/components/chart/comparison-stock-chart/layout-metrics.ts +6 -6
- package/src/components/chart/comparison-stock-chart/legend.tsx +45 -44
- package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
- package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
- package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
- package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
- package/src/components/chart/comparison-stock-chart/view.tsx +56 -6
- package/src/components/chart/core/pane-settings.test.ts +27 -0
- package/src/components/chart/core/pane-settings.ts +60 -1
- package/src/components/chart/core/pointer.ts +2 -0
- package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
- package/src/components/chart/stock-chart/settings.ts +7 -3
- package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
- package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
- package/src/components/command-bar/surface/pane.test.tsx +76 -0
- package/src/components/data-table/view.tsx +13 -2
- package/src/components/feed-data-table/stack-view.tsx +9 -1
- package/src/components/index.ts +1 -1
- package/src/components/input-search-bar.test.tsx +103 -0
- package/src/components/input-search-bar.tsx +16 -0
- package/src/components/layout/detached-pane-shell.tsx +5 -2
- package/src/components/layout/header.tsx +13 -5
- package/src/components/layout/shell/active-drag.ts +5 -1
- package/src/components/layout/shell/drag/runtime.ts +21 -3
- package/src/components/layout/shell/fullscreen.ts +10 -0
- package/src/components/layout/shell/index.test.tsx +395 -2
- package/src/components/layout/shell/index.tsx +128 -2
- package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
- package/src/components/layout/shell/pane/layers.tsx +26 -14
- package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
- package/src/components/layout/shell/shortcuts.ts +4 -0
- package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
- package/src/components/layout/shell/window-mode/index.ts +11 -3
- package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
- package/src/components/layout/status-bar.test.tsx +105 -0
- package/src/components/layout/status-bar.tsx +32 -6
- package/src/components/layout/transient-layout.tsx +57 -0
- package/src/components/layout/window-controls.tsx +19 -10
- package/src/components/layout/window-edit/mode.test.ts +76 -5
- package/src/components/layout/window-edit/mode.ts +47 -2
- package/src/components/layout/window-edit/presentation.ts +2 -1
- package/src/components/price-performance.tsx +183 -0
- package/src/core/app-services.ts +7 -4
- package/src/data/config/store/index.ts +1 -2
- package/src/market-data/performance.test.ts +54 -0
- package/src/market-data/performance.ts +109 -0
- package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
- package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
- package/src/plugins/builtin/comparison-chart/index.test.tsx +218 -15
- package/src/plugins/builtin/congress-trades/detail.tsx +204 -68
- package/src/plugins/builtin/congress-trades/footer.ts +14 -8
- package/src/plugins/builtin/congress-trades/keyboard.ts +4 -23
- package/src/plugins/builtin/congress-trades/model.ts +29 -0
- package/src/plugins/builtin/congress-trades/pane.tsx +8 -13
- package/src/plugins/builtin/congress-trades/table.tsx +10 -1
- package/src/plugins/builtin/news/wire/read-state.ts +9 -32
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
- package/src/plugins/builtin/shared/read-state.ts +43 -0
- package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
- package/src/plugins/builtin/substack/article-detail.tsx +39 -15
- package/src/plugins/builtin/substack/article-stack.tsx +6 -2
- package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
- package/src/plugins/builtin/substack/pane.tsx +8 -3
- package/src/plugins/builtin/substack/read-state.ts +55 -0
- package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
- package/src/plugins/builtin/ticker-detail/overview/model.ts +0 -14
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -0
- package/src/plugins/pane-manager/docking.ts +9 -2
- package/src/plugins/pane-manager/queries.ts +8 -2
- package/src/plugins/prediction-markets/controller/index.ts +2 -0
- package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
- package/src/plugins/prediction-markets/pane.test.tsx +32 -0
- package/src/plugins/prediction-markets/pane.tsx +21 -1
- package/src/plugins/prediction-markets/test-helpers.tsx +1 -0
- package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +9 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/window-controls.ts +11 -2
- package/src/renderers/electrobun/bun/desktop/window-style.ts +27 -1
- package/src/renderers/electrobun/bun/desktop/windows-custom-chrome.ts +58 -0
- package/src/renderers/electrobun/bun/desktop/windows-icons.ts +11 -64
- package/src/renderers/electrobun/bun/desktop/windows-native.ts +75 -0
- package/src/renderers/electrobun/bun/index.ts +22 -2
- package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
- package/src/renderers/electrobun/bun/window/focus.ts +5 -0
- package/src/renderers/electrobun/shared/protocol.ts +1 -0
- package/src/renderers/electrobun/view/app-services.ts +6 -4
- package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
- package/src/renderers/electrobun/view/host/text.test.ts +22 -0
- package/src/renderers/electrobun/view/host/text.tsx +24 -20
- package/src/renderers/electrobun/view/main.tsx +2 -1
- package/src/renderers/electrobun/view/styles.css +9 -8
- package/src/renderers/electrobun/view/ui-host.tsx +102 -77
- package/src/renderers/opentui/start.tsx +13 -5
- package/src/state/app/input-capture.ts +12 -0
- package/src/types/plugin.ts +28 -0
- package/src/utils/cli-output.ts +6 -0
- package/src/utils/search-focus-navigation.ts +24 -0
- package/src/version.ts +1 -1
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
DEFAULT_LIVE_CHART_REFRESH_INTERVAL_MS,
|
|
4
4
|
useChartQueries,
|
|
5
5
|
} from "../../../market-data/hooks";
|
|
6
|
-
import {
|
|
6
|
+
import type { ChartRequest, InstrumentRef } from "../../../market-data/request-types";
|
|
7
|
+
import { buildChartKey, resolveEntryData } from "../../../market-data/selectors";
|
|
7
8
|
import { getSharedMarketData } from "../../../plugins/registry";
|
|
8
9
|
import { blendHex, colors, getComparisonSeriesColor } from "../../../theme/colors";
|
|
9
10
|
import type { BrokerContractRef } from "../../../types/instrument";
|
|
@@ -24,6 +25,8 @@ import type {
|
|
|
24
25
|
ComparisonChartViewState,
|
|
25
26
|
} from "../core/types";
|
|
26
27
|
|
|
28
|
+
const COMPARISON_PERFORMANCE_RANGE = "5Y" as const;
|
|
29
|
+
|
|
27
30
|
export interface ComparisonChartSymbolSource {
|
|
28
31
|
symbol: string;
|
|
29
32
|
currency: string | undefined;
|
|
@@ -40,6 +43,16 @@ interface UseComparisonChartRenderDataOptions {
|
|
|
40
43
|
viewState: ComparisonChartViewState;
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
function instrumentFromSymbolSource(source: ComparisonChartSymbolSource): InstrumentRef {
|
|
47
|
+
return {
|
|
48
|
+
symbol: source.symbol,
|
|
49
|
+
exchange: source.exchange,
|
|
50
|
+
brokerId: source.brokerId,
|
|
51
|
+
brokerInstanceId: source.brokerInstanceId,
|
|
52
|
+
instrument: source.instrument,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
export function useComparisonChartRenderData({
|
|
44
57
|
symbolSources,
|
|
45
58
|
viewState,
|
|
@@ -143,21 +156,25 @@ export function useComparisonChartRenderData({
|
|
|
143
156
|
);
|
|
144
157
|
const chartRequests = useMemo(() => (
|
|
145
158
|
symbolSources.map((source) => ({
|
|
146
|
-
instrument:
|
|
147
|
-
symbol: source.symbol,
|
|
148
|
-
exchange: source.exchange,
|
|
149
|
-
brokerId: source.brokerId,
|
|
150
|
-
brokerInstanceId: source.brokerInstanceId,
|
|
151
|
-
instrument: source.instrument,
|
|
152
|
-
},
|
|
159
|
+
instrument: instrumentFromSymbolSource(source),
|
|
153
160
|
bufferRange: viewState.bufferRange,
|
|
154
161
|
granularity: effectiveResolution === "auto" ? "range" as const : "resolution" as const,
|
|
155
162
|
resolution: effectiveResolution === "auto" ? undefined : effectiveResolution,
|
|
156
163
|
}))
|
|
157
164
|
), [effectiveResolution, symbolSources, viewState.bufferRange]);
|
|
165
|
+
const performanceRequests = useMemo<ChartRequest[]>(() => (
|
|
166
|
+
symbolSources.map((source) => ({
|
|
167
|
+
instrument: instrumentFromSymbolSource(source),
|
|
168
|
+
bufferRange: COMPARISON_PERFORMANCE_RANGE,
|
|
169
|
+
granularity: "range",
|
|
170
|
+
}))
|
|
171
|
+
), [symbolSources]);
|
|
158
172
|
const chartEntries = useChartQueries(chartRequests, {
|
|
159
173
|
refreshIntervalMs: DEFAULT_LIVE_CHART_REFRESH_INTERVAL_MS,
|
|
160
174
|
});
|
|
175
|
+
const performanceEntries = useChartQueries(performanceRequests, {
|
|
176
|
+
refreshIntervalMs: DEFAULT_LIVE_CHART_REFRESH_INTERVAL_MS,
|
|
177
|
+
});
|
|
161
178
|
const entryStates = useMemo(() => chartRequests.map((request) => (
|
|
162
179
|
resolveChartBodyState(chartEntries.get(buildChartKey(request)), (value) => Array.isArray(value) && value.length > 0, "No chart data yet.")
|
|
163
180
|
)), [chartEntries, chartRequests]);
|
|
@@ -182,6 +199,17 @@ export function useComparisonChartRenderData({
|
|
|
182
199
|
points,
|
|
183
200
|
};
|
|
184
201
|
}), [chartEntries, chartRequests, symbolSources]);
|
|
202
|
+
const performanceHistoryBySymbol = useMemo(() => {
|
|
203
|
+
const bySymbol = new Map<string, PricePoint[]>();
|
|
204
|
+
symbolSources.forEach((source, index) => {
|
|
205
|
+
const request = performanceRequests[index];
|
|
206
|
+
const history = request ? resolveEntryData(performanceEntries.get(buildChartKey(request))) : null;
|
|
207
|
+
if (history?.length) {
|
|
208
|
+
bySymbol.set(source.symbol, history);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
return bySymbol;
|
|
212
|
+
}, [performanceEntries, performanceRequests, symbolSources]);
|
|
185
213
|
|
|
186
214
|
return {
|
|
187
215
|
availableManualResolutions,
|
|
@@ -192,6 +220,7 @@ export function useComparisonChartRenderData({
|
|
|
192
220
|
isUpdating,
|
|
193
221
|
resolutionChips,
|
|
194
222
|
selectionSupportMap,
|
|
223
|
+
performanceHistoryBySymbol,
|
|
195
224
|
series,
|
|
196
225
|
supportMap,
|
|
197
226
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { useEffect, useRef, type Dispatch, type MutableRefObject, type SetStateAction } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, type Dispatch, type MutableRefObject, type SetStateAction } from "react";
|
|
2
2
|
import type { BoxRenderable, NativeRendererHost } from "../../../ui";
|
|
3
|
-
import {
|
|
3
|
+
import { useAppDispatch, usePaneInstanceId } from "../../../state/app/context";
|
|
4
|
+
import { buildDisplayCursorState, type MouseInteractionEvent } from "../core/pointer";
|
|
4
5
|
import type { ComparisonChartViewState } from "../core/types";
|
|
5
6
|
import { useChartDisplayCursor, useChartDisplayCursorLayoutRemap } from "../display-cursor";
|
|
6
7
|
import { clamp } from "./helpers";
|
|
@@ -10,6 +11,7 @@ interface UseComparisonChartSelectionRuntimeOptions {
|
|
|
10
11
|
chartWidth: number;
|
|
11
12
|
cursorX: number | null;
|
|
12
13
|
cursorY: number | null;
|
|
14
|
+
focused: boolean;
|
|
13
15
|
renderer: NativeRendererHost;
|
|
14
16
|
setViewState: Dispatch<SetStateAction<ComparisonChartViewState>>;
|
|
15
17
|
symbols: string[];
|
|
@@ -20,6 +22,7 @@ export function useComparisonChartSelectionRuntime({
|
|
|
20
22
|
chartWidth,
|
|
21
23
|
cursorX: viewCursorX,
|
|
22
24
|
cursorY: viewCursorY,
|
|
25
|
+
focused,
|
|
23
26
|
renderer,
|
|
24
27
|
setViewState,
|
|
25
28
|
symbols,
|
|
@@ -28,6 +31,7 @@ export function useComparisonChartSelectionRuntime({
|
|
|
28
31
|
displayCursor: ReturnType<typeof useChartDisplayCursor>["displayCursor"];
|
|
29
32
|
displayCursorX: number | null;
|
|
30
33
|
displayCursorY: number | null;
|
|
34
|
+
focusPaneForMouseInteraction: (event: MouseInteractionEvent | null | undefined) => void;
|
|
31
35
|
mouseCrosshairDisabledRef: MutableRefObject<boolean>;
|
|
32
36
|
plotRef: MutableRefObject<BoxRenderable | null>;
|
|
33
37
|
scrollPanCellRemainderRef: MutableRefObject<number>;
|
|
@@ -35,6 +39,8 @@ export function useComparisonChartSelectionRuntime({
|
|
|
35
39
|
setSelectedSymbol: (symbol: string) => void;
|
|
36
40
|
updateDisplayCursorTarget: ReturnType<typeof useChartDisplayCursor>["updateDisplayCursorTarget"];
|
|
37
41
|
} {
|
|
42
|
+
const dispatch = useAppDispatch();
|
|
43
|
+
const paneId = usePaneInstanceId();
|
|
38
44
|
const plotRef = useRef<BoxRenderable | null>(null);
|
|
39
45
|
const mouseCrosshairDisabledRef = useRef(false);
|
|
40
46
|
const scrollPanCellRemainderRef = useRef(0);
|
|
@@ -52,6 +58,16 @@ export function useComparisonChartSelectionRuntime({
|
|
|
52
58
|
const displayCursorX = displayCursor.cellX !== null ? clamp(displayCursor.cellX, 0, chartWidth - 1) : null;
|
|
53
59
|
const displayCursorY = displayCursor.cellY !== null ? clamp(displayCursor.cellY, 0, chartHeight - 1) : null;
|
|
54
60
|
|
|
61
|
+
const focusPaneForMouseInteraction = useCallback((
|
|
62
|
+
event: MouseInteractionEvent | null | undefined,
|
|
63
|
+
) => {
|
|
64
|
+
event?.stopPropagation?.();
|
|
65
|
+
event?.preventDefault?.();
|
|
66
|
+
if (!focused) {
|
|
67
|
+
dispatch({ type: "FOCUS_PANE", paneId });
|
|
68
|
+
}
|
|
69
|
+
}, [dispatch, focused, paneId]);
|
|
70
|
+
|
|
55
71
|
const setSelectedSymbol = (symbol: string) => {
|
|
56
72
|
setViewState((current) => (
|
|
57
73
|
current.selectedSymbol === symbol
|
|
@@ -101,6 +117,7 @@ export function useComparisonChartSelectionRuntime({
|
|
|
101
117
|
displayCursor,
|
|
102
118
|
displayCursorX,
|
|
103
119
|
displayCursorY,
|
|
120
|
+
focusPaneForMouseInteraction,
|
|
104
121
|
mouseCrosshairDisabledRef,
|
|
105
122
|
plotRef,
|
|
106
123
|
scrollPanCellRemainderRef,
|
|
@@ -24,17 +24,21 @@ export function useComparisonChartSymbolSources(symbols: string[]): {
|
|
|
24
24
|
const marketFinancials = useTickerFinancialsMap(stableTickers);
|
|
25
25
|
const symbolSources = useMemo<ComparisonChartSymbolSource[]>(() => stableSymbols.map((symbol) => {
|
|
26
26
|
const ticker = tickers.get(symbol) ?? null;
|
|
27
|
-
const
|
|
27
|
+
const marketFinancial = marketFinancials.get(symbol) ?? null;
|
|
28
|
+
const storedFinancial = financials.get(symbol) ?? null;
|
|
28
29
|
const instrument = ticker?.metadata.broker_contracts?.[0] ?? null;
|
|
30
|
+
const quote = marketFinancial?.quote ?? storedFinancial?.quote;
|
|
29
31
|
return {
|
|
30
32
|
symbol,
|
|
31
|
-
currency:
|
|
32
|
-
quote
|
|
33
|
+
currency: quote?.currency ?? ticker?.metadata.currency,
|
|
34
|
+
quote,
|
|
33
35
|
exchange: ticker?.metadata.exchange ?? "",
|
|
34
36
|
brokerId: instrument?.brokerId,
|
|
35
37
|
brokerInstanceId: instrument?.brokerInstanceId,
|
|
36
38
|
instrument,
|
|
37
|
-
priceHistory:
|
|
39
|
+
priceHistory: marketFinancial?.priceHistory?.length
|
|
40
|
+
? marketFinancial.priceHistory
|
|
41
|
+
: storedFinancial?.priceHistory ?? [],
|
|
38
42
|
};
|
|
39
43
|
}), [financials, marketFinancials, stableSymbols, tickers]);
|
|
40
44
|
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type ComparisonChartRenderMode,
|
|
13
13
|
type TimeRange,
|
|
14
14
|
} from "../core/types";
|
|
15
|
+
import type { MouseInteractionEvent } from "../core/pointer";
|
|
15
16
|
|
|
16
17
|
const MODE_CHIPS: Record<ComparisonChartRenderMode, string> = {
|
|
17
18
|
area: "A",
|
|
@@ -22,6 +23,7 @@ interface ComparisonChartToolbarProps {
|
|
|
22
23
|
activePreset: TimeRange | null;
|
|
23
24
|
availableManualResolutions: ManualChartResolution[];
|
|
24
25
|
effectiveResolution: ChartResolution;
|
|
26
|
+
focusPaneForMouseInteraction: (event: MouseInteractionEvent | null | undefined) => void;
|
|
25
27
|
isUpdating: boolean;
|
|
26
28
|
onRangeSelect: (range: TimeRange) => void;
|
|
27
29
|
onRenderModeSelect: (mode: ComparisonChartRenderMode) => void;
|
|
@@ -36,6 +38,7 @@ export function ComparisonChartToolbar({
|
|
|
36
38
|
activePreset,
|
|
37
39
|
availableManualResolutions,
|
|
38
40
|
effectiveResolution,
|
|
41
|
+
focusPaneForMouseInteraction,
|
|
39
42
|
isUpdating,
|
|
40
43
|
onRangeSelect,
|
|
41
44
|
onRenderModeSelect,
|
|
@@ -54,7 +57,8 @@ export function ComparisonChartToolbar({
|
|
|
54
57
|
key={range}
|
|
55
58
|
fg={activePreset === range ? colors.textBright : (isRangePresetSupported(range, availableManualResolutions) ? colors.textDim : colors.textMuted)}
|
|
56
59
|
attributes={activePreset === range ? TextAttributes.BOLD : 0}
|
|
57
|
-
onMouseDown={() => {
|
|
60
|
+
onMouseDown={(event: any) => {
|
|
61
|
+
focusPaneForMouseInteraction(event);
|
|
58
62
|
if (isRangePresetSupported(range, availableManualResolutions)) {
|
|
59
63
|
onRangeSelect(range);
|
|
60
64
|
}
|
|
@@ -73,7 +77,10 @@ export function ComparisonChartToolbar({
|
|
|
73
77
|
key={resolution}
|
|
74
78
|
fg={effectiveResolution === resolution ? colors.textBright : colors.textDim}
|
|
75
79
|
attributes={effectiveResolution === resolution ? TextAttributes.BOLD : 0}
|
|
76
|
-
onMouseDown={() =>
|
|
80
|
+
onMouseDown={(event: any) => {
|
|
81
|
+
focusPaneForMouseInteraction(event);
|
|
82
|
+
onResolutionSelect(resolution);
|
|
83
|
+
}}
|
|
77
84
|
>
|
|
78
85
|
{getChartResolutionLabel(resolution)}
|
|
79
86
|
</Text>
|
|
@@ -89,7 +96,10 @@ export function ComparisonChartToolbar({
|
|
|
89
96
|
key={mode}
|
|
90
97
|
fg={renderMode === mode ? colors.textBright : colors.textDim}
|
|
91
98
|
attributes={renderMode === mode ? TextAttributes.BOLD : 0}
|
|
92
|
-
onMouseDown={() =>
|
|
99
|
+
onMouseDown={(event: any) => {
|
|
100
|
+
focusPaneForMouseInteraction(event);
|
|
101
|
+
onRenderModeSelect(mode);
|
|
102
|
+
}}
|
|
93
103
|
>
|
|
94
104
|
{MODE_CHIPS[mode]}
|
|
95
105
|
</Text>
|
|
@@ -5,6 +5,11 @@ import { blendHex, colors } from "../../../theme/colors";
|
|
|
5
5
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
6
6
|
import { usePaneSettingValue } from "../../../state/app/context";
|
|
7
7
|
import { useQuoteStreaming } from "../../../state/hooks/quote-streaming";
|
|
8
|
+
import {
|
|
9
|
+
appendQuoteToPriceReturnHistory,
|
|
10
|
+
buildPriceReturnFields,
|
|
11
|
+
type PriceReturnField,
|
|
12
|
+
} from "../../../market-data/performance";
|
|
8
13
|
import type { QuoteSubscriptionTarget } from "../../../types/data-provider";
|
|
9
14
|
import {
|
|
10
15
|
type ChartRendererPreference,
|
|
@@ -170,6 +175,27 @@ function useComparisonChartQuoteStreaming({
|
|
|
170
175
|
useQuoteStreaming(streamingTargets);
|
|
171
176
|
}
|
|
172
177
|
|
|
178
|
+
function getOneYearReturn(fields: readonly PriceReturnField[]): number | null {
|
|
179
|
+
return fields.find((field) => field.id === "1Y")?.value ?? null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function sortSymbolsByOneYearReturn(
|
|
183
|
+
symbols: readonly string[],
|
|
184
|
+
performanceBySymbol: ReadonlyMap<string, PriceReturnField[]>,
|
|
185
|
+
): string[] {
|
|
186
|
+
const originalIndex = new Map(symbols.map((symbol, index) => [symbol, index]));
|
|
187
|
+
return [...symbols].sort((left, right) => {
|
|
188
|
+
const leftReturn = getOneYearReturn(performanceBySymbol.get(left) ?? []);
|
|
189
|
+
const rightReturn = getOneYearReturn(performanceBySymbol.get(right) ?? []);
|
|
190
|
+
if (leftReturn != null && rightReturn != null && leftReturn !== rightReturn) {
|
|
191
|
+
return rightReturn - leftReturn;
|
|
192
|
+
}
|
|
193
|
+
if (leftReturn != null && rightReturn == null) return -1;
|
|
194
|
+
if (leftReturn == null && rightReturn != null) return 1;
|
|
195
|
+
return (originalIndex.get(left) ?? 0) - (originalIndex.get(right) ?? 0);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
173
199
|
function ComparisonStockChartView({
|
|
174
200
|
paneId,
|
|
175
201
|
width,
|
|
@@ -244,12 +270,33 @@ function ComparisonStockChartView({
|
|
|
244
270
|
isUpdating,
|
|
245
271
|
resolutionChips,
|
|
246
272
|
selectionSupportMap,
|
|
273
|
+
performanceHistoryBySymbol,
|
|
247
274
|
series,
|
|
248
275
|
supportMap,
|
|
249
276
|
} = useComparisonChartRenderData({
|
|
250
277
|
symbolSources,
|
|
251
278
|
viewState,
|
|
252
279
|
});
|
|
280
|
+
const performanceBySymbol = useMemo(() => {
|
|
281
|
+
const bySymbol = new Map<string, PriceReturnField[]>();
|
|
282
|
+
for (const source of symbolSources) {
|
|
283
|
+
const baselineHistory = performanceHistoryBySymbol.get(source.symbol) ?? [];
|
|
284
|
+
const sourceHistory = appendQuoteToPriceReturnHistory(
|
|
285
|
+
baselineHistory.length >= 2 ? baselineHistory : source.priceHistory,
|
|
286
|
+
source.quote,
|
|
287
|
+
);
|
|
288
|
+
const fallbackHistory = series.find((entry) => entry.symbol === source.symbol)?.points ?? [];
|
|
289
|
+
bySymbol.set(
|
|
290
|
+
source.symbol,
|
|
291
|
+
buildPriceReturnFields(sourceHistory.length >= 2 ? sourceHistory : fallbackHistory),
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
return bySymbol;
|
|
295
|
+
}, [performanceHistoryBySymbol, series, symbolSources]);
|
|
296
|
+
const summarySymbols = useMemo(
|
|
297
|
+
() => sortSymbolsByOneYearReturn(symbols, performanceBySymbol),
|
|
298
|
+
[performanceBySymbol, symbols],
|
|
299
|
+
);
|
|
253
300
|
|
|
254
301
|
useEffect(() => {
|
|
255
302
|
if (symbols.includes(viewState.selectedSymbol ?? "")) return;
|
|
@@ -298,6 +345,7 @@ function ComparisonStockChartView({
|
|
|
298
345
|
displayCursor,
|
|
299
346
|
displayCursorX,
|
|
300
347
|
displayCursorY,
|
|
348
|
+
focusPaneForMouseInteraction,
|
|
301
349
|
mouseCrosshairDisabledRef,
|
|
302
350
|
plotRef,
|
|
303
351
|
scrollPanCellRemainderRef,
|
|
@@ -309,9 +357,10 @@ function ComparisonStockChartView({
|
|
|
309
357
|
chartWidth,
|
|
310
358
|
cursorX: viewState.cursorX,
|
|
311
359
|
cursorY: viewState.cursorY,
|
|
360
|
+
focused,
|
|
312
361
|
renderer,
|
|
313
362
|
setViewState,
|
|
314
|
-
symbols,
|
|
363
|
+
symbols: summarySymbols,
|
|
315
364
|
});
|
|
316
365
|
const {
|
|
317
366
|
activePreset,
|
|
@@ -352,7 +401,6 @@ function ComparisonStockChartView({
|
|
|
352
401
|
result,
|
|
353
402
|
staticScene,
|
|
354
403
|
timeAxisLabel,
|
|
355
|
-
visiblePriceRange,
|
|
356
404
|
} = useComparisonChartRenderOutput({
|
|
357
405
|
axisMode,
|
|
358
406
|
chartColors,
|
|
@@ -386,7 +434,7 @@ function ComparisonStockChartView({
|
|
|
386
434
|
setResolution,
|
|
387
435
|
setViewState,
|
|
388
436
|
supportMap,
|
|
389
|
-
symbols,
|
|
437
|
+
symbols: summarySymbols,
|
|
390
438
|
updateDisplayCursorTarget,
|
|
391
439
|
viewState,
|
|
392
440
|
visibleDateWindow,
|
|
@@ -403,6 +451,7 @@ function ComparisonStockChartView({
|
|
|
403
451
|
chartWidth,
|
|
404
452
|
commitSelectionCursor,
|
|
405
453
|
expandBufferRange,
|
|
454
|
+
focusPaneForMouseInteraction,
|
|
406
455
|
mouseCrosshairDisabledRef,
|
|
407
456
|
plotRef,
|
|
408
457
|
pointCount: projection.dates.length,
|
|
@@ -479,15 +528,15 @@ function ComparisonStockChartView({
|
|
|
479
528
|
legend={(
|
|
480
529
|
<ComparisonChartLegend
|
|
481
530
|
legendActiveIndex={legendActiveIndex}
|
|
482
|
-
legendColumns={legendColumns}
|
|
483
531
|
legendItemWidth={legendItemWidth}
|
|
484
532
|
legendRows={legendRows}
|
|
533
|
+
onFocusInteraction={focusPaneForMouseInteraction}
|
|
485
534
|
onOpenSymbol={onOpenSymbol}
|
|
486
535
|
onSelectSymbol={setSelectedSymbol}
|
|
536
|
+
performanceBySymbol={performanceBySymbol}
|
|
487
537
|
projection={projection}
|
|
488
538
|
selectedSymbol={viewState.selectedSymbol}
|
|
489
|
-
symbols={
|
|
490
|
-
visiblePriceRange={visiblePriceRange}
|
|
539
|
+
symbols={summarySymbols}
|
|
491
540
|
/>
|
|
492
541
|
)}
|
|
493
542
|
plotBitmaps={plotBitmaps}
|
|
@@ -502,6 +551,7 @@ function ComparisonStockChartView({
|
|
|
502
551
|
activePreset={activePreset}
|
|
503
552
|
availableManualResolutions={availableManualResolutions}
|
|
504
553
|
effectiveResolution={effectiveResolution}
|
|
554
|
+
focusPaneForMouseInteraction={focusPaneForMouseInteraction}
|
|
505
555
|
isUpdating={isUpdating}
|
|
506
556
|
onRangeSelect={setRangePreset}
|
|
507
557
|
onRenderModeSelect={(mode) => setViewState((current) => ({ ...current, renderMode: mode }))}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
createDefaultConfig,
|
|
4
|
+
findPaneInstance,
|
|
5
|
+
} from "../../../types/config";
|
|
2
6
|
import {
|
|
3
7
|
consumeStoredChartSelectionChange,
|
|
8
|
+
createChartRenderModeConfig,
|
|
4
9
|
createStoredChartSelectionSyncState,
|
|
5
10
|
markStoredChartSelectionLocallyApplied,
|
|
6
11
|
} from "./pane-settings";
|
|
@@ -22,4 +27,26 @@ describe("chart pane setting sync", () => {
|
|
|
22
27
|
expect(consumeStoredChartSelectionChange(state, "5Y", "1wk")).toBe(true);
|
|
23
28
|
expect(state.lastAppliedKey).toBe("5Y:1wk");
|
|
24
29
|
});
|
|
30
|
+
|
|
31
|
+
test("persists chart render mode as both pane state and global default", () => {
|
|
32
|
+
const config = createDefaultConfig("/tmp/gloomberb-chart-mode-test");
|
|
33
|
+
|
|
34
|
+
const nextConfig = createChartRenderModeConfig({
|
|
35
|
+
activePanel: "right",
|
|
36
|
+
config,
|
|
37
|
+
focusedPaneId: "ticker-detail:main",
|
|
38
|
+
paneState: {},
|
|
39
|
+
}, "ticker-detail:main", "candles");
|
|
40
|
+
|
|
41
|
+
expect(nextConfig.chartPreferences.defaultRenderMode).toBe("candles");
|
|
42
|
+
expect(findPaneInstance(nextConfig.layout, "ticker-detail:main")?.settings).toMatchObject({
|
|
43
|
+
chartRangePreset: "5Y",
|
|
44
|
+
chartRenderMode: "candles",
|
|
45
|
+
chartResolution: "auto",
|
|
46
|
+
});
|
|
47
|
+
expect(findPaneInstance(
|
|
48
|
+
nextConfig.layouts[nextConfig.activeLayoutIndex]!.layout,
|
|
49
|
+
"ticker-detail:main",
|
|
50
|
+
)?.settings?.chartRenderMode).toBe("candles");
|
|
51
|
+
});
|
|
25
52
|
});
|
|
@@ -7,7 +7,18 @@ import {
|
|
|
7
7
|
syncConfigActiveLayoutState,
|
|
8
8
|
} from "../../../state/app/context";
|
|
9
9
|
import { scheduleConfigSave } from "../../../state/config-save-scheduler";
|
|
10
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
findPaneInstance,
|
|
12
|
+
type AppConfig,
|
|
13
|
+
} from "../../../types/config";
|
|
14
|
+
import type { ChartRenderMode, ChartResolution, TimeRange } from "./types";
|
|
15
|
+
|
|
16
|
+
interface ChartRenderModePersistenceState {
|
|
17
|
+
activePanel: "left" | "right";
|
|
18
|
+
config: AppConfig;
|
|
19
|
+
focusedPaneId: string | null;
|
|
20
|
+
paneState: Record<string, Record<string, unknown>>;
|
|
21
|
+
}
|
|
11
22
|
|
|
12
23
|
export interface StoredChartSelectionSyncState {
|
|
13
24
|
lastAppliedKey: string;
|
|
@@ -80,3 +91,51 @@ export function usePersistChartControlSelection(rangePresetKey: string): (
|
|
|
80
91
|
scheduleConfigSave(nextConfig);
|
|
81
92
|
};
|
|
82
93
|
}
|
|
94
|
+
|
|
95
|
+
export function createChartRenderModeConfig(
|
|
96
|
+
currentState: ChartRenderModePersistenceState,
|
|
97
|
+
paneId: string,
|
|
98
|
+
renderMode: ChartRenderMode,
|
|
99
|
+
): AppConfig {
|
|
100
|
+
const currentPane = findPaneInstance(currentState.config.layout, paneId);
|
|
101
|
+
const layout = currentPane
|
|
102
|
+
? setPaneSettings(currentState.config.layout, paneId, {
|
|
103
|
+
...(currentPane.settings ?? {}),
|
|
104
|
+
chartRenderMode: renderMode,
|
|
105
|
+
})
|
|
106
|
+
: currentState.config.layout;
|
|
107
|
+
return syncConfigActiveLayoutState(
|
|
108
|
+
{
|
|
109
|
+
...currentState.config,
|
|
110
|
+
chartPreferences: {
|
|
111
|
+
...currentState.config.chartPreferences,
|
|
112
|
+
defaultRenderMode: renderMode,
|
|
113
|
+
},
|
|
114
|
+
layout,
|
|
115
|
+
},
|
|
116
|
+
currentState.paneState,
|
|
117
|
+
currentState.focusedPaneId,
|
|
118
|
+
currentState.activePanel,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function usePersistChartRenderMode(): (renderMode: ChartRenderMode) => void {
|
|
123
|
+
const dispatch = useAppDispatch();
|
|
124
|
+
const stateRef = useAppStateRef();
|
|
125
|
+
const paneId = usePaneInstanceId();
|
|
126
|
+
|
|
127
|
+
return (renderMode) => {
|
|
128
|
+
const currentState = stateRef.current;
|
|
129
|
+
const currentPane = findPaneInstance(currentState.config.layout, paneId);
|
|
130
|
+
if (
|
|
131
|
+
currentState.config.chartPreferences.defaultRenderMode === renderMode
|
|
132
|
+
&& currentPane?.settings?.chartRenderMode === renderMode
|
|
133
|
+
) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const nextConfig = createChartRenderModeConfig(currentState, paneId, renderMode);
|
|
138
|
+
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
139
|
+
scheduleConfigSave(nextConfig);
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -31,6 +31,8 @@ export interface ChartMouseEvent {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export type MouseInteractionEvent = Pick<ChartMouseEvent, "stopPropagation" | "preventDefault">;
|
|
35
|
+
|
|
34
36
|
export function consumeChartMouseEvent(event: Pick<ChartMouseEvent, "stopPropagation" | "preventDefault">): void {
|
|
35
37
|
event.stopPropagation?.();
|
|
36
38
|
event.preventDefault?.();
|
|
@@ -221,12 +221,12 @@ export function useResolvedStockChartRuntime({
|
|
|
221
221
|
pendingCanonicalResetRef: interactionRuntime.pendingCanonicalResetRef,
|
|
222
222
|
pendingExpansionRef: interactionRuntime.pendingExpansionRef,
|
|
223
223
|
persistChartControls: settings.persistChartControls,
|
|
224
|
+
persistChartRenderMode: settings.persistChartRenderMode,
|
|
224
225
|
renderedResolution: dataRuntime.renderedResolution,
|
|
225
226
|
selectionSupportMap: resolutionRuntime.selectionSupportMap,
|
|
226
227
|
setPendingAutoWindowOverride: dataRuntime.setPendingAutoWindowOverride,
|
|
227
228
|
setRenderedAutoView: dataRuntime.setRenderedAutoView,
|
|
228
229
|
setRequestedResolution: settings.setRequestedResolution,
|
|
229
|
-
setStoredRenderMode: settings.setStoredRenderMode,
|
|
230
230
|
setViewState: settings.setViewState,
|
|
231
231
|
storedRangePreset: settings.storedRangePreset,
|
|
232
232
|
storedRenderMode: settings.storedRenderMode,
|
|
@@ -3,7 +3,10 @@ import {
|
|
|
3
3
|
usePaneInstance,
|
|
4
4
|
usePaneSettingValue,
|
|
5
5
|
} from "../../../state/app/context";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
usePersistChartControlSelection,
|
|
8
|
+
usePersistChartRenderMode,
|
|
9
|
+
} from "../core/pane-settings";
|
|
7
10
|
import type { IndicatorConfig } from "../indicators/types";
|
|
8
11
|
import {
|
|
9
12
|
DEFAULT_TICKER_CHART_RANGE_PRESET,
|
|
@@ -30,8 +33,9 @@ export function useStockChartSettings({
|
|
|
30
33
|
const pane = usePaneInstance();
|
|
31
34
|
const [storedRangePreset] = usePaneSettingValue("chartRangePreset", DEFAULT_TICKER_CHART_RANGE_PRESET);
|
|
32
35
|
const [storedResolution] = usePaneSettingValue<ChartResolution>("chartResolution", DEFAULT_TICKER_CHART_RESOLUTION);
|
|
33
|
-
const [storedRenderMode
|
|
36
|
+
const [storedRenderMode] = usePaneSettingValue<ChartRenderMode>("chartRenderMode", defaultRenderMode);
|
|
34
37
|
const persistChartControls = usePersistChartControlSelection("chartRangePreset");
|
|
38
|
+
const persistChartRenderMode = usePersistChartRenderMode();
|
|
35
39
|
const [viewState, setViewState] = useState<StockChartViewportState>({
|
|
36
40
|
presetRange: compact ? "1Y" : storedRangePreset,
|
|
37
41
|
bufferRange: compact ? "1Y" : storedRangePreset,
|
|
@@ -67,7 +71,7 @@ export function useStockChartSettings({
|
|
|
67
71
|
persistChartControls,
|
|
68
72
|
requestedResolution,
|
|
69
73
|
setRequestedResolution,
|
|
70
|
-
|
|
74
|
+
persistChartRenderMode,
|
|
71
75
|
setViewState,
|
|
72
76
|
storedRangePreset,
|
|
73
77
|
storedRenderMode,
|
|
@@ -67,10 +67,10 @@ interface UseStockChartViewportRuntimeOptions {
|
|
|
67
67
|
persistChartControls: (range: TimeRange, resolution: ChartResolution) => void;
|
|
68
68
|
renderedResolution: ChartResolution;
|
|
69
69
|
selectionSupportMap: ReadonlyMap<ManualChartResolution, TimeRange>;
|
|
70
|
+
persistChartRenderMode: (mode: ChartRenderMode) => void;
|
|
70
71
|
setPendingAutoWindowOverride: Dispatch<SetStateAction<DateWindowRange | null>>;
|
|
71
72
|
setRenderedAutoView: Dispatch<SetStateAction<AutoRenderedView | null>>;
|
|
72
73
|
setRequestedResolution: Dispatch<SetStateAction<ChartResolution>>;
|
|
73
|
-
setStoredRenderMode: Dispatch<SetStateAction<ChartRenderMode>>;
|
|
74
74
|
setViewState: Dispatch<SetStateAction<StockChartViewportState>>;
|
|
75
75
|
storedRangePreset: TimeRange;
|
|
76
76
|
storedRenderMode: ChartRenderMode;
|
|
@@ -101,12 +101,12 @@ export function useStockChartViewportRuntime({
|
|
|
101
101
|
pendingCanonicalResetRef,
|
|
102
102
|
pendingExpansionRef,
|
|
103
103
|
persistChartControls,
|
|
104
|
+
persistChartRenderMode,
|
|
104
105
|
renderedResolution,
|
|
105
106
|
selectionSupportMap,
|
|
106
107
|
setPendingAutoWindowOverride,
|
|
107
108
|
setRenderedAutoView,
|
|
108
109
|
setRequestedResolution,
|
|
109
|
-
setStoredRenderMode,
|
|
110
110
|
setViewState,
|
|
111
111
|
storedRangePreset,
|
|
112
112
|
storedRenderMode,
|
|
@@ -172,10 +172,10 @@ export function useStockChartViewportRuntime({
|
|
|
172
172
|
});
|
|
173
173
|
|
|
174
174
|
const persistRenderMode = useCallback((nextMode: ChartRenderMode) => {
|
|
175
|
-
if (!compact
|
|
176
|
-
|
|
175
|
+
if (!compact) {
|
|
176
|
+
persistChartRenderMode(nextMode);
|
|
177
177
|
}
|
|
178
|
-
}, [compact,
|
|
178
|
+
}, [compact, persistChartRenderMode]);
|
|
179
179
|
|
|
180
180
|
const expandBufferRange = useCallback((action: PendingExpansionAction): boolean => {
|
|
181
181
|
if (compact) return false;
|
|
@@ -16,7 +16,17 @@ import {
|
|
|
16
16
|
getPaneTemplateDisplayLabel,
|
|
17
17
|
} from "./items";
|
|
18
18
|
|
|
19
|
+
function paneTemplateHasConfigFields(template: PaneTemplateDef): boolean {
|
|
20
|
+
if (template.wizard?.some((step) => step.type !== "info")) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return !!buildGeneratedTemplateField(template, null).field;
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
export function shouldOpenPaneTemplateConfig(template: PaneTemplateDef, arg?: string): boolean {
|
|
27
|
+
if (!paneTemplateHasConfigFields(template)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
20
30
|
if (template.wizard && template.wizard.length > 0) {
|
|
21
31
|
if (!arg?.trim()) {
|
|
22
32
|
return true;
|