gloomberb 0.7.2 → 0.7.4
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 +79 -132
- package/package.json +1 -1
- package/src/app.tsx +73 -67
- package/src/components/chart/chart-data.ts +7 -2
- package/src/components/chart/chart-pane-settings.ts +7 -4
- package/src/components/chart/chart-renderer.ts +158 -47
- package/src/components/chart/chart-types.ts +20 -2
- package/src/components/chart/chart.test.ts +179 -13
- package/src/components/chart/comparison-chart-renderer.test.ts +14 -30
- package/src/components/chart/comparison-chart-renderer.ts +50 -11
- package/src/components/chart/comparison-stock-chart.tsx +129 -129
- package/src/components/chart/market-session.ts +187 -0
- package/src/components/chart/native/chart-rasterizer.ts +108 -19
- package/src/components/chart/price-axis-labels.tsx +25 -9
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +97 -1
- package/src/components/chart/stock-chart.tsx +95 -49
- package/src/components/chart/time-axis-label.tsx +56 -2
- package/src/components/command-bar/command-bar.test.tsx +446 -120
- package/src/components/command-bar/command-bar.tsx +535 -384
- package/src/components/command-bar/command-registry.ts +16 -6
- package/src/components/command-bar/list-model.ts +8 -4
- package/src/components/command-bar/root-shortcuts.ts +4 -5
- package/src/components/command-bar/view-model.test.ts +21 -0
- package/src/components/command-bar/view-model.ts +17 -4
- package/src/components/command-bar/workflow-ops.ts +11 -32
- package/src/components/data-table-view.test.tsx +24 -1
- package/src/components/data-table-view.tsx +3 -2
- package/src/components/feed-data-table-stack-view.tsx +3 -2
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/floating-pane.tsx +9 -2
- package/src/components/layout/pane-footer.tsx +1 -1
- package/src/components/layout/pane-header.tsx +4 -2
- package/src/components/layout/pane-sizing.ts +12 -0
- package/src/components/layout/pane.tsx +24 -2
- package/src/components/layout/shell.tsx +24 -10
- package/src/components/layout/status-bar.test.tsx +4 -1
- package/src/components/layout/status-bar.tsx +4 -24
- package/src/components/markdown-text.tsx +53 -8
- package/src/components/onboarding/onboarding-wizard.tsx +7 -6
- package/src/components/pane-settings-dialog.tsx +9 -6
- package/src/components/pane-template-wizard.tsx +3 -2
- package/src/components/price-selector-dialog.tsx +3 -2
- package/src/components/price-sparkline-view.tsx +463 -0
- package/src/components/price-sparkline.ts +46 -0
- package/src/components/table-view-shared.tsx +21 -3
- package/src/components/ticker-list-table-view.tsx +3 -2
- package/src/components/ticker-list-table.tsx +15 -66
- package/src/components/toggle-list.tsx +3 -3
- package/src/components/ui/choice-dialog.tsx +3 -2
- package/src/components/ui/multi-select-dialog.tsx +3 -2
- package/src/components/ui/native-select.tsx +3 -3
- package/src/core/state/app-state.ts +210 -80
- package/src/core/state/session-persistence.ts +25 -0
- package/src/data/config-store-node.ts +113 -25
- package/src/data/config-store.test.ts +65 -3
- package/src/data/plugin-state-store.ts +39 -13
- package/src/data/sqlite-cache.test.ts +13 -0
- package/src/market-data/coordinator-subscriptions.test.ts +102 -18
- package/src/market-data/coordinator.test.ts +122 -2
- package/src/market-data/coordinator.ts +200 -56
- package/src/market-data/hooks.test.tsx +31 -0
- package/src/market-data/hooks.tsx +1 -0
- package/src/news/aggregator.test.ts +41 -0
- package/src/news/aggregator.ts +61 -9
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +25 -6
- package/src/plugins/builtin/ai/screener-pane.tsx +3 -0
- package/src/plugins/builtin/ai/settings.ts +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +4 -3
- package/src/plugins/builtin/chat-controller.test.ts +398 -17
- package/src/plugins/builtin/chat-controller.ts +263 -48
- package/src/plugins/builtin/chat.test.tsx +312 -6
- package/src/plugins/builtin/chat.tsx +245 -66
- package/src/plugins/builtin/comparison-chart.test.tsx +117 -3
- package/src/plugins/builtin/correlation/colors.test.ts +39 -0
- package/src/plugins/builtin/correlation/colors.ts +87 -0
- package/src/plugins/builtin/correlation/index.test.tsx +151 -0
- package/src/plugins/builtin/correlation/index.tsx +92 -21
- package/src/plugins/builtin/debug.tsx +4 -3
- package/src/plugins/builtin/econ/index.tsx +3 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +25 -13
- package/src/plugins/builtin/layout-manager.tsx +1 -7
- package/src/plugins/builtin/market-movers/index.tsx +72 -40
- package/src/plugins/builtin/news-wire/breaking-pane.test.tsx +156 -0
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +0 -90
- package/src/plugins/builtin/news-wire/index.tsx +1 -3
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +4 -1
- package/src/plugins/builtin/news-wire/persisted-articles.ts +7 -1
- package/src/plugins/builtin/options.tsx +9 -17
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +6 -10
- package/src/plugins/builtin/portfolio-list/metrics.ts +13 -2
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +139 -23
- package/src/plugins/builtin/portfolio-list/quick-add.tsx +422 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -12
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -3
- package/src/plugins/builtin/portfolio-list.test.tsx +311 -9
- package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
- package/src/plugins/builtin/sectors/index.tsx +275 -43
- package/src/plugins/builtin/sectors/sector-data.ts +41 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +30 -9
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +18 -7
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +445 -63
- package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
- package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
- package/src/plugins/builtin/world-indices/index.tsx +58 -19
- package/src/plugins/ibkr/trading-pane.tsx +3 -2
- package/src/plugins/plugin-runtime.tsx +2 -2
- package/src/plugins/prediction-markets/settings.ts +4 -0
- package/src/plugins/registry.test.ts +55 -0
- package/src/plugins/registry.ts +11 -4
- package/src/plugins/ticker-navigation.test.ts +35 -5
- package/src/plugins/ticker-navigation.ts +16 -22
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +7 -4
- package/src/renderers/electrobun/bun/application-menu.ts +6 -1
- package/src/renderers/electrobun/bun/desktop-workspace.ts +47 -29
- package/src/renderers/electrobun/bun/index.ts +52 -13
- package/src/renderers/electrobun/view/app-services.ts +51 -9
- package/src/renderers/electrobun/view/backend-rpc.ts +5 -8
- package/src/renderers/electrobun/view/desktop-controls.tsx +4 -4
- package/src/renderers/electrobun/view/main.tsx +0 -16
- package/src/renderers/electrobun/view/styles.css +23 -21
- package/src/renderers/electrobun/view/ui-host.tsx +6 -4
- package/src/renderers/opentui/host.tsx +2 -1
- package/src/renderers/opentui/start.tsx +1 -1
- package/src/renderers/opentui/test-utils.tsx +6 -5
- package/src/sources/gloomberb-cloud.test.ts +55 -2
- package/src/sources/gloomberb-cloud.ts +180 -29
- package/src/sources/provider-router.test.ts +567 -10
- package/src/sources/provider-router.ts +323 -93
- package/src/sources/yahoo-finance.test.ts +37 -0
- package/src/sources/yahoo-finance.ts +48 -2
- package/src/state/app-bootstrap.test.ts +50 -23
- package/src/state/app-bootstrap.ts +29 -13
- package/src/state/app-context.test.ts +113 -0
- package/src/state/app-context.tsx +8 -4
- package/src/state/session-persistence.test.ts +5 -3
- package/src/state/use-inline-tickers.ts +3 -0
- package/src/state/use-quote-streaming.ts +11 -0
- package/src/theme/colors.ts +13 -14
- package/src/types/config.ts +9 -4
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +1 -0
- package/src/types/plugin.ts +8 -1
- package/src/ui/context-menu.tsx +4 -4
- package/src/utils/api-client.test.ts +248 -0
- package/src/utils/api-client.ts +178 -26
- package/src/utils/exchanges.test.ts +16 -0
- package/src/utils/exchanges.ts +33 -0
- package/src/utils/fuzzy-search.ts +12 -1
- package/src/utils/keyboard.ts +31 -0
- package/src/utils/options.ts +1 -1
- package/src/utils/price-history.test.ts +28 -1
- package/src/utils/price-history.ts +16 -0
- package/src/utils/quote-freshness.test.ts +150 -0
- package/src/utils/quote-freshness.ts +42 -1
- package/src/utils/quote-resolution.ts +2 -0
- package/src/utils/quote-time.test.ts +34 -0
- package/src/utils/quote-time.ts +18 -5
- package/src/utils/ticker-search.test.ts +2 -2
- package/src/utils/ticker-search.ts +48 -1
- package/src/version.ts +1 -1
- package/src/plugins/builtin/news-wire/digest-store.ts +0 -44
|
@@ -169,11 +169,33 @@ function defaultPaneStateForInstance(config: AppConfig, instance: PaneInstanceCo
|
|
|
169
169
|
return {};
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
function
|
|
172
|
+
function cloneRuntimeValue<T>(value: T): T {
|
|
173
|
+
if (Array.isArray(value)) {
|
|
174
|
+
return value.map((entry) => cloneRuntimeValue(entry)) as T;
|
|
175
|
+
}
|
|
176
|
+
if (value && typeof value === "object") {
|
|
177
|
+
return Object.fromEntries(
|
|
178
|
+
Object.entries(value as Record<string, unknown>).map(([key, entry]) => [key, cloneRuntimeValue(entry)]),
|
|
179
|
+
) as T;
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function clonePaneStateMap(previous: Record<string, Record<string, unknown>>): Record<string, PaneRuntimeState> {
|
|
185
|
+
return Object.fromEntries(
|
|
186
|
+
Object.entries(previous).map(([paneId, paneState]) => [paneId, cloneRuntimeValue(paneState) as PaneRuntimeState]),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function reconcilePaneState(
|
|
191
|
+
config: AppConfig,
|
|
192
|
+
previous: Record<string, PaneRuntimeState>,
|
|
193
|
+
layout: LayoutConfig = config.layout,
|
|
194
|
+
): Record<string, PaneRuntimeState> {
|
|
173
195
|
const next: Record<string, PaneRuntimeState> = {};
|
|
174
|
-
for (const instance of
|
|
196
|
+
for (const instance of layout.instances) {
|
|
175
197
|
const defaults = defaultPaneStateForInstance(config, instance);
|
|
176
|
-
const paneState = { ...defaults, ...(previous[instance.instanceId] ?? {}) };
|
|
198
|
+
const paneState = { ...defaults, ...cloneRuntimeValue(previous[instance.instanceId] ?? {}) };
|
|
177
199
|
if (
|
|
178
200
|
instance.paneId === "portfolio-list"
|
|
179
201
|
&& !isKnownCollection(config, paneState.collectionId as string | undefined)
|
|
@@ -286,10 +308,56 @@ function nextRecentTickers(current: string[], symbol: string | null): string[] {
|
|
|
286
308
|
return next;
|
|
287
309
|
}
|
|
288
310
|
|
|
289
|
-
function
|
|
290
|
-
return
|
|
291
|
-
|
|
292
|
-
|
|
311
|
+
function cloneSavedLayout(entry: SavedLayout): SavedLayout {
|
|
312
|
+
return {
|
|
313
|
+
...entry,
|
|
314
|
+
layout: cloneLayout(entry.layout),
|
|
315
|
+
paneState: entry.paneState ? clonePaneStateMap(entry.paneState) : entry.paneState,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function buildSavedLayoutSnapshot(
|
|
320
|
+
entry: SavedLayout | undefined,
|
|
321
|
+
layout: LayoutConfig,
|
|
322
|
+
paneState: Record<string, PaneRuntimeState>,
|
|
323
|
+
focusedPaneId: string | null,
|
|
324
|
+
activePanel: "left" | "right",
|
|
325
|
+
): SavedLayout {
|
|
326
|
+
return {
|
|
327
|
+
...(entry ?? { name: "Default" }),
|
|
328
|
+
layout: cloneLayout(layout),
|
|
329
|
+
paneState: clonePaneStateMap(paneState),
|
|
330
|
+
focusedPaneId,
|
|
331
|
+
activePanel,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function syncConfigActiveLayoutState(
|
|
336
|
+
config: AppConfig,
|
|
337
|
+
paneState: Record<string, PaneRuntimeState>,
|
|
338
|
+
focusedPaneId: string | null,
|
|
339
|
+
activePanel: "left" | "right",
|
|
340
|
+
): AppConfig {
|
|
341
|
+
const activeLayoutIndex = config.activeLayoutIndex >= 0 && config.activeLayoutIndex < config.layouts.length
|
|
342
|
+
? config.activeLayoutIndex
|
|
343
|
+
: 0;
|
|
344
|
+
const layouts = config.layouts.length > 0
|
|
345
|
+
? config.layouts.map((savedLayout, index) => (
|
|
346
|
+
index === activeLayoutIndex
|
|
347
|
+
? buildSavedLayoutSnapshot(savedLayout, config.layout, reconcilePaneState(config, paneState), focusedPaneId, activePanel)
|
|
348
|
+
: cloneSavedLayout(savedLayout)
|
|
349
|
+
))
|
|
350
|
+
: [buildSavedLayoutSnapshot(undefined, config.layout, reconcilePaneState(config, paneState), focusedPaneId, activePanel)];
|
|
351
|
+
return {
|
|
352
|
+
...config,
|
|
353
|
+
layouts,
|
|
354
|
+
activeLayoutIndex,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function getActiveSavedPaneState(config: AppConfig): Record<string, PaneRuntimeState> | null {
|
|
359
|
+
const paneState = config.layouts[config.activeLayoutIndex]?.paneState;
|
|
360
|
+
return paneState ? clonePaneStateMap(paneState) : null;
|
|
293
361
|
}
|
|
294
362
|
|
|
295
363
|
const PANEL_RESOLUTION_BOUNDS = { x: 0, y: 0, width: 120, height: 40 };
|
|
@@ -393,9 +461,7 @@ function bringFloatingToFront(layout: LayoutConfig, paneId: string): LayoutConfi
|
|
|
393
461
|
|
|
394
462
|
function focusPaneState(state: AppState, paneId: string): AppState {
|
|
395
463
|
const layout = bringFloatingToFront(state.config.layout, paneId);
|
|
396
|
-
const config = layout !== state.config.layout
|
|
397
|
-
? { ...state.config, layout, layouts: syncLayouts(state.config.layouts, state.config.activeLayoutIndex, layout) }
|
|
398
|
-
: state.config;
|
|
464
|
+
const config = layout !== state.config.layout ? { ...state.config, layout } : state.config;
|
|
399
465
|
const recentTickers = nextRecentTickers(
|
|
400
466
|
state.recentTickers,
|
|
401
467
|
resolveTickerForPane(state, paneId),
|
|
@@ -409,41 +475,44 @@ function focusPaneState(state: AppState, paneId: string): AppState {
|
|
|
409
475
|
}
|
|
410
476
|
return {
|
|
411
477
|
...state,
|
|
412
|
-
config,
|
|
478
|
+
config: syncConfigActiveLayoutState(config, state.paneState, paneId, state.activePanel),
|
|
413
479
|
focusedPaneId: paneId,
|
|
414
480
|
recentTickers,
|
|
415
481
|
};
|
|
416
482
|
}
|
|
417
483
|
|
|
418
|
-
function
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function withFocusedPane(state: AppState, config: AppConfig): AppState {
|
|
484
|
+
function withFocusedPane(
|
|
485
|
+
state: AppState,
|
|
486
|
+
config: AppConfig,
|
|
487
|
+
options: {
|
|
488
|
+
paneState?: Record<string, PaneRuntimeState>;
|
|
489
|
+
focusedPaneId?: string | null;
|
|
490
|
+
activePanel?: "left" | "right";
|
|
491
|
+
} = {},
|
|
492
|
+
): AppState {
|
|
430
493
|
const normalizedLayout = normalizePaneLayout(config.layout);
|
|
431
494
|
const nextConfig = normalizedLayout === config.layout
|
|
432
495
|
? config
|
|
433
496
|
: {
|
|
434
497
|
...config,
|
|
435
498
|
layout: normalizedLayout,
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const nextPaneState = reconcilePaneState(nextConfig, state.paneState);
|
|
439
|
-
const
|
|
440
|
-
|
|
499
|
+
};
|
|
500
|
+
const activePanel = options.activePanel ?? state.activePanel;
|
|
501
|
+
const nextPaneState = reconcilePaneState(nextConfig, options.paneState ?? state.paneState);
|
|
502
|
+
const requestedFocusedPaneId = Object.prototype.hasOwnProperty.call(options, "focusedPaneId")
|
|
503
|
+
? (options.focusedPaneId ?? null)
|
|
504
|
+
: state.focusedPaneId;
|
|
505
|
+
const focusedPaneId = resolveFocusedPaneId(nextConfig.layout, requestedFocusedPaneId);
|
|
506
|
+
const focusedLayout = focusedPaneId ? bringFloatingToFront(nextConfig.layout, focusedPaneId) : nextConfig.layout;
|
|
507
|
+
const focusedConfig = focusedLayout === nextConfig.layout ? nextConfig : { ...nextConfig, layout: focusedLayout };
|
|
508
|
+
const syncedConfig = syncConfigActiveLayoutState(focusedConfig, nextPaneState, focusedPaneId, activePanel);
|
|
441
509
|
return {
|
|
442
510
|
...state,
|
|
443
|
-
config:
|
|
511
|
+
config: syncedConfig,
|
|
444
512
|
paneState: nextPaneState,
|
|
445
|
-
brokerAccounts: reconcileBrokerAccounts(
|
|
513
|
+
brokerAccounts: reconcileBrokerAccounts(syncedConfig, state.brokerAccounts),
|
|
446
514
|
focusedPaneId,
|
|
515
|
+
activePanel,
|
|
447
516
|
};
|
|
448
517
|
}
|
|
449
518
|
|
|
@@ -465,7 +534,16 @@ function hydrateDesktopSnapshot(state: AppState, snapshot: DesktopSharedStateSna
|
|
|
465
534
|
export function appReducer(state: AppState, action: AppAction): AppState {
|
|
466
535
|
switch (action.type) {
|
|
467
536
|
case "SET_CONFIG":
|
|
468
|
-
return withFocusedPane(
|
|
537
|
+
return withFocusedPane(
|
|
538
|
+
{ ...state, themePreview: null, layoutHistory: {} },
|
|
539
|
+
action.config,
|
|
540
|
+
{
|
|
541
|
+
paneState: {
|
|
542
|
+
...state.paneState,
|
|
543
|
+
...(getActiveSavedPaneState(action.config) ?? {}),
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
);
|
|
469
547
|
|
|
470
548
|
case "SET_TICKERS":
|
|
471
549
|
return { ...state, tickers: action.tickers };
|
|
@@ -488,15 +566,13 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
488
566
|
: currentState;
|
|
489
567
|
}
|
|
490
568
|
const layout = clearTickerBindings(state.config.layout, action.symbol);
|
|
491
|
-
|
|
492
|
-
return {
|
|
569
|
+
return withFocusedPane({
|
|
493
570
|
...state,
|
|
494
571
|
tickers,
|
|
495
572
|
financials,
|
|
496
573
|
paneState,
|
|
497
574
|
recentTickers: state.recentTickers.filter((symbol) => symbol !== action.symbol),
|
|
498
|
-
|
|
499
|
-
};
|
|
575
|
+
}, { ...state.config, layout }, { paneState });
|
|
500
576
|
}
|
|
501
577
|
|
|
502
578
|
case "SET_FINANCIALS": {
|
|
@@ -526,11 +602,10 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
526
602
|
|
|
527
603
|
case "SET_ACTIVE_PANEL": {
|
|
528
604
|
const firstPane = getPanelFocusTarget(state.config.layout, action.panel);
|
|
529
|
-
return {
|
|
530
|
-
...state,
|
|
605
|
+
return withFocusedPane(state, state.config, {
|
|
531
606
|
activePanel: action.panel,
|
|
532
607
|
focusedPaneId: firstPane ?? state.focusedPaneId,
|
|
533
|
-
};
|
|
608
|
+
});
|
|
534
609
|
}
|
|
535
610
|
|
|
536
611
|
case "TOGGLE_COMMAND_BAR":
|
|
@@ -658,14 +733,12 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
658
733
|
const target = entry.past[entry.past.length - 1]!;
|
|
659
734
|
entry.past = entry.past.slice(0, -1);
|
|
660
735
|
entry.future = [cloneLayout(state.config.layout), ...entry.future].slice(0, MAX_LAYOUT_HISTORY);
|
|
661
|
-
const layouts = syncLayouts(state.config.layouts, currentIndex, target);
|
|
662
736
|
return withFocusedPane({
|
|
663
737
|
...state,
|
|
664
738
|
layoutHistory: setHistoryForIndex(state.layoutHistory, currentIndex, entry),
|
|
665
739
|
}, {
|
|
666
740
|
...state.config,
|
|
667
741
|
layout: cloneLayout(target),
|
|
668
|
-
layouts,
|
|
669
742
|
});
|
|
670
743
|
}
|
|
671
744
|
|
|
@@ -676,54 +749,76 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
676
749
|
const target = entry.future[0]!;
|
|
677
750
|
entry.future = entry.future.slice(1);
|
|
678
751
|
entry.past = [...entry.past, cloneLayout(state.config.layout)].slice(-MAX_LAYOUT_HISTORY);
|
|
679
|
-
const layouts = syncLayouts(state.config.layouts, currentIndex, target);
|
|
680
752
|
return withFocusedPane({
|
|
681
753
|
...state,
|
|
682
754
|
layoutHistory: setHistoryForIndex(state.layoutHistory, currentIndex, entry),
|
|
683
755
|
}, {
|
|
684
756
|
...state.config,
|
|
685
757
|
layout: cloneLayout(target),
|
|
686
|
-
layouts,
|
|
687
758
|
});
|
|
688
759
|
}
|
|
689
760
|
|
|
690
761
|
case "UPDATE_LAYOUT": {
|
|
691
|
-
|
|
692
|
-
return withFocusedPane(state, { ...state.config, layout: action.layout, layouts });
|
|
762
|
+
return withFocusedPane(state, { ...state.config, layout: action.layout });
|
|
693
763
|
}
|
|
694
764
|
|
|
695
765
|
case "SWITCH_LAYOUT": {
|
|
696
766
|
if (action.index < 0 || action.index >= state.config.layouts.length) return state;
|
|
697
|
-
|
|
698
|
-
const
|
|
767
|
+
if (action.index === state.config.activeLayoutIndex) return state;
|
|
768
|
+
const currentConfig = syncConfigActiveLayoutState(
|
|
769
|
+
state.config,
|
|
770
|
+
state.paneState,
|
|
771
|
+
state.focusedPaneId,
|
|
772
|
+
state.activePanel,
|
|
773
|
+
);
|
|
774
|
+
const target = currentConfig.layouts[action.index]!;
|
|
699
775
|
return withFocusedPane(state, {
|
|
700
|
-
...
|
|
776
|
+
...currentConfig,
|
|
701
777
|
layout: cloneLayout(target.layout),
|
|
702
|
-
layouts,
|
|
703
778
|
activeLayoutIndex: action.index,
|
|
779
|
+
}, {
|
|
780
|
+
paneState: target.paneState ? clonePaneStateMap(target.paneState) : {},
|
|
781
|
+
focusedPaneId: target.focusedPaneId ?? null,
|
|
782
|
+
activePanel: target.activePanel ?? state.activePanel,
|
|
704
783
|
});
|
|
705
784
|
}
|
|
706
785
|
|
|
707
786
|
case "NEW_LAYOUT": {
|
|
787
|
+
const currentConfig = syncConfigActiveLayoutState(
|
|
788
|
+
state.config,
|
|
789
|
+
state.paneState,
|
|
790
|
+
state.focusedPaneId,
|
|
791
|
+
state.activePanel,
|
|
792
|
+
);
|
|
708
793
|
const newLayout: SavedLayout = {
|
|
709
794
|
name: action.name,
|
|
710
795
|
layout: cloneLayout(DEFAULT_LAYOUT),
|
|
796
|
+
paneState: {},
|
|
711
797
|
};
|
|
712
|
-
const layouts = [...
|
|
798
|
+
const layouts = [...currentConfig.layouts, newLayout];
|
|
713
799
|
return withFocusedPane({
|
|
714
800
|
...state,
|
|
715
801
|
layoutHistory: setHistoryForIndex(state.layoutHistory, layouts.length - 1, { past: [], future: [] }),
|
|
716
802
|
}, {
|
|
717
|
-
...
|
|
803
|
+
...currentConfig,
|
|
718
804
|
layout: cloneLayout(newLayout.layout),
|
|
719
805
|
layouts,
|
|
720
806
|
activeLayoutIndex: layouts.length - 1,
|
|
807
|
+
}, {
|
|
808
|
+
paneState: {},
|
|
809
|
+
focusedPaneId: null,
|
|
721
810
|
});
|
|
722
811
|
}
|
|
723
812
|
|
|
724
813
|
case "DELETE_LAYOUT": {
|
|
725
814
|
if (state.config.layouts.length <= 1) return state;
|
|
726
|
-
const
|
|
815
|
+
const currentConfig = syncConfigActiveLayoutState(
|
|
816
|
+
state.config,
|
|
817
|
+
state.paneState,
|
|
818
|
+
state.focusedPaneId,
|
|
819
|
+
state.activePanel,
|
|
820
|
+
);
|
|
821
|
+
const layouts = currentConfig.layouts.filter((_, index) => index !== action.index);
|
|
727
822
|
const nextActiveLayoutIndex = action.index <= state.config.activeLayoutIndex
|
|
728
823
|
? Math.max(0, state.config.activeLayoutIndex - 1)
|
|
729
824
|
: state.config.activeLayoutIndex;
|
|
@@ -732,20 +827,30 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
732
827
|
...state,
|
|
733
828
|
layoutHistory: removeHistoryIndex(state.layoutHistory, action.index),
|
|
734
829
|
}, {
|
|
735
|
-
...
|
|
830
|
+
...currentConfig,
|
|
736
831
|
layout: cloneLayout(nextLayout.layout),
|
|
737
832
|
layouts,
|
|
738
833
|
activeLayoutIndex: nextActiveLayoutIndex,
|
|
834
|
+
}, {
|
|
835
|
+
paneState: nextLayout.paneState ? clonePaneStateMap(nextLayout.paneState) : {},
|
|
836
|
+
focusedPaneId: nextLayout.focusedPaneId ?? null,
|
|
837
|
+
activePanel: nextLayout.activePanel ?? state.activePanel,
|
|
739
838
|
});
|
|
740
839
|
}
|
|
741
840
|
|
|
742
841
|
case "RENAME_LAYOUT": {
|
|
743
842
|
if (action.index < 0 || action.index >= state.config.layouts.length) return state;
|
|
843
|
+
const currentConfig = syncConfigActiveLayoutState(
|
|
844
|
+
state.config,
|
|
845
|
+
state.paneState,
|
|
846
|
+
state.focusedPaneId,
|
|
847
|
+
state.activePanel,
|
|
848
|
+
);
|
|
744
849
|
return {
|
|
745
850
|
...state,
|
|
746
851
|
config: {
|
|
747
|
-
...
|
|
748
|
-
layouts:
|
|
852
|
+
...currentConfig,
|
|
853
|
+
layouts: currentConfig.layouts.map((savedLayout, index) => (
|
|
749
854
|
index === action.index ? { ...savedLayout, name: action.name } : savedLayout
|
|
750
855
|
)),
|
|
751
856
|
},
|
|
@@ -754,20 +859,30 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
754
859
|
|
|
755
860
|
case "DUPLICATE_LAYOUT": {
|
|
756
861
|
if (action.index < 0 || action.index >= state.config.layouts.length) return state;
|
|
757
|
-
const
|
|
862
|
+
const currentConfig = syncConfigActiveLayoutState(
|
|
863
|
+
state.config,
|
|
864
|
+
state.paneState,
|
|
865
|
+
state.focusedPaneId,
|
|
866
|
+
state.activePanel,
|
|
867
|
+
);
|
|
868
|
+
const source = currentConfig.layouts[action.index]!;
|
|
758
869
|
const duplicate: SavedLayout = {
|
|
870
|
+
...cloneSavedLayout(source),
|
|
759
871
|
name: `${source.name} Copy`,
|
|
760
|
-
layout: cloneLayout(source.layout),
|
|
761
872
|
};
|
|
762
|
-
const layouts = [...
|
|
873
|
+
const layouts = [...currentConfig.layouts, duplicate];
|
|
763
874
|
return withFocusedPane({
|
|
764
875
|
...state,
|
|
765
876
|
layoutHistory: setHistoryForIndex(state.layoutHistory, layouts.length - 1, { past: [], future: [] }),
|
|
766
877
|
}, {
|
|
767
|
-
...
|
|
878
|
+
...currentConfig,
|
|
768
879
|
layout: cloneLayout(duplicate.layout),
|
|
769
880
|
layouts,
|
|
770
881
|
activeLayoutIndex: layouts.length - 1,
|
|
882
|
+
}, {
|
|
883
|
+
paneState: duplicate.paneState ? clonePaneStateMap(duplicate.paneState) : {},
|
|
884
|
+
focusedPaneId: duplicate.focusedPaneId ?? state.focusedPaneId,
|
|
885
|
+
activePanel: duplicate.activePanel ?? state.activePanel,
|
|
771
886
|
});
|
|
772
887
|
}
|
|
773
888
|
|
|
@@ -802,12 +917,14 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
802
917
|
const recentTickers = Object.prototype.hasOwnProperty.call(action.patch, "cursorSymbol")
|
|
803
918
|
? nextRecentTickers(state.recentTickers, typeof nextState.cursorSymbol === "string" ? nextState.cursorSymbol : null)
|
|
804
919
|
: state.recentTickers;
|
|
920
|
+
const paneState = {
|
|
921
|
+
...state.paneState,
|
|
922
|
+
[action.paneId]: nextState,
|
|
923
|
+
};
|
|
805
924
|
return {
|
|
806
925
|
...state,
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
[action.paneId]: nextState,
|
|
810
|
-
},
|
|
926
|
+
config: syncConfigActiveLayoutState(state.config, paneState, state.focusedPaneId, state.activePanel),
|
|
927
|
+
paneState,
|
|
811
928
|
recentTickers,
|
|
812
929
|
};
|
|
813
930
|
}
|
|
@@ -819,22 +936,24 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
819
936
|
if (Object.is(currentPluginValues[action.key], action.value)) {
|
|
820
937
|
return state;
|
|
821
938
|
}
|
|
822
|
-
|
|
823
|
-
...state,
|
|
824
|
-
|
|
825
|
-
...
|
|
826
|
-
|
|
827
|
-
...
|
|
828
|
-
|
|
829
|
-
...
|
|
830
|
-
[action.
|
|
831
|
-
...currentPluginValues,
|
|
832
|
-
[action.key]: action.value,
|
|
833
|
-
},
|
|
939
|
+
const paneState = {
|
|
940
|
+
...state.paneState,
|
|
941
|
+
[action.paneId]: {
|
|
942
|
+
...current,
|
|
943
|
+
pluginState: {
|
|
944
|
+
...currentPluginState,
|
|
945
|
+
[action.pluginId]: {
|
|
946
|
+
...currentPluginValues,
|
|
947
|
+
[action.key]: action.value,
|
|
834
948
|
},
|
|
835
949
|
},
|
|
836
950
|
},
|
|
837
951
|
};
|
|
952
|
+
return {
|
|
953
|
+
...state,
|
|
954
|
+
config: syncConfigActiveLayoutState(state.config, paneState, state.focusedPaneId, state.activePanel),
|
|
955
|
+
paneState,
|
|
956
|
+
};
|
|
838
957
|
}
|
|
839
958
|
|
|
840
959
|
default:
|
|
@@ -843,20 +962,31 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
843
962
|
}
|
|
844
963
|
|
|
845
964
|
export function createInitialState(config: AppConfig, sessionSnapshot: AppSessionSnapshot | null = null): AppState {
|
|
846
|
-
const
|
|
965
|
+
const activeSavedLayout = config.layouts[config.activeLayoutIndex];
|
|
966
|
+
const savedPaneState = activeSavedLayout?.paneState ? clonePaneStateMap(activeSavedLayout.paneState) : {};
|
|
967
|
+
const sessionPaneState = sessionSnapshot?.paneState ? clonePaneStateMap(sessionSnapshot.paneState) : {};
|
|
968
|
+
const paneState = reconcilePaneState(config, { ...sessionPaneState, ...savedPaneState });
|
|
847
969
|
const defaultFocusedPaneId = getTopFloatingPaneId(config.layout) ?? getPaneOrder(config.layout)[0] ?? null;
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
|
|
970
|
+
const requestedFocusedPaneId = activeSavedLayout?.focusedPaneId ?? sessionSnapshot?.focusedPaneId ?? null;
|
|
971
|
+
const focusedPaneId = requestedFocusedPaneId
|
|
972
|
+
&& config.layout.instances.some((instance) => instance.instanceId === requestedFocusedPaneId)
|
|
973
|
+
? requestedFocusedPaneId
|
|
851
974
|
: defaultFocusedPaneId;
|
|
852
|
-
const
|
|
975
|
+
const activePanel = activeSavedLayout?.activePanel ?? (sessionSnapshot?.activePanel === "right" ? "right" : "left");
|
|
976
|
+
const focusedLayout = focusedPaneId ? bringFloatingToFront(config.layout, focusedPaneId) : config.layout;
|
|
977
|
+
const focusedConfig = syncConfigActiveLayoutState(
|
|
978
|
+
focusedLayout === config.layout ? config : { ...config, layout: focusedLayout },
|
|
979
|
+
paneState,
|
|
980
|
+
focusedPaneId,
|
|
981
|
+
activePanel,
|
|
982
|
+
);
|
|
853
983
|
return {
|
|
854
984
|
config: focusedConfig,
|
|
855
985
|
tickers: new Map(),
|
|
856
986
|
financials: new Map(),
|
|
857
987
|
exchangeRates: new Map([["USD", 1]]),
|
|
858
988
|
brokerAccounts: {},
|
|
859
|
-
activePanel
|
|
989
|
+
activePanel,
|
|
860
990
|
focusedPaneId,
|
|
861
991
|
paneState,
|
|
862
992
|
recentTickers: config.recentTickers,
|
|
@@ -73,6 +73,23 @@ function targetFromTicker(ticker: TickerRecord): HydrationTarget {
|
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
function resolvePortfolioPaneCollectionId(
|
|
77
|
+
config: AppConfig,
|
|
78
|
+
paneState: Record<string, unknown> | undefined,
|
|
79
|
+
instance: AppConfig["layout"]["instances"][number],
|
|
80
|
+
): string | null {
|
|
81
|
+
const stateCollectionId = typeof paneState?.collectionId === "string" ? paneState.collectionId : null;
|
|
82
|
+
if (stateCollectionId) return stateCollectionId;
|
|
83
|
+
if (typeof instance.params?.collectionId === "string") return instance.params.collectionId;
|
|
84
|
+
return config.portfolios[0]?.id ?? config.watchlists[0]?.id ?? null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function tickerInCollection(ticker: TickerRecord, collectionId: string | null): boolean {
|
|
88
|
+
if (!collectionId) return false;
|
|
89
|
+
return ticker.metadata.portfolios.includes(collectionId)
|
|
90
|
+
|| ticker.metadata.watchlists.includes(collectionId);
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
export function buildAppSessionSnapshot(state: SessionStateInput): AppSessionSnapshot {
|
|
77
94
|
const seen = new Set<string>();
|
|
78
95
|
const hydrationTargets: HydrationTarget[] = [];
|
|
@@ -104,6 +121,14 @@ export function buildAppSessionSnapshot(state: SessionStateInput): AppSessionSna
|
|
|
104
121
|
if (instance.binding?.kind === "fixed") {
|
|
105
122
|
pushTarget(state.tickers.get(instance.binding.symbol));
|
|
106
123
|
}
|
|
124
|
+
if (instance.paneId === "portfolio-list") {
|
|
125
|
+
const collectionId = resolvePortfolioPaneCollectionId(state.config, paneState, instance);
|
|
126
|
+
for (const ticker of state.tickers.values()) {
|
|
127
|
+
if (tickerInCollection(ticker, collectionId)) {
|
|
128
|
+
pushTarget(ticker);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
107
132
|
}
|
|
108
133
|
|
|
109
134
|
return {
|