gloomberb 0.5.1 → 0.6.1
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 +91 -11
- package/package.json +13 -5
- package/src/app.tsx +414 -191
- package/src/architecture/import-boundaries.test.ts +118 -0
- package/src/assets/gloomberb-logo.png +0 -0
- package/src/assets/gloomberb-logo.svg +73 -0
- package/src/brokers/account-cache.ts +157 -0
- package/src/brokers/profile-form.test.ts +73 -0
- package/src/brokers/profile-form.ts +109 -0
- package/src/brokers/remote-broker-adapter.ts +91 -0
- package/src/brokers/sync-broker-instance.test.ts +124 -1
- package/src/brokers/sync-broker-instance.ts +113 -17
- package/src/capabilities/factories.ts +100 -0
- package/src/capabilities/index.ts +4 -0
- package/src/capabilities/registry.test.ts +137 -0
- package/src/capabilities/registry.ts +131 -0
- package/src/capabilities/service-capabilities.ts +27 -0
- package/src/capabilities/types.ts +84 -0
- package/src/cli/commands/ticker.ts +11 -3
- package/src/cli/context.ts +19 -13
- package/src/cli/helpers.ts +1 -0
- package/src/cli/types.ts +2 -2
- package/src/components/chart/chart-controller.ts +4 -24
- package/src/components/chart/chart-data.ts +3 -14
- package/src/components/chart/chart-indicator-selector.tsx +28 -6
- package/src/components/chart/chart-pane-settings.ts +15 -28
- package/src/components/chart/chart-renderer.ts +74 -99
- package/src/components/chart/chart-resolution.ts +10 -10
- package/src/components/chart/chart-scroll.test.ts +6 -5
- package/src/components/chart/chart-scroll.ts +2 -2
- package/src/components/chart/chart-types.ts +0 -1
- package/src/components/chart/chart-viewport.ts +2 -1
- package/src/components/chart/chart.test.ts +37 -0
- package/src/components/chart/comparison-chart-data.ts +3 -17
- package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
- package/src/components/chart/comparison-chart-renderer.ts +1 -1
- package/src/components/chart/comparison-stock-chart.tsx +318 -158
- package/src/components/chart/cursor-motion.ts +3 -2
- package/src/components/chart/indicators/bands.test.ts +0 -14
- package/src/components/chart/indicators/moving-averages.test.ts +0 -15
- package/src/components/chart/indicators/options.ts +3 -3
- package/src/components/chart/indicators/oscillators.test.ts +0 -21
- package/src/components/chart/indicators/types.ts +1 -2
- package/src/components/chart/native/chart-rasterizer.ts +37 -44
- package/src/components/chart/native/kitty-adapter.ts +2 -5
- package/src/components/chart/native/kitty-manager.ts +1 -1
- package/src/components/chart/native/kitty-protocol.ts +0 -1
- package/src/components/chart/native/kitty-support.ts +1 -1
- package/src/components/chart/native/kitty.test.ts +53 -13
- package/src/components/chart/native/renderer-selection.ts +1 -1
- package/src/components/chart/native/surface-manager.ts +1 -1
- package/src/components/chart/native/surface-visibility.ts +48 -0
- package/src/components/chart/static-chart-surface.test.tsx +51 -0
- package/src/components/chart/static-chart-surface.tsx +191 -0
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
- package/src/components/chart/stock-chart.tsx +452 -191
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
- package/src/components/command-bar/command-bar.test.tsx +269 -20
- package/src/components/command-bar/command-bar.tsx +830 -353
- package/src/components/command-bar/command-registry.ts +10 -20
- package/src/components/command-bar/helpers.test.ts +1 -1
- package/src/components/command-bar/helpers.ts +9 -11
- package/src/components/command-bar/pane-template-display.ts +4 -8
- package/src/components/command-bar/root-shortcuts.ts +12 -11
- package/src/components/command-bar/view-model.test.ts +8 -42
- package/src/components/command-bar/view-model.ts +8 -36
- package/src/components/command-bar/workflow-ops.test.ts +76 -2
- package/src/components/command-bar/workflow-ops.ts +40 -9
- package/src/components/command-bar/workflow-types.ts +2 -1
- package/src/components/data-table-stack-view.test.tsx +206 -0
- package/src/components/data-table-stack-view.tsx +152 -0
- package/src/components/data-table-view.test.tsx +188 -0
- package/src/components/data-table-view.tsx +279 -0
- package/src/components/feed-data-table-stack-view.test.tsx +225 -0
- package/src/components/feed-data-table-stack-view.tsx +365 -0
- package/src/components/index.ts +26 -31
- package/src/components/layout/detached-pane-shell.tsx +185 -0
- package/src/components/layout/floating-pane.tsx +87 -14
- package/src/components/layout/header.tsx +139 -49
- package/src/components/layout/pane-content.tsx +42 -0
- package/src/components/layout/pane-footer.test.tsx +195 -0
- package/src/components/layout/pane-footer.tsx +425 -0
- package/src/components/layout/pane-header.test.tsx +39 -0
- package/src/components/layout/pane-header.tsx +192 -14
- package/src/components/layout/pane-keyboard-scroll.test.tsx +226 -0
- package/src/components/layout/pane-sizing.ts +9 -10
- package/src/components/layout/pane-title.ts +35 -0
- package/src/components/layout/pane.tsx +52 -11
- package/src/components/layout/shell.test.tsx +315 -107
- package/src/components/layout/shell.tsx +1010 -368
- package/src/components/layout/status-bar.test.tsx +17 -10
- package/src/components/layout/status-bar.tsx +367 -100
- package/src/components/layout/titlebar-overlay.ts +2 -0
- package/src/components/markdown-editor.tsx +29 -14
- package/src/components/markdown-text.tsx +25 -24
- package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
- package/src/components/onboarding/onboarding-wizard.tsx +347 -473
- package/src/components/pane-settings-dialog.tsx +521 -89
- package/src/components/pane-template-wizard.test.tsx +1 -1
- package/src/components/pane-template-wizard.tsx +38 -37
- package/src/components/price-selector-dialog.tsx +22 -21
- package/src/components/ticker-badge-list.tsx +53 -0
- package/src/components/ticker-badge-text.test.tsx +44 -1
- package/src/components/ticker-badge-text.tsx +78 -38
- package/src/components/ticker-badge.tsx +28 -7
- package/src/components/ticker-list-table-view.tsx +248 -0
- package/src/components/ticker-list-table.test.tsx +47 -1
- package/src/components/ticker-list-table.tsx +212 -41
- package/src/components/toggle-list.tsx +114 -8
- package/src/components/ui/button.tsx +24 -16
- package/src/components/ui/choice-dialog.tsx +89 -0
- package/src/components/ui/data-table.tsx +313 -114
- package/src/components/ui/external-link.tsx +52 -13
- package/src/components/ui/fields.tsx +44 -30
- package/src/components/ui/frame.tsx +24 -57
- package/src/components/ui/index.ts +15 -50
- package/src/components/ui/list-view.tsx +65 -25
- package/src/components/ui/loading.tsx +5 -60
- package/src/components/ui/message-composer.test.tsx +81 -0
- package/src/components/ui/message-composer.tsx +139 -0
- package/src/components/ui/multi-select-dialog.tsx +100 -32
- package/src/components/ui/multi-select.ts +112 -0
- package/src/components/ui/native-select.tsx +120 -0
- package/src/components/ui/page-stack-view.tsx +49 -19
- package/src/components/ui/remote-image.tsx +50 -0
- package/src/components/ui/row-value-cache.test.ts +17 -0
- package/src/components/ui/row-value-cache.ts +41 -0
- package/src/components/ui/shortcut-hint.tsx +55 -0
- package/src/components/ui/status.tsx +12 -67
- package/src/components/ui/table-layout.ts +59 -0
- package/src/components/ui/tabs.tsx +249 -27
- package/src/components/ui/toggle.tsx +19 -127
- package/src/components/ui/ui.test.tsx +266 -53
- package/src/components/use-double-click-activation.ts +7 -2
- package/src/components/use-external-link-footer.ts +79 -0
- package/src/core/app-services.ts +95 -0
- package/src/core/state/app-state.ts +886 -0
- package/src/{state → core/state}/session-persistence.ts +7 -6
- package/src/data/config-layout.ts +225 -0
- package/src/data/config-store-node.ts +390 -0
- package/src/data/config-store.test.ts +72 -0
- package/src/data/config-store.ts +35 -478
- package/src/data/sqlite-json.ts +2 -5
- package/src/data/sqlite-retry.ts +1 -0
- package/src/index.tsx +2 -68
- package/src/market-data/coordinator-subscriptions.test.ts +125 -0
- package/src/market-data/coordinator.test.ts +73 -4
- package/src/market-data/coordinator.ts +190 -24
- package/src/market-data/hooks.test.tsx +1 -1
- package/src/market-data/hooks.tsx +109 -87
- package/src/market-data/query-store.ts +3 -3
- package/src/market-data/request-types.test.ts +0 -14
- package/src/market-data/result-types.ts +1 -0
- package/src/news/aggregator.test.ts +267 -0
- package/src/news/aggregator.ts +452 -0
- package/src/news/hooks.ts +39 -0
- package/src/news/ticker-symbols.ts +21 -0
- package/src/news/types.ts +77 -0
- package/src/notifications/app-notifier.ts +21 -10
- package/src/pane-settings.test.ts +0 -2
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
- package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
- package/src/plugins/builtin/ai/providers.ts +1 -9
- package/src/plugins/builtin/ai/runner.ts +41 -5
- package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
- package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
- package/src/plugins/builtin/ai/settings.ts +0 -1
- package/src/plugins/builtin/ai/utils.ts +1 -71
- package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
- package/src/plugins/builtin/alerts/index.test.tsx +61 -82
- package/src/plugins/builtin/alerts/index.tsx +263 -155
- package/src/plugins/builtin/alerts/types.ts +7 -0
- package/src/plugins/builtin/analytics/index.test.tsx +13 -8
- package/src/plugins/builtin/analytics/index.tsx +78 -120
- package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
- package/src/plugins/builtin/ask-ai.test.tsx +55 -54
- package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
- package/src/plugins/builtin/broker-manager/index.tsx +759 -0
- package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
- package/src/plugins/builtin/broker-manager/model.ts +142 -0
- package/src/plugins/builtin/chat-controller.test.ts +129 -0
- package/src/plugins/builtin/chat-controller.ts +85 -14
- package/src/plugins/builtin/chat.test.tsx +283 -36
- package/src/plugins/builtin/chat.tsx +363 -170
- package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
- package/src/plugins/builtin/cloud-tweets.tsx +896 -0
- package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
- package/src/plugins/builtin/comparison-chart.tsx +11 -11
- package/src/plugins/builtin/correlation/compute.test.ts +0 -17
- package/src/plugins/builtin/correlation/index.tsx +51 -46
- package/src/plugins/builtin/correlation/settings.ts +1 -2
- package/src/plugins/builtin/debug.tsx +99 -70
- package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
- package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
- package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
- package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
- package/src/plugins/builtin/earnings/index.tsx +187 -94
- package/src/plugins/builtin/econ/calendar-source.ts +9 -116
- package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
- package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
- package/src/plugins/builtin/econ/index.tsx +236 -421
- package/src/plugins/builtin/econ/types.ts +1 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
- package/src/plugins/builtin/help.test.tsx +120 -60
- package/src/plugins/builtin/help.tsx +266 -82
- package/src/plugins/builtin/holders/index.test.ts +45 -0
- package/src/plugins/builtin/holders/index.tsx +949 -0
- package/src/plugins/builtin/insider/index.tsx +207 -253
- package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
- package/src/plugins/builtin/insider/insider-data.ts +1 -0
- package/src/plugins/builtin/layout-manager.tsx +12 -40
- package/src/plugins/builtin/market-movers/index.tsx +70 -144
- package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
- package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
- package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
- package/src/plugins/builtin/news-wire/categories.ts +114 -0
- package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
- package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
- package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
- package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
- package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
- package/src/plugins/builtin/news-wire/hash.ts +9 -0
- package/src/plugins/builtin/news-wire/index.tsx +92 -0
- package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
- package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
- package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
- package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
- package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
- package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
- package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
- package/src/plugins/builtin/news-wire/read-state.ts +76 -0
- package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
- package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
- package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
- package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
- package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
- package/src/plugins/builtin/news.tsx +98 -25
- package/src/plugins/builtin/notes-files.ts +51 -10
- package/src/plugins/builtin/notes.tsx +332 -131
- package/src/plugins/builtin/options.test.tsx +185 -0
- package/src/plugins/builtin/options.tsx +346 -206
- package/src/plugins/builtin/plugin-groups.ts +92 -0
- package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
- package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
- package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
- package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
- package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
- package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
- package/src/plugins/builtin/portfolio-list.test.ts +60 -1
- package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
- package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
- package/src/plugins/builtin/research/index.tsx +724 -0
- package/src/plugins/builtin/sec.tsx +67 -19
- package/src/plugins/builtin/sectors/index.tsx +42 -80
- package/src/plugins/builtin/sectors/sector-data.ts +1 -0
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
- package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
- package/src/plugins/builtin/ticker-detail/pane.tsx +143 -49
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
- package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +82 -10
- package/src/plugins/builtin/ticker-detail.test.tsx +258 -33
- package/src/plugins/builtin/ticker-surface.ts +68 -0
- package/src/plugins/builtin/world-indices/index.tsx +44 -82
- package/src/plugins/builtin/world-indices/indices.ts +1 -0
- package/src/plugins/builtin/yahoo.ts +55 -1
- package/src/plugins/builtin/yield-curve/index.tsx +87 -145
- package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
- package/src/plugins/catalog-ui.test.ts +24 -0
- package/src/plugins/catalog-ui.ts +44 -0
- package/src/plugins/catalog.ts +2 -50
- package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
- package/src/plugins/ibkr/account-cache.test.ts +15 -14
- package/src/plugins/ibkr/account-cache.ts +15 -120
- package/src/plugins/ibkr/broker-adapter.ts +67 -0
- package/src/plugins/ibkr/config.test.ts +0 -18
- package/src/plugins/ibkr/dialogs.tsx +14 -56
- package/src/plugins/ibkr/flex.test.ts +57 -2
- package/src/plugins/ibkr/flex.ts +116 -12
- package/src/plugins/ibkr/gateway-helpers.ts +8 -3
- package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
- package/src/plugins/ibkr/gateway-service.test.ts +47 -9
- package/src/plugins/ibkr/gateway-service.ts +264 -2235
- package/src/plugins/ibkr/gateway-types.ts +27 -0
- package/src/plugins/ibkr/index.test.tsx +3 -11
- package/src/plugins/ibkr/index.tsx +1 -0
- package/src/plugins/ibkr/price-normalization.test.ts +1 -6
- package/src/plugins/ibkr/price-normalization.ts +1 -0
- package/src/plugins/ibkr/trade-badge.tsx +6 -5
- package/src/plugins/ibkr/trade-tab.test.tsx +55 -3
- package/src/plugins/ibkr/trade-tab.tsx +166 -117
- package/src/plugins/ibkr/trading-pane.tsx +117 -83
- package/src/plugins/pane-manager.test.ts +3 -1
- package/src/plugins/pane-manager.ts +93 -19
- package/src/plugins/plugin-runtime.test.tsx +176 -74
- package/src/plugins/plugin-runtime.tsx +228 -10
- package/src/plugins/prediction-markets/cache.ts +49 -0
- package/src/plugins/prediction-markets/categories.ts +0 -10
- package/src/plugins/prediction-markets/chart.test.tsx +1 -1
- package/src/plugins/prediction-markets/chart.tsx +21 -20
- package/src/plugins/prediction-markets/cli.ts +1 -0
- package/src/plugins/prediction-markets/controller-data.ts +135 -89
- package/src/plugins/prediction-markets/controller.ts +37 -108
- package/src/plugins/prediction-markets/detail/book.tsx +23 -22
- package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
- package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
- package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
- package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
- package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
- package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
- package/src/plugins/prediction-markets/detail.test.tsx +1 -1
- package/src/plugins/prediction-markets/keyboard.ts +1 -0
- package/src/plugins/prediction-markets/launch.test.ts +1 -1
- package/src/plugins/prediction-markets/launch.ts +1 -1
- package/src/plugins/prediction-markets/metrics.ts +25 -12
- package/src/plugins/prediction-markets/pane.test.tsx +16 -7
- package/src/plugins/prediction-markets/pane.tsx +180 -82
- package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
- package/src/plugins/prediction-markets/services/fetch.ts +27 -10
- package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
- package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
- package/src/plugins/prediction-markets/settings.ts +0 -5
- package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
- package/src/plugins/registry.test.ts +307 -0
- package/src/plugins/registry.ts +293 -82
- package/src/react/input.ts +61 -0
- package/src/react/plugins/plugin-slot.tsx +21 -0
- package/src/react/use-raf-callback.ts +44 -0
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
- package/src/renderers/electrobun/bun/application-menu.ts +118 -0
- package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
- package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
- package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
- package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
- package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
- package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
- package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
- package/src/renderers/electrobun/bun/index.ts +1149 -0
- package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
- package/src/renderers/electrobun/bun/window-frame.ts +46 -0
- package/src/renderers/electrobun/shared/protocol.ts +72 -0
- package/src/renderers/electrobun/view/ai-host.ts +93 -0
- package/src/renderers/electrobun/view/app-services.ts +348 -0
- package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
- package/src/renderers/electrobun/view/config-host.ts +35 -0
- package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
- package/src/renderers/electrobun/view/context-menu.ts +164 -0
- package/src/renderers/electrobun/view/data-table.tsx +773 -0
- package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
- package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
- package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
- package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
- package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
- package/src/renderers/electrobun/view/http-fetch.ts +113 -0
- package/src/renderers/electrobun/view/input-host.tsx +112 -0
- package/src/renderers/electrobun/view/key-event.test.ts +34 -0
- package/src/renderers/electrobun/view/key-event.ts +134 -0
- package/src/renderers/electrobun/view/main.tsx +104 -0
- package/src/renderers/electrobun/view/native-renderer.ts +110 -0
- package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
- package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
- package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
- package/src/renderers/electrobun/view/notes-files.ts +56 -0
- package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
- package/src/renderers/electrobun/view/resource-store.ts +106 -0
- package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
- package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
- package/src/renderers/electrobun/view/toast-host.tsx +65 -0
- package/src/renderers/electrobun/view/ui-host.tsx +1509 -0
- package/src/renderers/electrobun/view/update-host.ts +38 -0
- package/src/renderers/opentui/dialog-host.test.tsx +44 -0
- package/src/renderers/opentui/dialog-host.tsx +56 -0
- package/src/renderers/opentui/host.tsx +140 -0
- package/src/renderers/opentui/image-loader.test.ts +25 -0
- package/src/renderers/opentui/image-loader.ts +105 -0
- package/src/renderers/opentui/image-surface.tsx +266 -0
- package/src/renderers/opentui/input-host.tsx +63 -0
- package/src/renderers/opentui/start.tsx +71 -0
- package/src/renderers/opentui/test-utils.tsx +76 -0
- package/src/renderers/opentui/toast-host.tsx +10 -0
- package/src/renderers/opentui/ui-host.tsx +98 -0
- package/src/sources/gloomberb-cloud.test.ts +288 -17
- package/src/sources/gloomberb-cloud.ts +176 -9
- package/src/sources/provider-errors.ts +1 -0
- package/src/sources/provider-router.test.ts +220 -81
- package/src/sources/provider-router.ts +294 -111
- package/src/sources/sec-edgar.ts +19 -6
- package/src/sources/yahoo-finance.test.ts +0 -5
- package/src/sources/yahoo-finance.ts +420 -11
- package/src/state/app-activity.ts +7 -7
- package/src/state/app-bootstrap.ts +114 -40
- package/src/state/app-context-selector.test.tsx +70 -1
- package/src/state/app-context.test.ts +71 -3
- package/src/state/app-context.test.tsx +14 -7
- package/src/state/app-context.tsx +209 -869
- package/src/state/config-save-scheduler.ts +35 -0
- package/src/state/pane-scroll-registry.tsx +254 -0
- package/src/state/persist-scheduler.test.ts +67 -0
- package/src/state/persist-scheduler.ts +69 -0
- package/src/state/selectors-ui.ts +55 -0
- package/src/state/selectors.ts +15 -71
- package/src/state/session-persistence.test.ts +1 -1
- package/src/state/ticker-refresh-queue.ts +52 -3
- package/src/state/use-inline-tickers.ts +18 -16
- package/src/state/use-quote-streaming.test.ts +0 -6
- package/src/state/use-quote-streaming.test.tsx +1 -1
- package/src/state/use-quote-streaming.ts +3 -2
- package/src/state/use-session-snapshot.ts +32 -17
- package/src/test-support/plugin-runtime.ts +149 -0
- package/src/theme/color-utils.ts +1 -0
- package/src/theme/colors.ts +16 -0
- package/src/types/broker.ts +16 -1
- package/src/types/capability-route-source.ts +23 -0
- package/src/types/config.test.ts +23 -0
- package/src/types/config.ts +145 -10
- package/src/types/context-menu.ts +95 -0
- package/src/types/data-provider.ts +34 -3
- package/src/types/desktop-menu.ts +13 -0
- package/src/types/desktop-window.ts +29 -0
- package/src/types/financials.ts +123 -2
- package/src/types/instrument.ts +1 -0
- package/src/types/news-source.ts +9 -0
- package/src/types/persistence.ts +1 -0
- package/src/types/plugin.ts +32 -8
- package/src/ui/ascii-font.ts +85 -0
- package/src/ui/context-menu.test.ts +94 -0
- package/src/ui/context-menu.tsx +433 -0
- package/src/ui/dialog.tsx +55 -0
- package/src/ui/host.tsx +364 -0
- package/src/ui/index.tsx +134 -0
- package/src/ui/toast.tsx +40 -0
- package/src/updater.test.ts +69 -0
- package/src/updater.ts +61 -18
- package/src/utils/api-client.test.ts +99 -9
- package/src/utils/api-client.ts +378 -18
- package/src/utils/back-navigation.ts +6 -0
- package/src/utils/cli-output.ts +1 -0
- package/src/utils/currency-units.test.ts +1 -6
- package/src/utils/currency-units.ts +1 -0
- package/src/utils/debug-log.ts +68 -0
- package/src/utils/dom-screenshot.ts +326 -0
- package/src/utils/exchange-rate-map.ts +1 -0
- package/src/utils/exchanges.ts +214 -0
- package/src/utils/format.test.ts +7 -0
- package/src/utils/format.ts +1 -0
- package/src/utils/fuzzy-search.ts +1 -0
- package/src/utils/html-entities.test.ts +8 -0
- package/src/utils/html-entities.ts +32 -0
- package/src/utils/http-transport.ts +11 -0
- package/src/utils/main-thread-monitor.ts +127 -0
- package/src/utils/perf-marks.ts +54 -0
- package/src/utils/quote-freshness.ts +27 -50
- package/src/utils/quote-resolution.test.ts +5 -5
- package/src/utils/quote-resolution.ts +0 -1
- package/src/utils/quote-time.ts +1 -5
- package/src/utils/sec.test.ts +0 -12
- package/src/utils/selection-clipboard.ts +14 -6
- package/src/utils/shortcut-labels.test.ts +20 -0
- package/src/utils/shortcut-labels.ts +67 -0
- package/src/utils/slugify.ts +1 -0
- package/src/utils/terminal-input-reset.ts +1 -0
- package/src/utils/text-wrap.ts +82 -0
- package/src/utils/throttled-fetch.test.ts +19 -0
- package/src/utils/throttled-fetch.ts +11 -9
- package/src/utils/ticker-list.test.ts +1 -7
- package/src/utils/ticker-list.ts +1 -0
- package/src/utils/timestamp.ts +29 -0
- package/src/utils/tweet-text.test.ts +13 -0
- package/src/utils/tweet-text.ts +18 -0
- package/src/utils/url.ts +11 -0
- package/src/version.ts +1 -1
- package/src/components/chart/chart-control-hint.tsx +0 -39
- package/src/components/detail-feed-view.test.tsx +0 -180
- package/src/components/detail-feed-view.tsx +0 -531
- package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
- package/src/components/layout/pixel-mouse.test.ts +0 -40
- package/src/components/spinner.tsx +0 -2
- package/src/components/tab-bar.tsx +0 -17
- package/src/components/ui/multi-select-chips.tsx +0 -69
- package/src/plugins/builtin/ai/index.test.tsx +0 -139
- package/src/plugins/builtin/ask-ai.tsx +0 -9
- package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
- package/src/plugins/builtin/correlation/settings.test.ts +0 -34
- package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
- package/src/plugins/builtin/econ/fred-client.ts +0 -79
- package/src/plugins/builtin/fred-settings.ts +0 -10
- package/src/plugins/builtin/options-availability.test.tsx +0 -179
- package/src/plugins/builtin/options-availability.ts +0 -158
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
- package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
- package/src/plugins/prediction-markets/status.ts +0 -24
- package/src/plugins/prediction-markets/table.tsx +0 -87
- package/src/utils/quote-time.test.ts +0 -22
- package/src/utils/selection-clipboard.test.ts +0 -43
- package/src/utils/terminal-input-reset.test.ts +0 -50
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Box, ChartSurface, ScrollBox, Text } from "../../ui";
|
|
2
|
+
import { TextAttributes, type BoxRenderable, type NativeRendererHost as CliRenderer } from "../../ui";
|
|
3
|
+
import { useNativeRenderer, useUiCapabilities } from "../../ui";
|
|
4
|
+
import { useShortcut } from "../../react/input";
|
|
3
5
|
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
4
6
|
import { useChartQueries } from "../../market-data/hooks";
|
|
5
7
|
import { buildChartKey } from "../../market-data/selectors";
|
|
6
|
-
import {
|
|
8
|
+
import { useAppSelector, usePaneSettingValue } from "../../state/app-context";
|
|
9
|
+
import { usePaneFooter, type PaneHint } from "../layout/pane-footer";
|
|
7
10
|
import { blendHex, colors, getComparisonSeriesColor, priceColor } from "../../theme/colors";
|
|
8
11
|
import type { BrokerContractRef } from "../../types/instrument";
|
|
9
12
|
import type { PricePoint } from "../../types/financials";
|
|
10
13
|
import { formatPercentRaw } from "../../utils/format";
|
|
11
14
|
import { formatMarketPriceWithCurrency, formatSignedMarketPrice } from "../../utils/market-format";
|
|
12
|
-
import {
|
|
15
|
+
import { getSharedMarketData } from "../../plugins/registry";
|
|
13
16
|
import {
|
|
14
17
|
applyComparisonZoomAroundAnchor,
|
|
15
18
|
getMaxComparisonPanOffset,
|
|
@@ -79,7 +82,6 @@ import {
|
|
|
79
82
|
} from "./chart-types";
|
|
80
83
|
import {
|
|
81
84
|
computeBitmapSize,
|
|
82
|
-
intersectCellRects,
|
|
83
85
|
renderNativeComparisonChartBase,
|
|
84
86
|
renderNativeCrosshairOverlay,
|
|
85
87
|
type CellRect,
|
|
@@ -90,8 +92,12 @@ import { ensureKittySupport, getCachedKittySupport } from "./native/kitty-suppor
|
|
|
90
92
|
import { resolveChartRendererState } from "./native/renderer-selection";
|
|
91
93
|
import { getNativeSurfaceManager } from "./native/surface-manager";
|
|
92
94
|
import { syncCachedNativeSurface } from "./native/surface-sync";
|
|
95
|
+
import {
|
|
96
|
+
getRenderableCellRect,
|
|
97
|
+
resolveNativeSurfaceVisibleRect,
|
|
98
|
+
type NativeSurfaceRenderableNode,
|
|
99
|
+
} from "./native/surface-visibility";
|
|
93
100
|
import { formatAxisCell, formatDateShort, resolveChartAxisWidth, type StyledContent } from "./chart-renderer";
|
|
94
|
-
import { ChartControlHint } from "./chart-control-hint";
|
|
95
101
|
|
|
96
102
|
const MODE_CHIPS: Record<ComparisonChartRenderMode, string> = {
|
|
97
103
|
area: "A",
|
|
@@ -167,14 +173,6 @@ interface DisplayCursorState {
|
|
|
167
173
|
pixelY: number | null;
|
|
168
174
|
}
|
|
169
175
|
|
|
170
|
-
interface RenderableNode {
|
|
171
|
-
x: number;
|
|
172
|
-
y: number;
|
|
173
|
-
width: number;
|
|
174
|
-
height: number;
|
|
175
|
-
parent: RenderableNode | null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
176
|
const EMPTY_DISPLAY_CURSOR: DisplayCursorState = {
|
|
179
177
|
cellX: null,
|
|
180
178
|
cellY: null,
|
|
@@ -382,41 +380,6 @@ function resolveCursorMotionKind(
|
|
|
382
380
|
: "cell";
|
|
383
381
|
}
|
|
384
382
|
|
|
385
|
-
function extractCellRect(renderable: RenderableNode): CellRect {
|
|
386
|
-
return {
|
|
387
|
-
x: renderable.x,
|
|
388
|
-
y: renderable.y,
|
|
389
|
-
width: renderable.width,
|
|
390
|
-
height: renderable.height,
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function resolveVisibleRect(
|
|
395
|
-
renderable: RenderableNode | null,
|
|
396
|
-
terminalWidth: number,
|
|
397
|
-
terminalHeight: number,
|
|
398
|
-
): CellRect | null {
|
|
399
|
-
if (!renderable) return null;
|
|
400
|
-
|
|
401
|
-
let visible: CellRect = {
|
|
402
|
-
x: 0,
|
|
403
|
-
y: 0,
|
|
404
|
-
width: terminalWidth,
|
|
405
|
-
height: terminalHeight,
|
|
406
|
-
};
|
|
407
|
-
let current: RenderableNode | null = renderable;
|
|
408
|
-
|
|
409
|
-
while (current) {
|
|
410
|
-
const currentRect = extractCellRect(current);
|
|
411
|
-
const nextVisible = intersectCellRects(visible, currentRect);
|
|
412
|
-
if (!nextVisible) return null;
|
|
413
|
-
visible = nextVisible;
|
|
414
|
-
current = current.parent;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return visible;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
383
|
function buildBlankPlotLines(width: number, height: number): string[] {
|
|
421
384
|
return Array.from({ length: height }, () => " ".repeat(width));
|
|
422
385
|
}
|
|
@@ -552,7 +515,8 @@ function ComparisonStockChartView({
|
|
|
552
515
|
symbolSources,
|
|
553
516
|
onOpenSymbol,
|
|
554
517
|
}: ComparisonStockChartViewProps) {
|
|
555
|
-
const renderer =
|
|
518
|
+
const renderer = useNativeRenderer();
|
|
519
|
+
const { canvasCharts, cellWidthPx = 8, cellHeightPx = 18, pixelRatio = 1 } = useUiCapabilities();
|
|
556
520
|
const nativeSurfaceManager = useMemo(() => getNativeSurfaceManager(renderer), [renderer]);
|
|
557
521
|
const [storedRangePreset] = usePaneSettingValue("rangePreset", DEFAULT_COMPARISON_CHART_RANGE_PRESET);
|
|
558
522
|
const [storedResolution] = usePaneSettingValue<ChartResolution>("chartResolution", DEFAULT_COMPARISON_CHART_RESOLUTION);
|
|
@@ -573,6 +537,7 @@ function ComparisonStockChartView({
|
|
|
573
537
|
const supportMap = useMemo(() => buildChartResolutionSupportMap(resolutionSupport ?? []), [resolutionSupport]);
|
|
574
538
|
const [kittySupport, setKittySupport] = useState<boolean | null>(() => getCachedKittySupport(renderer));
|
|
575
539
|
const [displayCursor, setDisplayCursor] = useState<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
|
|
540
|
+
const [canvasBaseBitmapState, setCanvasBaseBitmapState] = useState<{ key: string; bitmap: NativeChartBitmap } | null>(null);
|
|
576
541
|
const plotRef = useRef<BoxRenderable | null>(null);
|
|
577
542
|
const nativeBaseSurfaceIdRef = useRef(`comparison-chart-surface:${paneId}:base`);
|
|
578
543
|
const nativeCrosshairSurfaceIdRef = useRef(`comparison-chart-surface:${paneId}:crosshair`);
|
|
@@ -580,6 +545,7 @@ function ComparisonStockChartView({
|
|
|
580
545
|
const lastNativeGeometryRef = useRef<{ rect: CellRect; visibleRect: CellRect | null } | null>(null);
|
|
581
546
|
const lastNativeBaseBitmapRef = useRef<{ key: string; bitmap: NativeChartBitmap } | null>(null);
|
|
582
547
|
const lastNativeCrosshairBitmapRef = useRef<{ key: string; bitmap: NativeChartBitmap } | null>(null);
|
|
548
|
+
const lastCanvasBaseBitmapRef = useRef<{ key: string; bitmap: NativeChartBitmap } | null>(null);
|
|
583
549
|
const displayCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
|
|
584
550
|
const targetCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
|
|
585
551
|
const cursorMotionKindRef = useRef<ChartCursorMotionKind>("discrete");
|
|
@@ -596,7 +562,7 @@ function ComparisonStockChartView({
|
|
|
596
562
|
const headerRows = 1;
|
|
597
563
|
const controlRows = 2;
|
|
598
564
|
const timeAxisRows = 1;
|
|
599
|
-
const helpRows =
|
|
565
|
+
const helpRows = 0;
|
|
600
566
|
const legendColumns = getLegendColumns(width);
|
|
601
567
|
const legendNeededRows = symbols.length > 0 ? Math.ceil(symbols.length / legendColumns) : 0;
|
|
602
568
|
const legendRows = legendNeededRows > 0
|
|
@@ -734,7 +700,7 @@ function ComparisonStockChartView({
|
|
|
734
700
|
), [nativeSurfaceManager]);
|
|
735
701
|
|
|
736
702
|
useEffect(() => {
|
|
737
|
-
const provider =
|
|
703
|
+
const provider = getSharedMarketData();
|
|
738
704
|
if ((!provider?.getChartResolutionSupport && !provider?.getChartResolutionCapabilities) || symbolSources.length === 0) {
|
|
739
705
|
setResolutionSupport(null);
|
|
740
706
|
return;
|
|
@@ -1009,13 +975,19 @@ function ComparisonStockChartView({
|
|
|
1009
975
|
setViewState(nextState);
|
|
1010
976
|
};
|
|
1011
977
|
|
|
978
|
+
const projectionViewState = useMemo(() => ({
|
|
979
|
+
panOffset: viewState.panOffset,
|
|
980
|
+
zoomLevel: viewState.zoomLevel,
|
|
981
|
+
renderMode: viewState.renderMode,
|
|
982
|
+
}), [viewState.panOffset, viewState.renderMode, viewState.zoomLevel]);
|
|
983
|
+
|
|
1012
984
|
const visibleWindow = useMemo(() => (
|
|
1013
|
-
getVisibleComparisonWindow(series,
|
|
1014
|
-
), [chartWidth,
|
|
985
|
+
getVisibleComparisonWindow(series, projectionViewState, chartWidth)
|
|
986
|
+
), [chartWidth, projectionViewState, series]);
|
|
1015
987
|
|
|
1016
988
|
const projection = useMemo(() => (
|
|
1017
|
-
projectComparisonChartData(series, chartWidth,
|
|
1018
|
-
), [axisMode, chartWidth,
|
|
989
|
+
projectComparisonChartData(series, chartWidth, projectionViewState, axisMode)
|
|
990
|
+
), [axisMode, chartWidth, projectionViewState, series]);
|
|
1019
991
|
|
|
1020
992
|
const cursorX = viewState.cursorX !== null ? clamp(viewState.cursorX, 0, chartWidth - 1) : null;
|
|
1021
993
|
const cursorY = viewState.cursorY !== null ? clamp(viewState.cursorY, 0, chartHeight - 1) : null;
|
|
@@ -1054,6 +1026,8 @@ function ComparisonStockChartView({
|
|
|
1054
1026
|
|
|
1055
1027
|
const rendererState = resolveChartRendererState(preferredRenderer, kittySupport, renderer.resolution);
|
|
1056
1028
|
const effectiveRenderer: ResolvedChartRenderer = rendererState.renderer;
|
|
1029
|
+
const useCanvasChart = canvasCharts && effectiveRenderer !== "kitty";
|
|
1030
|
+
const showNativeUnavailable = rendererState.nativeUnavailable && !useCanvasChart;
|
|
1057
1031
|
|
|
1058
1032
|
const staticScene = useMemo(() => buildComparisonChartScene(projection, {
|
|
1059
1033
|
width: chartWidth,
|
|
@@ -1074,7 +1048,7 @@ function ComparisonStockChartView({
|
|
|
1074
1048
|
}), [chartColors, chartHeight, chartWidth, projection, viewState.selectedSymbol]);
|
|
1075
1049
|
|
|
1076
1050
|
const interactiveResult = useMemo(() => (
|
|
1077
|
-
effectiveRenderer === "kitty"
|
|
1051
|
+
effectiveRenderer === "kitty" || useCanvasChart
|
|
1078
1052
|
? null
|
|
1079
1053
|
: renderComparisonChart(projection, {
|
|
1080
1054
|
width: chartWidth,
|
|
@@ -1084,9 +1058,66 @@ function ComparisonStockChartView({
|
|
|
1084
1058
|
selectedSymbol: viewState.selectedSymbol,
|
|
1085
1059
|
colors: chartColors,
|
|
1086
1060
|
})
|
|
1087
|
-
), [chartColors, chartHeight, chartWidth, displayCursorX, displayCursorY, effectiveRenderer, projection, viewState.selectedSymbol]);
|
|
1061
|
+
), [chartColors, chartHeight, chartWidth, displayCursorX, displayCursorY, effectiveRenderer, projection, useCanvasChart, viewState.selectedSymbol]);
|
|
1062
|
+
|
|
1063
|
+
const result = effectiveRenderer === "kitty" || useCanvasChart ? staticResult : interactiveResult!;
|
|
1064
|
+
const timeAxisLabel = result.timeLabels || staticResult.timeLabels;
|
|
1065
|
+
const footerHints = useMemo<PaneHint[]>(() => {
|
|
1066
|
+
const zoomIn = () => {
|
|
1067
|
+
setViewState((current) => applyComparisonZoomAroundAnchor(
|
|
1068
|
+
clearActivePreset(current),
|
|
1069
|
+
current.zoomLevel * 1.5,
|
|
1070
|
+
RIGHT_EDGE_ANCHOR_RATIO,
|
|
1071
|
+
series,
|
|
1072
|
+
));
|
|
1073
|
+
};
|
|
1074
|
+
const resetView = () => {
|
|
1075
|
+
pendingCanonicalResetRef.current += 1;
|
|
1076
|
+
updateDisplayCursorTarget(EMPTY_DISPLAY_CURSOR, "discrete");
|
|
1077
|
+
setViewState((current) => ({
|
|
1078
|
+
...(resolveResolutionSelection(current, effectiveResolution, supportMap, visibleDateWindow) ?? current),
|
|
1079
|
+
panOffset: 0,
|
|
1080
|
+
zoomLevel: 1,
|
|
1081
|
+
cursorX: null,
|
|
1082
|
+
cursorY: null,
|
|
1083
|
+
}));
|
|
1084
|
+
};
|
|
1085
|
+
const cycleRange = () => {
|
|
1086
|
+
const currentIndex = TIME_RANGES.indexOf(viewState.presetRange);
|
|
1087
|
+
setRangePreset(TIME_RANGES[(currentIndex + 1) % TIME_RANGES.length] ?? TIME_RANGES[0]!);
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
return [
|
|
1091
|
+
{
|
|
1092
|
+
id: "mode",
|
|
1093
|
+
key: "m",
|
|
1094
|
+
label: "ode",
|
|
1095
|
+
onPress: () => setViewState((current) => ({
|
|
1096
|
+
...current,
|
|
1097
|
+
renderMode: current.renderMode === "line" ? "area" : "line",
|
|
1098
|
+
})),
|
|
1099
|
+
},
|
|
1100
|
+
{
|
|
1101
|
+
id: "resolution",
|
|
1102
|
+
key: "r",
|
|
1103
|
+
label: "es",
|
|
1104
|
+
onPress: () => {
|
|
1105
|
+
const currentIndex = resolutionChips.indexOf(effectiveResolution);
|
|
1106
|
+
const nextResolution = resolutionChips[(currentIndex + 1) % resolutionChips.length] ?? "auto";
|
|
1107
|
+
setResolution(nextResolution);
|
|
1108
|
+
},
|
|
1109
|
+
},
|
|
1110
|
+
{ id: "zoom", key: "+/-", label: "zoom", onPress: zoomIn },
|
|
1111
|
+
{ id: "reset", key: "0", label: "reset", onPress: resetView },
|
|
1112
|
+
...(width >= 72 ? [{ id: "range", key: "1-7", label: "range", onPress: cycleRange }] : []),
|
|
1113
|
+
];
|
|
1114
|
+
}, [effectiveResolution, resolutionChips, series, supportMap, viewState.presetRange, visibleDateWindow, width]);
|
|
1115
|
+
|
|
1116
|
+
usePaneFooter("comparison-chart", () => ({
|
|
1117
|
+
order: 10,
|
|
1118
|
+
hints: footerHints,
|
|
1119
|
+
}), [footerHints]);
|
|
1088
1120
|
|
|
1089
|
-
const result = effectiveRenderer === "kitty" ? staticResult : interactiveResult!;
|
|
1090
1121
|
const liveScene = useMemo(() => (
|
|
1091
1122
|
effectiveRenderer === "kitty"
|
|
1092
1123
|
? staticScene
|
|
@@ -1131,8 +1162,9 @@ function ComparisonStockChartView({
|
|
|
1131
1162
|
|
|
1132
1163
|
const syncPlacement = () => {
|
|
1133
1164
|
if (effectiveRenderer !== "kitty" || !rendererState.nativeReady || !plotRef.current) return;
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
1165
|
+
const plot = plotRef.current as NativeSurfaceRenderableNode;
|
|
1166
|
+
const rect = getRenderableCellRect(plot);
|
|
1167
|
+
const visibleRect = resolveNativeSurfaceVisibleRect(plot, renderer.terminalWidth, renderer.terminalHeight);
|
|
1136
1168
|
const previous = lastNativeGeometryRef.current;
|
|
1137
1169
|
if (previous
|
|
1138
1170
|
&& previous.rect.x === rect.x
|
|
@@ -1187,8 +1219,9 @@ function ComparisonStockChartView({
|
|
|
1187
1219
|
return;
|
|
1188
1220
|
}
|
|
1189
1221
|
|
|
1190
|
-
const
|
|
1191
|
-
const
|
|
1222
|
+
const plot = plotRef.current as NativeSurfaceRenderableNode;
|
|
1223
|
+
const plotRect = getRenderableCellRect(plot);
|
|
1224
|
+
const visibleRect = resolveNativeSurfaceVisibleRect(plot, renderer.terminalWidth, renderer.terminalHeight);
|
|
1192
1225
|
const bitmapSize = computeBitmapSize(plotRect, renderer.resolution, renderer.terminalWidth, renderer.terminalHeight);
|
|
1193
1226
|
const bitmapKey = buildComparisonNativeBitmapKey(
|
|
1194
1227
|
symbols.length,
|
|
@@ -1244,8 +1277,9 @@ function ComparisonStockChartView({
|
|
|
1244
1277
|
return;
|
|
1245
1278
|
}
|
|
1246
1279
|
|
|
1247
|
-
const
|
|
1248
|
-
const
|
|
1280
|
+
const plot = plotRef.current as NativeSurfaceRenderableNode;
|
|
1281
|
+
const plotRect = getRenderableCellRect(plot);
|
|
1282
|
+
const visibleRect = resolveNativeSurfaceVisibleRect(plot, renderer.terminalWidth, renderer.terminalHeight);
|
|
1249
1283
|
const bitmapSize = computeBitmapSize(plotRect, renderer.resolution, renderer.terminalWidth, renderer.terminalHeight);
|
|
1250
1284
|
const renderablePixelSize = getRenderablePixelSize(plotRef.current, renderer);
|
|
1251
1285
|
const overlayPixelX = scaleLocalPixelCoordinate(
|
|
@@ -1309,7 +1343,7 @@ function ComparisonStockChartView({
|
|
|
1309
1343
|
rendererState.nativeReady,
|
|
1310
1344
|
]);
|
|
1311
1345
|
|
|
1312
|
-
|
|
1346
|
+
useShortcut((event) => {
|
|
1313
1347
|
if (!focused || symbols.length === 0) return;
|
|
1314
1348
|
|
|
1315
1349
|
switch (event.name) {
|
|
@@ -1575,12 +1609,13 @@ function ComparisonStockChartView({
|
|
|
1575
1609
|
};
|
|
1576
1610
|
|
|
1577
1611
|
if (symbols.length === 0) {
|
|
1578
|
-
return <
|
|
1612
|
+
return <Text fg={colors.textDim}>No comparison tickers configured.</Text>;
|
|
1579
1613
|
}
|
|
1580
1614
|
|
|
1581
1615
|
const hasChartData = series.some((entry) => entry.points.length > 0);
|
|
1582
|
-
const
|
|
1583
|
-
const
|
|
1616
|
+
const cursorScene = effectiveRenderer === "kitty" ? staticScene : liveScene;
|
|
1617
|
+
const selectedSeries = hasChartData ? (cursorScene?.selectedSeries ?? staticResult.selectedSeries) : null;
|
|
1618
|
+
const selectedPoint = hasChartData ? (cursorScene?.selectedPoint ?? staticResult.selectedPoint) : null;
|
|
1584
1619
|
const selectedRawValue = selectedPoint?.rawValue ?? selectedSeries?.latestRawValue ?? null;
|
|
1585
1620
|
const selectedBaseValue = selectedSeries?.baseValue ?? null;
|
|
1586
1621
|
const selectedChange = selectedRawValue !== null && selectedBaseValue !== null
|
|
@@ -1590,12 +1625,14 @@ function ComparisonStockChartView({
|
|
|
1590
1625
|
? (selectedChange / selectedBaseValue) * 100
|
|
1591
1626
|
: null;
|
|
1592
1627
|
const selectedCurrency = selectedSeries?.currency ?? "USD";
|
|
1593
|
-
const displayDate =
|
|
1628
|
+
const displayDate = cursorScene?.activeDate ?? staticResult.activeDate;
|
|
1594
1629
|
const visiblePriceRange = result.priceRange ?? staticResult.priceRange ?? undefined;
|
|
1595
1630
|
const axisLabels = new Map(staticResult.axisLabels.map((entry) => [entry.row, entry.label]));
|
|
1596
|
-
const
|
|
1631
|
+
const cursorRow = useCanvasChart ? cursorScene?.cursorRow ?? null : result.cursorRow;
|
|
1632
|
+
const crosshairValue = useCanvasChart ? cursorScene?.crosshairValue ?? null : result.crosshairValue;
|
|
1633
|
+
const cursorAxisLabel = cursorRow !== null && crosshairValue !== null
|
|
1597
1634
|
? formatComparisonCursorAxisValue(
|
|
1598
|
-
|
|
1635
|
+
crosshairValue,
|
|
1599
1636
|
projection.effectiveAxisMode,
|
|
1600
1637
|
visiblePriceRange,
|
|
1601
1638
|
)
|
|
@@ -1605,57 +1642,188 @@ function ComparisonStockChartView({
|
|
|
1605
1642
|
end: visibleWindow.dates[visibleWindow.dates.length - 1] ?? null,
|
|
1606
1643
|
});
|
|
1607
1644
|
|
|
1645
|
+
const canvasBitmapSize = useMemo(() => {
|
|
1646
|
+
if (!canvasCharts) return null;
|
|
1647
|
+
const resolutionScale = Math.max(1, pixelRatio);
|
|
1648
|
+
return {
|
|
1649
|
+
pixelWidth: Math.max(1, Math.round(chartWidth * cellWidthPx * resolutionScale)),
|
|
1650
|
+
pixelHeight: Math.max(1, Math.round(chartHeight * cellHeightPx * resolutionScale)),
|
|
1651
|
+
};
|
|
1652
|
+
}, [canvasCharts, cellHeightPx, cellWidthPx, chartHeight, chartWidth, pixelRatio]);
|
|
1653
|
+
|
|
1654
|
+
const canvasProjection = useMemo(() => (
|
|
1655
|
+
canvasBitmapSize
|
|
1656
|
+
? projectComparisonChartData(
|
|
1657
|
+
series,
|
|
1658
|
+
Math.max(chartWidth, canvasBitmapSize.pixelWidth),
|
|
1659
|
+
projectionViewState,
|
|
1660
|
+
axisMode,
|
|
1661
|
+
)
|
|
1662
|
+
: null
|
|
1663
|
+
), [axisMode, canvasBitmapSize, chartWidth, projectionViewState, series]);
|
|
1664
|
+
|
|
1665
|
+
const canvasBaseScene = useMemo(() => (
|
|
1666
|
+
canvasProjection
|
|
1667
|
+
? buildComparisonChartScene(canvasProjection, {
|
|
1668
|
+
width: Math.max(chartWidth, canvasBitmapSize?.pixelWidth ?? chartWidth),
|
|
1669
|
+
height: chartHeight,
|
|
1670
|
+
cursorX: null,
|
|
1671
|
+
cursorY: null,
|
|
1672
|
+
selectedSymbol: viewState.selectedSymbol,
|
|
1673
|
+
colors: chartColors,
|
|
1674
|
+
})
|
|
1675
|
+
: null
|
|
1676
|
+
), [canvasBitmapSize?.pixelWidth, canvasProjection, chartColors, chartHeight, chartWidth, viewState.selectedSymbol]);
|
|
1677
|
+
|
|
1678
|
+
const canvasBaseBitmapKey = useMemo(() => {
|
|
1679
|
+
if (!canvasBitmapSize || !canvasProjection || !hasChartData || isBlockingBody || bodyMessage) return null;
|
|
1680
|
+
return buildComparisonNativeBitmapKey(
|
|
1681
|
+
symbols.length,
|
|
1682
|
+
canvasProjection,
|
|
1683
|
+
viewState.selectedSymbol,
|
|
1684
|
+
canvasBitmapSize.pixelWidth,
|
|
1685
|
+
canvasBitmapSize.pixelHeight,
|
|
1686
|
+
[chartColors.bgColor, chartColors.gridColor, chartColors.crosshairColor].join(","),
|
|
1687
|
+
);
|
|
1688
|
+
}, [
|
|
1689
|
+
bodyMessage,
|
|
1690
|
+
canvasBitmapSize,
|
|
1691
|
+
canvasProjection,
|
|
1692
|
+
chartColors.bgColor,
|
|
1693
|
+
chartColors.crosshairColor,
|
|
1694
|
+
chartColors.gridColor,
|
|
1695
|
+
hasChartData,
|
|
1696
|
+
isBlockingBody,
|
|
1697
|
+
symbols.length,
|
|
1698
|
+
viewState.selectedSymbol,
|
|
1699
|
+
]);
|
|
1700
|
+
|
|
1701
|
+
const canvasBaseBitmap = useMemo<NativeChartBitmap | null>(() => {
|
|
1702
|
+
return canvasBaseBitmapKey && canvasBaseBitmapState?.key === canvasBaseBitmapKey
|
|
1703
|
+
? canvasBaseBitmapState.bitmap
|
|
1704
|
+
: null;
|
|
1705
|
+
}, [canvasBaseBitmapKey, canvasBaseBitmapState]);
|
|
1706
|
+
|
|
1707
|
+
const visibleCanvasBaseBitmap = useMemo<NativeChartBitmap | null>(() => {
|
|
1708
|
+
if (canvasBaseBitmap) return canvasBaseBitmap;
|
|
1709
|
+
if (!canvasBaseBitmapKey || !canvasBitmapSize || !canvasBaseBitmapState) return null;
|
|
1710
|
+
return canvasBaseBitmapState.bitmap.width === canvasBitmapSize.pixelWidth
|
|
1711
|
+
&& canvasBaseBitmapState.bitmap.height === canvasBitmapSize.pixelHeight
|
|
1712
|
+
? canvasBaseBitmapState.bitmap
|
|
1713
|
+
: null;
|
|
1714
|
+
}, [canvasBaseBitmap, canvasBaseBitmapKey, canvasBaseBitmapState, canvasBitmapSize]);
|
|
1715
|
+
|
|
1716
|
+
useEffect(() => {
|
|
1717
|
+
if (!canvasBitmapSize || !canvasBaseBitmapKey || !canvasBaseScene) {
|
|
1718
|
+
lastCanvasBaseBitmapRef.current = null;
|
|
1719
|
+
setCanvasBaseBitmapState((current) => (current === null ? current : null));
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
const cachedBitmap = lastCanvasBaseBitmapRef.current?.key === canvasBaseBitmapKey
|
|
1724
|
+
? lastCanvasBaseBitmapRef.current.bitmap
|
|
1725
|
+
: null;
|
|
1726
|
+
if (cachedBitmap) {
|
|
1727
|
+
setCanvasBaseBitmapState((current) => (
|
|
1728
|
+
current?.key === canvasBaseBitmapKey ? current : { key: canvasBaseBitmapKey, bitmap: cachedBitmap }
|
|
1729
|
+
));
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
let cancelled = false;
|
|
1734
|
+
const frame = requestAnimationFrameSafe(() => {
|
|
1735
|
+
const bitmap = renderNativeComparisonChartBase(canvasBaseScene, canvasBitmapSize.pixelWidth, canvasBitmapSize.pixelHeight);
|
|
1736
|
+
if (cancelled) return;
|
|
1737
|
+
lastCanvasBaseBitmapRef.current = { key: canvasBaseBitmapKey, bitmap };
|
|
1738
|
+
setCanvasBaseBitmapState((current) => (
|
|
1739
|
+
current?.key === canvasBaseBitmapKey ? current : { key: canvasBaseBitmapKey, bitmap }
|
|
1740
|
+
));
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1743
|
+
return () => {
|
|
1744
|
+
cancelled = true;
|
|
1745
|
+
cancelAnimationFrameSafe(frame);
|
|
1746
|
+
};
|
|
1747
|
+
}, [canvasBaseBitmapKey, canvasBaseScene, canvasBitmapSize]);
|
|
1748
|
+
|
|
1749
|
+
const canvasCrosshair = useMemo(() => {
|
|
1750
|
+
if (!canvasBitmapSize || !visibleCanvasBaseBitmap || !nativeCrosshair) return null;
|
|
1751
|
+
const renderablePixelSize = getRenderablePixelSize(plotRef.current, renderer);
|
|
1752
|
+
const overlayPixelX = scaleLocalPixelCoordinate(
|
|
1753
|
+
nativeCrosshair.pixelX,
|
|
1754
|
+
renderablePixelSize?.pixelWidth ?? canvasBitmapSize.pixelWidth,
|
|
1755
|
+
canvasBitmapSize.pixelWidth,
|
|
1756
|
+
);
|
|
1757
|
+
const overlayPixelY = scaleLocalPixelCoordinate(
|
|
1758
|
+
nativeCrosshair.pixelY,
|
|
1759
|
+
renderablePixelSize?.pixelHeight ?? canvasBitmapSize.pixelHeight,
|
|
1760
|
+
canvasBitmapSize.pixelHeight,
|
|
1761
|
+
);
|
|
1762
|
+
if (overlayPixelX === null || overlayPixelY === null) return null;
|
|
1763
|
+
return {
|
|
1764
|
+
pixelX: overlayPixelX,
|
|
1765
|
+
pixelY: overlayPixelY,
|
|
1766
|
+
color: nativeCrosshair.colors.crosshairColor,
|
|
1767
|
+
};
|
|
1768
|
+
}, [canvasBitmapSize, nativeCrosshair, renderer, visibleCanvasBaseBitmap]);
|
|
1769
|
+
|
|
1770
|
+
const plotBitmaps = useMemo(() => {
|
|
1771
|
+
if (!visibleCanvasBaseBitmap) return null;
|
|
1772
|
+
return [visibleCanvasBaseBitmap];
|
|
1773
|
+
}, [visibleCanvasBaseBitmap]);
|
|
1774
|
+
|
|
1608
1775
|
const plotLines: Array<string | StyledContent> = effectiveRenderer === "kitty"
|
|
1609
1776
|
? blankPlotLines
|
|
1610
1777
|
: result.lines;
|
|
1611
|
-
const plotContent =
|
|
1612
|
-
|
|
1613
|
-
|
|
1778
|
+
const plotContent = plotBitmaps || (useCanvasChart && canvasBaseBitmapKey)
|
|
1779
|
+
? null
|
|
1780
|
+
: plotLines.map((line, index) => (
|
|
1781
|
+
<Text key={index} content={line as unknown as string} />
|
|
1782
|
+
));
|
|
1614
1783
|
|
|
1615
1784
|
const plotBox = (
|
|
1616
|
-
<
|
|
1785
|
+
<ChartSurface
|
|
1617
1786
|
ref={plotRef}
|
|
1618
1787
|
width={chartWidth}
|
|
1619
1788
|
height={chartHeight}
|
|
1620
1789
|
flexDirection="column"
|
|
1621
|
-
|
|
1790
|
+
bitmaps={plotBitmaps}
|
|
1791
|
+
crosshair={canvasCrosshair}
|
|
1622
1792
|
onMouseMove={hasChartData && !isBlockingBody && !bodyMessage ? handlePlotMove : undefined}
|
|
1623
1793
|
onMouseDown={hasChartData && !isBlockingBody && !bodyMessage ? handlePlotDown : undefined}
|
|
1624
1794
|
onMouseUp={hasChartData && !isBlockingBody && !bodyMessage ? () => { dragRef.current = null; } : undefined}
|
|
1625
1795
|
onMouseDrag={hasChartData && !isBlockingBody && !bodyMessage ? handlePlotDrag : undefined}
|
|
1626
1796
|
onMouseDragEnd={hasChartData && !isBlockingBody && !bodyMessage ? () => { dragRef.current = null; } : undefined}
|
|
1627
|
-
|
|
1628
|
-
dragRef.current = null;
|
|
1629
|
-
} : undefined}
|
|
1797
|
+
onMouseScroll={hasChartData && !isBlockingBody && !bodyMessage ? handlePlotScroll : undefined}
|
|
1630
1798
|
>
|
|
1631
1799
|
{isBlockingBody
|
|
1632
1800
|
? (
|
|
1633
|
-
<
|
|
1634
|
-
<
|
|
1635
|
-
</
|
|
1801
|
+
<Box flexGrow={1} alignItems="center" justifyContent="center">
|
|
1802
|
+
<Text fg={colors.textDim}>Loading chart...</Text>
|
|
1803
|
+
</Box>
|
|
1636
1804
|
)
|
|
1637
1805
|
: bodyMessage
|
|
1638
1806
|
? (
|
|
1639
|
-
<
|
|
1640
|
-
<
|
|
1641
|
-
</
|
|
1807
|
+
<Box flexGrow={1} alignItems="center" justifyContent="center">
|
|
1808
|
+
<Text fg={colors.textDim}>{bodyMessage}</Text>
|
|
1809
|
+
</Box>
|
|
1642
1810
|
)
|
|
1643
1811
|
: plotContent}
|
|
1644
|
-
</
|
|
1812
|
+
</ChartSurface>
|
|
1645
1813
|
);
|
|
1646
1814
|
|
|
1647
1815
|
const axisBox = (
|
|
1648
|
-
<
|
|
1816
|
+
<Box width={axisSectionWidth} height={chartHeight} flexDirection="column">
|
|
1649
1817
|
{Array.from({ length: chartHeight }, (_, row) => {
|
|
1650
|
-
const isCursorRow = cursorAxisLabel !== null &&
|
|
1818
|
+
const isCursorRow = cursorAxisLabel !== null && cursorRow === row;
|
|
1651
1819
|
const label = isCursorRow ? cursorAxisLabel : (axisLabels.get(row) ?? null);
|
|
1652
1820
|
return (
|
|
1653
|
-
<
|
|
1821
|
+
<Text key={row} fg={isCursorRow ? chartColors.crosshairColor : colors.textDim}>
|
|
1654
1822
|
{formatAxisCell(label, axisWidth).padEnd(axisSectionWidth)}
|
|
1655
|
-
</
|
|
1823
|
+
</Text>
|
|
1656
1824
|
);
|
|
1657
1825
|
})}
|
|
1658
|
-
</
|
|
1826
|
+
</Box>
|
|
1659
1827
|
);
|
|
1660
1828
|
|
|
1661
1829
|
const legendRowsData = Array.from({ length: Math.ceil(symbols.length / legendColumns) }, (_, rowIndex) => (
|
|
@@ -1663,17 +1831,17 @@ function ComparisonStockChartView({
|
|
|
1663
1831
|
));
|
|
1664
1832
|
|
|
1665
1833
|
return (
|
|
1666
|
-
<
|
|
1834
|
+
<Box
|
|
1667
1835
|
flexDirection="column"
|
|
1668
1836
|
flexGrow={1}
|
|
1669
1837
|
onMouseScroll={hasChartData && !isBlockingBody && !bodyMessage ? handlePlotScroll : undefined}
|
|
1670
1838
|
>
|
|
1671
|
-
<
|
|
1672
|
-
<
|
|
1839
|
+
<Box flexDirection="row" gap={2} height={1}>
|
|
1840
|
+
<Text attributes={TextAttributes.BOLD} fg={colors.textBright}>
|
|
1673
1841
|
{viewState.selectedSymbol ?? "Compare"} - {getChartResolutionLabel(effectiveResolution)}
|
|
1674
|
-
</
|
|
1675
|
-
<
|
|
1676
|
-
<
|
|
1842
|
+
</Text>
|
|
1843
|
+
<Text fg={colors.textDim}>{visibleLabel}</Text>
|
|
1844
|
+
<Text fg={selectedChange !== null ? priceColor(selectedChange) : colors.textDim}>
|
|
1677
1845
|
{selectedRawValue !== null
|
|
1678
1846
|
? formatMarketPriceWithCurrency(selectedRawValue, selectedCurrency, {
|
|
1679
1847
|
minimumFractionDigits: 2,
|
|
@@ -1681,8 +1849,8 @@ function ComparisonStockChartView({
|
|
|
1681
1849
|
priceRange: visiblePriceRange,
|
|
1682
1850
|
})
|
|
1683
1851
|
: "—"}
|
|
1684
|
-
</
|
|
1685
|
-
<
|
|
1852
|
+
</Text>
|
|
1853
|
+
<Text fg={selectedChange !== null ? priceColor(selectedChange) : colors.textDim}>
|
|
1686
1854
|
{selectedChange !== null && selectedChangePct !== null
|
|
1687
1855
|
? `${formatSignedMarketPrice(selectedChange, {
|
|
1688
1856
|
minimumFractionDigits: 2,
|
|
@@ -1690,14 +1858,14 @@ function ComparisonStockChartView({
|
|
|
1690
1858
|
priceRange: visiblePriceRange,
|
|
1691
1859
|
})} (${formatPercentRaw(selectedChangePct)})`
|
|
1692
1860
|
: "Waiting for data"}
|
|
1693
|
-
</
|
|
1694
|
-
{displayDate && <
|
|
1695
|
-
</
|
|
1861
|
+
</Text>
|
|
1862
|
+
{displayDate && <Text fg={colors.textDim}>{formatDateShort(displayDate)}</Text>}
|
|
1863
|
+
</Box>
|
|
1696
1864
|
|
|
1697
|
-
<
|
|
1698
|
-
<
|
|
1865
|
+
<Box flexDirection="row" height={1}>
|
|
1866
|
+
<Box flexDirection="row" gap={1}>
|
|
1699
1867
|
{TIME_RANGES.map((range, index) => (
|
|
1700
|
-
<
|
|
1868
|
+
<Text
|
|
1701
1869
|
key={range}
|
|
1702
1870
|
fg={activePreset === range ? colors.textBright : (isRangePresetSupported(range, availableManualResolutions) ? colors.textDim : colors.textMuted)}
|
|
1703
1871
|
attributes={activePreset === range ? TextAttributes.BOLD : 0}
|
|
@@ -1708,49 +1876,49 @@ function ComparisonStockChartView({
|
|
|
1708
1876
|
}}
|
|
1709
1877
|
>
|
|
1710
1878
|
{`${index + 1}:${range}`}
|
|
1711
|
-
</
|
|
1879
|
+
</Text>
|
|
1712
1880
|
))}
|
|
1713
|
-
</
|
|
1714
|
-
</
|
|
1881
|
+
</Box>
|
|
1882
|
+
</Box>
|
|
1715
1883
|
|
|
1716
|
-
<
|
|
1717
|
-
<
|
|
1884
|
+
<Box flexDirection="row" height={1}>
|
|
1885
|
+
<Box flexDirection="row" gap={1}>
|
|
1718
1886
|
{resolutionChips.map((resolution) => (
|
|
1719
|
-
<
|
|
1887
|
+
<Text
|
|
1720
1888
|
key={resolution}
|
|
1721
1889
|
fg={effectiveResolution === resolution ? colors.textBright : colors.textDim}
|
|
1722
1890
|
attributes={effectiveResolution === resolution ? TextAttributes.BOLD : 0}
|
|
1723
1891
|
onMouseDown={() => setResolution(resolution)}
|
|
1724
1892
|
>
|
|
1725
1893
|
{getChartResolutionLabel(resolution)}
|
|
1726
|
-
</
|
|
1894
|
+
</Text>
|
|
1727
1895
|
))}
|
|
1728
1896
|
{isUpdating && (
|
|
1729
|
-
<
|
|
1897
|
+
<Text fg={colors.textDim}>updating</Text>
|
|
1730
1898
|
)}
|
|
1731
|
-
</
|
|
1732
|
-
<
|
|
1733
|
-
<
|
|
1899
|
+
</Box>
|
|
1900
|
+
<Box flexGrow={1} />
|
|
1901
|
+
<Box flexDirection="row" gap={1}>
|
|
1734
1902
|
{COMPARISON_RENDER_MODES.map((mode) => (
|
|
1735
|
-
<
|
|
1903
|
+
<Text
|
|
1736
1904
|
key={mode}
|
|
1737
1905
|
fg={viewState.renderMode === mode ? colors.textBright : colors.textDim}
|
|
1738
1906
|
attributes={viewState.renderMode === mode ? TextAttributes.BOLD : 0}
|
|
1739
1907
|
onMouseDown={() => setViewState((current) => ({ ...current, renderMode: mode }))}
|
|
1740
1908
|
>
|
|
1741
1909
|
{MODE_CHIPS[mode]}
|
|
1742
|
-
</
|
|
1910
|
+
</Text>
|
|
1743
1911
|
))}
|
|
1744
1912
|
{projection.warning && (
|
|
1745
|
-
<
|
|
1913
|
+
<Text fg={colors.textDim}>{projection.warning}</Text>
|
|
1746
1914
|
)}
|
|
1747
|
-
{
|
|
1748
|
-
<
|
|
1915
|
+
{showNativeUnavailable && (
|
|
1916
|
+
<Text fg={colors.textDim}>native unavailable</Text>
|
|
1749
1917
|
)}
|
|
1750
|
-
</
|
|
1751
|
-
</
|
|
1918
|
+
</Box>
|
|
1919
|
+
</Box>
|
|
1752
1920
|
|
|
1753
|
-
<
|
|
1921
|
+
<Box
|
|
1754
1922
|
flexDirection="row"
|
|
1755
1923
|
height={chartHeight}
|
|
1756
1924
|
gap={axisGap}
|
|
@@ -1758,17 +1926,17 @@ function ComparisonStockChartView({
|
|
|
1758
1926
|
>
|
|
1759
1927
|
{plotBox}
|
|
1760
1928
|
{axisBox}
|
|
1761
|
-
</
|
|
1929
|
+
</Box>
|
|
1762
1930
|
|
|
1763
|
-
<
|
|
1764
|
-
<
|
|
1765
|
-
</
|
|
1931
|
+
<Box height={timeAxisRows}>
|
|
1932
|
+
<Text fg={colors.textDim}>{timeAxisLabel}</Text>
|
|
1933
|
+
</Box>
|
|
1766
1934
|
|
|
1767
1935
|
{legendRows > 0 && (
|
|
1768
|
-
<
|
|
1769
|
-
<
|
|
1936
|
+
<ScrollBox height={legendRows} scrollY>
|
|
1937
|
+
<Box flexDirection="column">
|
|
1770
1938
|
{legendRowsData.map((legendRow, rowIndex) => (
|
|
1771
|
-
<
|
|
1939
|
+
<Box key={`legend-row:${rowIndex}`} flexDirection="row" gap={1}>
|
|
1772
1940
|
{legendRow.map((symbol) => {
|
|
1773
1941
|
const item = projection.series.find((entry) => entry.symbol === symbol) ?? null;
|
|
1774
1942
|
const isSelected = viewState.selectedSymbol === symbol;
|
|
@@ -1787,7 +1955,7 @@ function ComparisonStockChartView({
|
|
|
1787
1955
|
: `${symbol} waiting`;
|
|
1788
1956
|
|
|
1789
1957
|
return (
|
|
1790
|
-
<
|
|
1958
|
+
<Box
|
|
1791
1959
|
key={symbol}
|
|
1792
1960
|
width={legendItemWidth}
|
|
1793
1961
|
backgroundColor={isSelected ? blendHex(colors.panel, colors.borderFocused, 0.18) : colors.panel}
|
|
@@ -1797,59 +1965,51 @@ function ComparisonStockChartView({
|
|
|
1797
1965
|
onOpenSymbol(symbol);
|
|
1798
1966
|
}}
|
|
1799
1967
|
>
|
|
1800
|
-
<
|
|
1968
|
+
<Text fg={item?.color ?? colors.textDim} attributes={isSelected ? TextAttributes.BOLD : 0}>
|
|
1801
1969
|
{clipText(`${isSelected ? ">" : " "} ${summary}`, legendItemWidth)}
|
|
1802
|
-
</
|
|
1803
|
-
</
|
|
1970
|
+
</Text>
|
|
1971
|
+
</Box>
|
|
1804
1972
|
);
|
|
1805
1973
|
})}
|
|
1806
|
-
</
|
|
1974
|
+
</Box>
|
|
1807
1975
|
))}
|
|
1808
|
-
</
|
|
1809
|
-
</
|
|
1976
|
+
</Box>
|
|
1977
|
+
</ScrollBox>
|
|
1810
1978
|
)}
|
|
1811
1979
|
|
|
1812
|
-
|
|
1813
|
-
<box flexDirection="row" gap={1}>
|
|
1814
|
-
<ChartControlHint hotkey="m" label="ode" />
|
|
1815
|
-
<ChartControlHint hotkey="r" label="es" />
|
|
1816
|
-
<ChartControlHint hotkey="+/-" label="zoom" />
|
|
1817
|
-
<ChartControlHint hotkey="0" label="reset" />
|
|
1818
|
-
{width >= 72 && <ChartControlHint hotkey="1-7" label="range" />}
|
|
1819
|
-
{width >= 88 && <ChartControlHint hotkey="up/down" label="legend" />}
|
|
1820
|
-
</box>
|
|
1821
|
-
</box>
|
|
1822
|
-
</box>
|
|
1980
|
+
</Box>
|
|
1823
1981
|
);
|
|
1824
1982
|
}
|
|
1825
1983
|
|
|
1826
1984
|
const MemoizedComparisonStockChartView = memo(ComparisonStockChartView);
|
|
1827
1985
|
|
|
1828
1986
|
export function ComparisonStockChart(props: ComparisonStockChartProps) {
|
|
1829
|
-
const
|
|
1987
|
+
const tickers = useAppSelector((state) => state.tickers);
|
|
1988
|
+
const financials = useAppSelector((state) => state.financials);
|
|
1989
|
+
const chartPreferences = useAppSelector((state) => state.config.chartPreferences);
|
|
1830
1990
|
const symbolsKey = props.symbols.join("|");
|
|
1831
1991
|
const stableSymbols = useMemo(() => props.symbols, [symbolsKey]);
|
|
1832
1992
|
const symbolSources = useMemo<ComparisonChartSymbolSource[]>(() => stableSymbols.map((symbol) => {
|
|
1833
|
-
const ticker =
|
|
1834
|
-
const
|
|
1993
|
+
const ticker = tickers.get(symbol) ?? null;
|
|
1994
|
+
const financial = financials.get(symbol) ?? null;
|
|
1835
1995
|
const instrument = ticker?.metadata.broker_contracts?.[0] ?? null;
|
|
1836
1996
|
return {
|
|
1837
1997
|
symbol,
|
|
1838
|
-
currency:
|
|
1998
|
+
currency: financial?.quote?.currency ?? ticker?.metadata.currency,
|
|
1839
1999
|
exchange: ticker?.metadata.exchange ?? "",
|
|
1840
2000
|
brokerId: instrument?.brokerId,
|
|
1841
2001
|
brokerInstanceId: instrument?.brokerInstanceId,
|
|
1842
2002
|
instrument,
|
|
1843
|
-
priceHistory:
|
|
2003
|
+
priceHistory: financial?.priceHistory ?? [],
|
|
1844
2004
|
};
|
|
1845
|
-
}), [
|
|
2005
|
+
}), [financials, stableSymbols, tickers]);
|
|
1846
2006
|
|
|
1847
2007
|
return (
|
|
1848
2008
|
<MemoizedComparisonStockChartView
|
|
1849
2009
|
{...props}
|
|
1850
2010
|
symbols={stableSymbols}
|
|
1851
|
-
defaultRenderMode={
|
|
1852
|
-
preferredRenderer={
|
|
2011
|
+
defaultRenderMode={chartPreferences.defaultRenderMode}
|
|
2012
|
+
preferredRenderer={chartPreferences.renderer}
|
|
1853
2013
|
symbolSources={symbolSources}
|
|
1854
2014
|
/>
|
|
1855
2015
|
);
|