gloomberb 0.18.1 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +106 -5
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +21 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/request.ts +5 -3
- package/src/api-client/telemetry.ts +17 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/global-shortcuts.ts +1 -16
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/app/runtime/update.ts +28 -8
- package/src/architecture/pane-tabs-allowlist.json +65 -0
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +354 -187
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +167 -60
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/help.ts +1 -1
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +12 -1
- package/src/cli/issuer-membership.ts +103 -0
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +51 -3
- package/src/cli/name-entries.ts +51 -12
- package/src/cli/pane-functions/capabilities.ts +2 -0
- package/src/cli/pane-functions/catalog.ts +19 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +11 -5
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +31 -11
- package/src/cli/pane-functions/index.ts +67 -7
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +21 -6
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/report-tables.ts +136 -18
- package/src/cli/result.ts +12 -4
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +47 -11
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +25 -7
- package/src/components/chart/composite/drawing-store.ts +26 -6
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/interactions.ts +2 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +69 -8
- package/src/components/chart/composite/types.ts +6 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +1 -1
- package/src/components/command-bar/routes/root/command-items.ts +32 -6
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/data-table/view.tsx +23 -3
- package/src/components/index.ts +3 -0
- package/src/components/layout/detached-pane-shell.tsx +1 -0
- package/src/components/layout/floating-pane.tsx +1 -0
- package/src/components/layout/header.tsx +228 -55
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/layout/pane/header.tsx +10 -3
- package/src/components/layout/pane/index.tsx +1 -0
- package/src/components/layout/pane/pane-tabs.tsx +9 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/stat-grid.tsx +28 -3
- package/src/components/ui/status.tsx +20 -4
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/app-services.ts +4 -0
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +31 -3
- package/src/i18n/ja.ts +31 -3
- package/src/i18n/ko.ts +31 -3
- package/src/i18n/zh-cn.ts +32 -4
- package/src/i18n/zh-tw.ts +32 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/alert-engine.ts +3 -1
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/events.ts +1 -0
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/alerts/research-rules.ts +1 -0
- package/src/plugins/builtin/analytics/risk-pane.tsx +10 -7
- package/src/plugins/builtin/attention/pane.tsx +16 -14
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/awards/pane.tsx +6 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/export.ts +1 -1
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +184 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +507 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +578 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +11 -7
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/catalysts/client.ts +2 -2
- package/src/plugins/builtin/catalysts/pane.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +32 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +39 -9
- package/src/plugins/builtin/company-attention/app-rank.tsx +4 -5
- package/src/plugins/builtin/company-attention/pane.tsx +13 -11
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +30 -15
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/correlation/headless.ts +2 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
- package/src/plugins/builtin/correlation/settings.ts +1 -0
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/cot/pane.tsx +4 -3
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/doe/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +30 -5
- package/src/plugins/builtin/equity-screener/model.ts +68 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +129 -119
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +43 -21
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +4 -1
- package/src/plugins/builtin/gpu/changes.tsx +10 -10
- package/src/plugins/builtin/gpu/equities.tsx +4 -1
- package/src/plugins/builtin/gpu/history.tsx +4 -2
- package/src/plugins/builtin/gpu/pane.tsx +5 -3
- package/src/plugins/builtin/holders/client.ts +2 -1
- package/src/plugins/builtin/holders/headless.ts +2 -1
- package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
- package/src/plugins/builtin/insider/base-values.ts +76 -0
- package/src/plugins/builtin/insider/client.ts +3 -0
- package/src/plugins/builtin/insider/headless.ts +48 -10
- package/src/plugins/builtin/insider/index.tsx +17 -8
- package/src/plugins/builtin/insider/issuer.ts +34 -4
- package/src/plugins/builtin/insider/signals-client.ts +2 -2
- package/src/plugins/builtin/insider/table-model.ts +35 -13
- package/src/plugins/builtin/insider/table.tsx +5 -2
- package/src/plugins/builtin/iv-history/events.ts +145 -0
- package/src/plugins/builtin/iv-history/format.ts +3 -0
- package/src/plugins/builtin/iv-history/headless.ts +91 -7
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/jodi/pane.tsx +4 -4
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +4 -3
- package/src/plugins/builtin/market-movers/screener.ts +4 -3
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +1 -1
- package/src/plugins/builtin/notes/export.ts +17 -6
- package/src/plugins/builtin/notes/files.ts +16 -5
- package/src/plugins/builtin/options/enrichment-model.ts +10 -9
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/model-spot.ts +70 -4
- package/src/plugins/builtin/options/view.tsx +118 -35
- package/src/plugins/builtin/options-calculator/pane.tsx +7 -4
- package/src/plugins/builtin/options-positioning/headless.ts +11 -3
- package/src/plugins/builtin/options-positioning/model.ts +24 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
- package/src/plugins/builtin/options-scenario/client.ts +19 -1
- package/src/plugins/builtin/options-scenario/headless.ts +13 -6
- package/src/plugins/builtin/options-scenario/pane.tsx +54 -11
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/power/client.ts +2 -2
- package/src/plugins/builtin/power/pane.tsx +36 -31
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +4 -3
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/client.ts +2 -1
- package/src/plugins/builtin/sectors/client.ts +4 -3
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +64 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
- package/src/plugins/builtin/shared/volatility/options.ts +2 -1
- package/src/plugins/builtin/social-mentions/client.ts +2 -1
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/client.ts +23 -3
- package/src/plugins/builtin/vol-surface/headless.ts +33 -14
- package/src/plugins/builtin/vol-surface/model.ts +30 -10
- package/src/plugins/builtin/vol-surface/pane.tsx +35 -15
- package/src/plugins/builtin/vol-surface/stored.ts +34 -3
- package/src/plugins/builtin/vol-surface/tables.ts +14 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +40 -8
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/query-bar.tsx +2 -0
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +22 -5
- package/src/renderers/dom/host/top-surface.ts +127 -0
- package/src/renderers/dom/styles.css +51 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +21 -1
- package/src/renderers/electrobun/bun/desktop/backend.ts +10 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/renderers/electrobun/view/news-source.ts +4 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +8 -1
- package/src/renderers/opentui/start.tsx +7 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +57 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +18 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/telemetry/crash-reports.ts +4 -0
- package/src/telemetry/update-failures-node.ts +59 -0
- package/src/telemetry/update-failures.ts +307 -0
- package/src/tickers/company-name-listing.ts +9 -7
- package/src/tickers/company-name.ts +43 -0
- package/src/tickers/listing-fate.ts +57 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +11 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +19 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/issuer-listings.ts +28 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/updater/failure.ts +163 -0
- package/src/updater/index.ts +87 -23
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +28 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
- package/src/utils/sec.ts +68 -0
|
@@ -11,6 +11,7 @@ import { ChartToolbar, CHART_TOOLBAR_WIDTH } from "./toolbar";
|
|
|
11
11
|
import { AsciiText, Box, ChartSurface, Text, useUiCapabilities, type BoxRenderable } from "../../../ui";
|
|
12
12
|
import { useLiveStretch } from "../../../ui/live-stretch";
|
|
13
13
|
import { useOptionalPaneInstanceId } from "../../../state/app/context";
|
|
14
|
+
import { usePaneFocused } from "../../../state/app/activity";
|
|
14
15
|
import { useTabCursorLink } from "../../../state/app/tab-cursor";
|
|
15
16
|
import { useThemeColors, useThemeId } from "../../../theme/theme-context";
|
|
16
17
|
import { themedSeriesColor, themeSeriesColors } from "../../../theme/series-colors";
|
|
@@ -144,6 +145,7 @@ function ThemedCompositeChart({
|
|
|
144
145
|
onCursorDateChange,
|
|
145
146
|
onViewportChange,
|
|
146
147
|
onActivate,
|
|
148
|
+
onPress,
|
|
147
149
|
onToggleSeries,
|
|
148
150
|
isSeriesToggleable,
|
|
149
151
|
timePick,
|
|
@@ -155,6 +157,8 @@ function ThemedCompositeChart({
|
|
|
155
157
|
const showTextFallback = useShowChartTextFallback();
|
|
156
158
|
const [internalCursorDate, setInternalCursorDate] = useState<Date | null>(null);
|
|
157
159
|
const [toolSpan, setToolSpan] = useState<ChartToolSpan | null>(null);
|
|
160
|
+
const [pointerDragging, setPointerDragging] = useState(false);
|
|
161
|
+
const paneFocused = usePaneFocused();
|
|
158
162
|
const paneInstanceId = useOptionalPaneInstanceId();
|
|
159
163
|
const [drawings, setDrawings] = useState<readonly ChartDrawing[]>(NO_DRAWINGS);
|
|
160
164
|
const [selectedDrawingId, setSelectedDrawingId] = useState<string | null>(null);
|
|
@@ -333,6 +337,7 @@ function ThemedCompositeChart({
|
|
|
333
337
|
availableAxisWidth,
|
|
334
338
|
Math.max(
|
|
335
339
|
Math.min(MINIMUM_AXIS_LABEL_WIDTH, maximumAxisWidth),
|
|
340
|
+
...(levels?.items ?? []).map((level) => level.axisLabel?.length ?? 0),
|
|
336
341
|
...(layoutPanels ?? []).flatMap((panel) => (["left", "right"] as const).map((side) => {
|
|
337
342
|
const width = compositeAxisLabelWidth(panel.axes[side], formatAxisValue, includeCursorLabels);
|
|
338
343
|
if (!includeSeriesTags || !width) return includeCursorLabels ? width : Math.min(width, maximumAxisWidth);
|
|
@@ -343,7 +348,7 @@ function ThemedCompositeChart({
|
|
|
343
348
|
return (width + 1) * lanes;
|
|
344
349
|
})),
|
|
345
350
|
),
|
|
346
|
-
), [availableAxisWidth, formatAxisValue, includeCursorLabels, includeSeriesTags, layoutPanels, maximumAxisWidth]);
|
|
351
|
+
), [availableAxisWidth, formatAxisValue, includeCursorLabels, includeSeriesTags, layoutPanels, maximumAxisWidth, levels?.items]);
|
|
347
352
|
const leftAxisWidth = hasLeftAxis ? resolvedAxisWidth : 0;
|
|
348
353
|
const rightAxisWidth = hasRightAxis ? resolvedAxisWidth : 0;
|
|
349
354
|
const axisGap = resolvedAxisWidth > 0 ? 1 : 0;
|
|
@@ -523,6 +528,14 @@ function ThemedCompositeChart({
|
|
|
523
528
|
removeDrawing,
|
|
524
529
|
pickDrawColor,
|
|
525
530
|
});
|
|
531
|
+
const pressFromPointer = useCallback(() => {
|
|
532
|
+
onPress?.();
|
|
533
|
+
activateFromPointer();
|
|
534
|
+
}, [activateFromPointer, onPress]);
|
|
535
|
+
const pressPlot = useCallback(() => {
|
|
536
|
+
cancelKeyboardPlacement();
|
|
537
|
+
onPress?.();
|
|
538
|
+
}, [cancelKeyboardPlacement, onPress]);
|
|
526
539
|
const handleEmptyMouseScroll = useCallback((event: ChartMouseEvent) => {
|
|
527
540
|
const scroll = event.scroll;
|
|
528
541
|
if (!interactive || !navigationFrame || !scroll) return;
|
|
@@ -593,7 +606,7 @@ function ThemedCompositeChart({
|
|
|
593
606
|
accessoryWidth={legendAccessoryWidth}
|
|
594
607
|
formatValue={formatValue}
|
|
595
608
|
showLatestChangePercent={showLatestChangePercent}
|
|
596
|
-
onActivate={
|
|
609
|
+
onActivate={pressFromPointer}
|
|
597
610
|
onToggleSeries={onToggleSeries}
|
|
598
611
|
isSeriesToggleable={isSeriesToggleable}
|
|
599
612
|
keyboardIndex={legendKeyboardIndex}
|
|
@@ -650,7 +663,11 @@ function ThemedCompositeChart({
|
|
|
650
663
|
);
|
|
651
664
|
}
|
|
652
665
|
|
|
653
|
-
|
|
666
|
+
// The tools belong to the focused pane, and stay while one is in hand or a
|
|
667
|
+
// press drags the plot, so focus moving elsewhere never hides what is
|
|
668
|
+
// drawing. A chart outside a pane (a share page) keeps them.
|
|
669
|
+
const toolbarWanted = paneFocused !== false || armedTool !== null || pointerDragging || toolSpan !== null;
|
|
670
|
+
const showToolbar = interactive && navigable && toolbarWanted && plotWidth > CHART_TOOLBAR_WIDTH + 4;
|
|
654
671
|
// Bars shorter than a day carry a time of day the cursor reads out.
|
|
655
672
|
const intradayBars = scene.timeZone !== undefined || visibleSeries.some((entry) => (
|
|
656
673
|
!!entry.historyResolution && isIntradayResolution(entry.historyResolution)
|
|
@@ -708,7 +725,7 @@ function ThemedCompositeChart({
|
|
|
708
725
|
accessoryWidth={legendAccessoryWidth}
|
|
709
726
|
formatValue={formatValue}
|
|
710
727
|
showLatestChangePercent={showLatestChangePercent}
|
|
711
|
-
onActivate={
|
|
728
|
+
onActivate={pressFromPointer}
|
|
712
729
|
onToggleSeries={onToggleSeries}
|
|
713
730
|
isSeriesToggleable={isSeriesToggleable}
|
|
714
731
|
keyboardIndex={legendKeyboardIndex}
|
|
@@ -726,11 +743,11 @@ function ThemedCompositeChart({
|
|
|
726
743
|
showColors={isDrawingTool(armedTool) || !!selectedDrawingId}
|
|
727
744
|
levelTool={levelsEditable}
|
|
728
745
|
onArmTool={(tool) => {
|
|
729
|
-
|
|
746
|
+
pressFromPointer();
|
|
730
747
|
armTool(tool);
|
|
731
748
|
}}
|
|
732
749
|
onPickColor={(color) => {
|
|
733
|
-
|
|
750
|
+
pressFromPointer();
|
|
734
751
|
pickDrawColor(color);
|
|
735
752
|
}}
|
|
736
753
|
/>
|
|
@@ -772,8 +789,9 @@ function ThemedCompositeChart({
|
|
|
772
789
|
onZoomViewport={zoomViewport}
|
|
773
790
|
onSetViewport={setViewportRange}
|
|
774
791
|
onToolSpanChange={setToolSpan}
|
|
792
|
+
onDragActiveChange={setPointerDragging}
|
|
775
793
|
keyboardToolDrag={keyboardToolDrag?.panelId === panel.id ? keyboardToolDrag : null}
|
|
776
|
-
onPointerPress={
|
|
794
|
+
onPointerPress={pressPlot}
|
|
777
795
|
showTextFallback={showTextFallback}
|
|
778
796
|
onPickTime={timePick?.onPick}
|
|
779
797
|
levels={levelHost?.panel.id === panel.id ? levels ?? null : null}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
2
|
import { usePaneSettingValue } from "../../../state/app/context";
|
|
3
3
|
import { CHART_DRAWINGS_SETTING_KEY, parseChartDrawings, type ChartDrawing } from "./tools";
|
|
4
4
|
|
|
@@ -40,6 +40,12 @@ export function ChartDrawingStore({
|
|
|
40
40
|
if (restoredRef.current === null) {
|
|
41
41
|
restoredRef.current = parseChartDrawings(stored);
|
|
42
42
|
}
|
|
43
|
+
// What the settings hold, so removing the last drawing is told even when the
|
|
44
|
+
// pane opened with none: it is not "nothing to say" but "the stored one is gone".
|
|
45
|
+
const writtenRef = useRef<readonly ChartDrawing[]>(restoredRef.current);
|
|
46
|
+
const pendingRef = useRef<readonly ChartDrawing[] | null>(null);
|
|
47
|
+
const setStoredRef = useRef(setStored);
|
|
48
|
+
setStoredRef.current = setStored;
|
|
43
49
|
|
|
44
50
|
useEffect(() => {
|
|
45
51
|
const restored = restoredRef.current;
|
|
@@ -47,13 +53,27 @@ export function ChartDrawingStore({
|
|
|
47
53
|
// Restoring once on mount: later writes must not scroll back in time.
|
|
48
54
|
}, []);
|
|
49
55
|
|
|
56
|
+
const write = useCallback((next: readonly ChartDrawing[]) => {
|
|
57
|
+
pendingRef.current = null;
|
|
58
|
+
writtenRef.current = next;
|
|
59
|
+
setStoredRef.current(next);
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
50
62
|
useEffect(() => {
|
|
51
|
-
|
|
52
|
-
if (drawings ===
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
// Untouched, or still waiting for the restore above: nothing of the user's to write.
|
|
64
|
+
if (drawings === NO_DRAWINGS || drawings === writtenRef.current || (drawings.length === 0 && writtenRef.current.length === 0)) {
|
|
65
|
+
pendingRef.current = null;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
pendingRef.current = drawings;
|
|
69
|
+
const timer = setTimeout(() => write(drawings), DRAWING_PERSIST_DELAY_MS);
|
|
55
70
|
return () => clearTimeout(timer);
|
|
56
|
-
}, [drawings,
|
|
71
|
+
}, [drawings, write]);
|
|
72
|
+
|
|
73
|
+
// A change made just before the chart goes away (a tab switch) is still saved.
|
|
74
|
+
useEffect(() => () => {
|
|
75
|
+
if (pendingRef.current) write(pendingRef.current);
|
|
76
|
+
}, [write]);
|
|
57
77
|
|
|
58
78
|
return null;
|
|
59
79
|
}
|
|
@@ -298,6 +298,14 @@ export function formatChartLegendValue(
|
|
|
298
298
|
return trimmed && trimmed.length <= 6 ? `${compact}${trimmed.startsWith("/") ? "" : " "}${trimmed}` : compact;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
/** compactNumber's scale, with the decimals a tick gap needs at that scale. */
|
|
302
|
+
function preciseCompactNumber(value: number, gap: number): string {
|
|
303
|
+
const absolute = Math.abs(value);
|
|
304
|
+
const [divisor, suffix] = ([[1e12, "T"], [1e9, "B"], [1e6, "M"], [1e3, "K"]] as const).find(([scale]) => absolute >= scale) ?? [1, ""];
|
|
305
|
+
const digits = Math.min(12, Math.max(0, Math.ceil(-Math.log10(gap / divisor) - 1e-9)));
|
|
306
|
+
return `${(value / divisor).toFixed(digits)}${suffix}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
301
309
|
const COMPACT_LABEL = /^(-?\d+)(?:\.(\d+))?([KMBT]?)$/;
|
|
302
310
|
|
|
303
311
|
function trimmedDecimals(label: string): number {
|
|
@@ -332,13 +340,17 @@ export function formatCompositeAxisValue(value: number, domain: CompositeAxisDom
|
|
|
332
340
|
// Where that happens the axis spends the digits needed to tell them apart,
|
|
333
341
|
// shared across every tick so the column reads as one scale.
|
|
334
342
|
const gap = narrowestAxisTickGap(domain);
|
|
343
|
+
const prefix = currencyPrefix(domain.unit);
|
|
335
344
|
if (gap !== null && gap < compactResolution(value)) {
|
|
336
345
|
const resolved = formatAxisPriceValue(value, domain, {
|
|
337
346
|
fixedFractionDigits: Math.max(0, Math.ceil(-Math.log10(gap))),
|
|
338
347
|
});
|
|
339
348
|
if (resolved) return resolved;
|
|
349
|
+
// No currency to format with, such as a price whose quote has not
|
|
350
|
+
// arrived: the same digits still keep the ticks apart.
|
|
351
|
+
const precise = preciseCompactNumber(value, gap);
|
|
352
|
+
return prefix && precise.startsWith("-") ? `-${prefix}${precise.slice(1)}` : `${prefix}${precise}`;
|
|
340
353
|
}
|
|
341
|
-
const prefix = currencyPrefix(domain.unit);
|
|
342
354
|
return prefix && compact.startsWith("-") ? `-${prefix}${compact.slice(1)}` : `${prefix}${compact}`;
|
|
343
355
|
}
|
|
344
356
|
|
|
@@ -362,7 +362,8 @@ export function resolveCompositeChartInteraction(event: {
|
|
|
362
362
|
if (shifted && key === "d") return "arm-line";
|
|
363
363
|
if (shifted && key === "p") return "arm-pencil";
|
|
364
364
|
if (shifted && key === "h") return "arm-level";
|
|
365
|
-
|
|
365
|
+
// Delete is the same act on a keyboard that has the key, or on a Mac with fn.
|
|
366
|
+
if (key === "backspace" || key === "delete") return "delete-drawing";
|
|
366
367
|
if (key === "c" && !event.shift) return "cycle-colour";
|
|
367
368
|
if (event.shift) return null;
|
|
368
369
|
if (key === "a") return "pan-left";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import { blendForContrast, higherContrast } from "../../../theme/color-utils";
|
|
3
|
+
import { fitAxisLabels, type CompositeChartLevel, type ProjectedLevel } from "./levels";
|
|
4
|
+
|
|
5
|
+
/** Keep a precise price in the tag only when it has no readable axis marker. */
|
|
6
|
+
export function levelLabelText(level: CompositeChartLevel, axisVisible: boolean): string {
|
|
7
|
+
return `${level.label ?? ""}${!axisVisible && level.axisLabel ? ` ${level.axisLabel}` : ""}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Compact DOM tags straddle their line; crowded or clipped tags yield. */
|
|
11
|
+
export function layoutLevelLabels(levels: readonly ProjectedLevel[], plotHeight: number, textHeight: number) {
|
|
12
|
+
const candidates = levels.filter(({ level }) => level.label).map((entry) => ({
|
|
13
|
+
...entry, top: entry.yRatio * plotHeight - textHeight / 2,
|
|
14
|
+
})).filter(({ top }) => top >= 0 && top + textHeight <= plotHeight);
|
|
15
|
+
return fitAxisLabels(candidates, [], ({ top }) => top / textHeight);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function levelLabelStyle({ yRatio, level }: ProjectedLevel, background: string): CSSProperties {
|
|
19
|
+
return {
|
|
20
|
+
top: `${yRatio * 100}%`,
|
|
21
|
+
transform: "translateY(-50%)",
|
|
22
|
+
pointerEvents: "none",
|
|
23
|
+
paddingInline: 3,
|
|
24
|
+
height: "auto",
|
|
25
|
+
width: "max-content",
|
|
26
|
+
border: `1px solid ${level.color}`,
|
|
27
|
+
backgroundColor: background,
|
|
28
|
+
color: blendForContrast(level.color, background, higherContrast("#000000", "#ffffff", background), 4.5),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -14,6 +14,11 @@ export interface CompositeChartLevel {
|
|
|
14
14
|
editable: boolean;
|
|
15
15
|
/** Whether the chart's level action applies to it. */
|
|
16
16
|
actionable: boolean;
|
|
17
|
+
/** Optional read-only annotation, independent of drawing-tool selection. */
|
|
18
|
+
label?: string;
|
|
19
|
+
/** Exact instrument price, independent of the grid's compact tick formatter. */
|
|
20
|
+
axisLabel?: string;
|
|
21
|
+
dashed?: boolean;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export type CompositeLevelEdit =
|
|
@@ -111,13 +116,17 @@ export function fitAxisLabels<T>(
|
|
|
111
116
|
});
|
|
112
117
|
}
|
|
113
118
|
|
|
119
|
+
function levelSegments(dashed?: boolean): Array<[number, number]> {
|
|
120
|
+
return dashed ? Array.from({ length: 48 }, (_, i) => [i / 48, (i + 0.55) / 48]) : [[0, 1]];
|
|
121
|
+
}
|
|
122
|
+
|
|
114
123
|
export function levelVectors(levels: readonly ProjectedLevel[], selectedId: string | null): ChartVectorShape[] {
|
|
115
|
-
return levels.
|
|
116
|
-
id: `level:${level.id}`,
|
|
117
|
-
points: [{ x:
|
|
124
|
+
return levels.flatMap(({ level, yRatio }) => levelSegments(level.dashed).map(([start, end], index) => ({
|
|
125
|
+
id: `level:${level.id}:${index}`,
|
|
126
|
+
points: [{ x: start, y: yRatio }, { x: end, y: yRatio }],
|
|
118
127
|
color: level.color,
|
|
119
128
|
strokeWidth: level.id === selectedId ? 2.2 : 1.2,
|
|
120
|
-
}));
|
|
129
|
+
})));
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
/** Levels painted on a copy of the panel raster, the one layer the terminal uploads. */
|
|
@@ -131,7 +140,9 @@ export function paintLevels(
|
|
|
131
140
|
const maxY = Math.max(base.height - 1, 0);
|
|
132
141
|
for (const { level, yRatio } of levels) {
|
|
133
142
|
const y = Math.round(yRatio * maxY);
|
|
134
|
-
|
|
143
|
+
for (const [start, end] of levelSegments(level.dashed)) {
|
|
144
|
+
drawLine(data, base.width, base.height, start * maxX, y, end * maxX, y, parseHex(level.color), level.id === selectedId ? 2.2 : 1.2);
|
|
145
|
+
}
|
|
135
146
|
}
|
|
136
147
|
return { width: base.width, height: base.height, pixels: data };
|
|
137
148
|
}
|
|
@@ -145,7 +156,7 @@ export function writeLevelText(lines: readonly string[], levels: readonly Projec
|
|
|
145
156
|
const rows = lines.map((line) => Array.from(line));
|
|
146
157
|
for (const { level, yRatio } of levels) {
|
|
147
158
|
const row = rows[clamp(Math.round(yRatio * (rows.length - 1)), 0, rows.length - 1)]!;
|
|
148
|
-
const glyph = level.id === selectedId ? "━" : level.editable ? "
|
|
159
|
+
const glyph = level.id === selectedId ? "━" : level.dashed || !level.editable ? "┄" : "─";
|
|
149
160
|
for (let x = 0; x < row.length; x += 1) {
|
|
150
161
|
if (row[x] === " " || row[x] === "·" || row[x] === "╌") row[x] = glyph;
|
|
151
162
|
}
|
|
@@ -12,7 +12,8 @@ import { colors as themeColors } from "../../../theme/colors";
|
|
|
12
12
|
import { consumeChartMouseEvent, getGlobalMouseX, getLocalPlotPointer, type ChartMouseEvent } from "../core/pointer";
|
|
13
13
|
import { useStaticChartBitmapSize } from "./bitmap";
|
|
14
14
|
import { useMeasuredCells } from "../../../ui/measured-cells";
|
|
15
|
-
import { PriceAxisLabels } from "./price-axis-labels";
|
|
15
|
+
import { PriceAxisLabels, placeAxisTicks } from "./price-axis-labels";
|
|
16
|
+
import { layoutLevelLabels, levelLabelStyle, levelLabelText } from "./level-labels";
|
|
16
17
|
import { formatCompositeCursorValue, type CompositeAxisValueFormatter } from "./format";
|
|
17
18
|
import {
|
|
18
19
|
compositeViewportPositions,
|
|
@@ -111,6 +112,8 @@ interface CompositePanelSurfaceProps {
|
|
|
111
112
|
onZoomViewport: (zoomFactor: number, anchorRatio: number) => void;
|
|
112
113
|
onSetViewport: (range: CompositeViewportRange) => void;
|
|
113
114
|
onToolSpanChange: (span: ChartToolSpan | null) => void;
|
|
115
|
+
/** A press on the plot started (true) or stopped (false) dragging: a pan, a tool, a drawing or a level. */
|
|
116
|
+
onDragActiveChange?: (dragging: boolean) => void;
|
|
114
117
|
/** The tool being placed from the keyboard, when it lives in this panel. */
|
|
115
118
|
keyboardToolDrag: KeyboardToolDrag | null;
|
|
116
119
|
/** A press on the plot: the pointer takes over from a keyboard placement. */
|
|
@@ -154,6 +157,7 @@ export function CompositePanelSurface({
|
|
|
154
157
|
onZoomViewport,
|
|
155
158
|
onSetViewport,
|
|
156
159
|
onToolSpanChange,
|
|
160
|
+
onDragActiveChange,
|
|
157
161
|
keyboardToolDrag,
|
|
158
162
|
onPointerPress,
|
|
159
163
|
showTextFallback,
|
|
@@ -477,14 +481,16 @@ export function CompositePanelSurface({
|
|
|
477
481
|
.sort((left, right) => Number(right.level.id === selectedLevelId) - Number(left.level.id === selectedLevelId)
|
|
478
482
|
|| Number(left.level.editable) - Number(right.level.editable))
|
|
479
483
|
.map(({ level, yRatio }) => ({
|
|
484
|
+
id: level.id,
|
|
480
485
|
side: levelDomain.side,
|
|
481
486
|
yRatio,
|
|
482
|
-
label: formatAxisValue ? formatAxisValue(level.value, levelDomain) : formatCompositeCursorValue(level.value, levelDomain),
|
|
487
|
+
label: level.axisLabel ?? (formatAxisValue ? formatAxisValue(level.value, levelDomain) : formatCompositeCursorValue(level.value, levelDomain)),
|
|
483
488
|
color: level.color,
|
|
484
489
|
})) : [], [formatAxisValue, levelDomain, projectedLevels, selectedLevelId]);
|
|
485
490
|
const buildAxisMarkers = (side: "left" | "right") => {
|
|
486
491
|
if (!panel.axes[side]) return undefined;
|
|
487
|
-
const toAxisMarker = (marker: { yRatio: number; label: string; color: string }) => ({
|
|
492
|
+
const toAxisMarker = (marker: { id?: string; yRatio: number; label: string; color: string }) => ({
|
|
493
|
+
id: marker.id,
|
|
488
494
|
row: Math.round(marker.yRatio * Math.max(panel.height - 1, 0)),
|
|
489
495
|
pixelY: marker.yRatio * Math.max(panel.height * cellHeightPx - 1, 0),
|
|
490
496
|
label: marker.label,
|
|
@@ -499,8 +505,14 @@ export function CompositePanelSurface({
|
|
|
499
505
|
// A label is a row tall, so close levels would stack into an unreadable
|
|
500
506
|
// pile and hide the last price: each keeps its line, and only the labels
|
|
501
507
|
// with a row to themselves show. Desktop badges sit between rows.
|
|
508
|
+
const grid = fractionalViewport
|
|
509
|
+
? placeAxisTicks({ ticks: side === "left" ? leftAxisTicks : rightAxisTicks, height: panel.height, cellHeightPx }).map((tick) => ({
|
|
510
|
+
...toAxisMarker({ yRatio: tick.ratio, label: tick.label, color: colors.textDim }),
|
|
511
|
+
pixelY: tick.topPercent / 100 * Math.max(panel.height * cellHeightPx - 1, 1),
|
|
512
|
+
}))
|
|
513
|
+
: [...(side === "left" ? leftAxisLabels : rightAxisLabels)].map(([row, label]) => ({ id: undefined, row, pixelY: row * cellHeightPx, label, color: colors.textDim }));
|
|
502
514
|
const markers = [
|
|
503
|
-
...fitAxisLabels(levelLabels, fixed, (marker) => fractionalViewport ? marker.pixelY / cellHeightPx : marker.row),
|
|
515
|
+
...fitAxisLabels(levelLabels, [...fixed, ...grid], (marker) => fractionalViewport ? marker.pixelY / cellHeightPx : marker.row),
|
|
504
516
|
...fixed,
|
|
505
517
|
];
|
|
506
518
|
return markers.length > 0 ? markers : undefined;
|
|
@@ -611,6 +623,17 @@ export function CompositePanelSurface({
|
|
|
611
623
|
updateCursor(event);
|
|
612
624
|
return;
|
|
613
625
|
}
|
|
626
|
+
// With no tool in hand, a press on a drawing or a level picks it so Backspace
|
|
627
|
+
// or Delete can remove it; the press still pans, and one on bare plot lets go.
|
|
628
|
+
const ratios = pointerRatios(event);
|
|
629
|
+
if (ratios) {
|
|
630
|
+
const drawingHit = drawings.length > 0 ? hitTestDrawings(drawings, scene, panel, ratios, plotAspect) : null;
|
|
631
|
+
const levelHit = !drawingHit && levels?.onEdit && levelDomain
|
|
632
|
+
? hitTestLevel(projectedLevels, ratios.yRatio, levelGrabRatio(panel.height))
|
|
633
|
+
: null;
|
|
634
|
+
onSelectDrawing(drawingHit ? drawingHit.drawing.id : null);
|
|
635
|
+
onSelectLevel(levelHit?.editable ? levelHit.id : null);
|
|
636
|
+
}
|
|
614
637
|
if (!updateCursor(event)) return;
|
|
615
638
|
dragRef.current = startPanGesture(
|
|
616
639
|
frame,
|
|
@@ -736,12 +759,29 @@ export function CompositePanelSurface({
|
|
|
736
759
|
}
|
|
737
760
|
finishToolDrag();
|
|
738
761
|
}, [finishToolDrag]);
|
|
762
|
+
// Said once per change, so a pointer move costs the chart nothing.
|
|
763
|
+
const reportedDragRef = useRef(false);
|
|
764
|
+
const reportDrag = useCallback((dragging: boolean) => {
|
|
765
|
+
if (reportedDragRef.current === dragging) return;
|
|
766
|
+
reportedDragRef.current = dragging;
|
|
767
|
+
onDragActiveChange?.(dragging);
|
|
768
|
+
}, [onDragActiveChange]);
|
|
769
|
+
const pressPlot = useCallback((event: ChartMouseEvent) => {
|
|
770
|
+
const before = dragRef.current;
|
|
771
|
+
startDrag(event);
|
|
772
|
+
if (dragRef.current !== null && dragRef.current !== before) reportDrag(true);
|
|
773
|
+
}, [reportDrag, startDrag]);
|
|
774
|
+
const releasePlot = useCallback(() => {
|
|
775
|
+
resetDrag();
|
|
776
|
+
reportDrag(false);
|
|
777
|
+
}, [reportDrag, resetDrag]);
|
|
739
778
|
const handleMouseMove = useCallback((event: ChartMouseEvent) => {
|
|
740
779
|
// A release over a neighbouring pane never reaches this surface, so treat a
|
|
741
780
|
// plain move, which only fires with no button held, as the missing release.
|
|
742
781
|
finishToolDrag();
|
|
782
|
+
reportDrag(false);
|
|
743
783
|
updateCursor(event);
|
|
744
|
-
}, [finishToolDrag, updateCursor]);
|
|
784
|
+
}, [finishToolDrag, reportDrag, updateCursor]);
|
|
745
785
|
// Trackpads fire wheel events faster than the plot can paint, so one frame
|
|
746
786
|
// absorbs every event that arrived since the last one.
|
|
747
787
|
const pendingWheelRef = useRef<PendingWheel | null>(null);
|
|
@@ -876,10 +916,10 @@ export function CompositePanelSurface({
|
|
|
876
916
|
crosshair={crosshair}
|
|
877
917
|
vectors={vectors}
|
|
878
918
|
onMouseMove={interactive ? handleMouseMove : undefined}
|
|
879
|
-
onMouseDown={interactive ? navigable ?
|
|
919
|
+
onMouseDown={interactive ? navigable ? pressPlot : pressCursor : undefined}
|
|
880
920
|
onMouseDrag={interactive && navigable ? dragViewport : undefined}
|
|
881
|
-
onMouseUp={interactive && navigable ?
|
|
882
|
-
onMouseDragEnd={interactive && navigable ?
|
|
921
|
+
onMouseUp={interactive && navigable ? releasePlot : undefined}
|
|
922
|
+
onMouseDragEnd={interactive && navigable ? releasePlot : undefined}
|
|
883
923
|
onMouseScroll={interactive && navigable ? panFromWheel : undefined}
|
|
884
924
|
onMouseOut={interactive ? clearCursor : undefined}
|
|
885
925
|
cursor={interactive ? toolDrag || !navigable || onPickTime ? "crosshair" : "grab" : undefined}
|
|
@@ -890,6 +930,27 @@ export function CompositePanelSurface({
|
|
|
890
930
|
>
|
|
891
931
|
{textLines.map((line, index) => <Text key={index} fg={colors.text}>{line}</Text>)}
|
|
892
932
|
</ChartSurface>
|
|
933
|
+
{(isDesktopWeb
|
|
934
|
+
? layoutLevelLabels(projectedLevels, plotSize.height * cellHeightPx, cellHeightPx)
|
|
935
|
+
: fitAxisLabels(projectedLevels.filter((entry) => entry.level.label), [], (entry) => entry.yRatio * Math.max(1, panel.height - 1))
|
|
936
|
+
).map((entry) => {
|
|
937
|
+
const { level, yRatio } = entry;
|
|
938
|
+
const axisWidth = levelDomain?.side === "left" ? leftAxisWidth : rightAxisWidth;
|
|
939
|
+
const axisMarkers = levelDomain?.side === "left" ? leftAxisMarkers : rightAxisMarkers;
|
|
940
|
+
const cursorCovers = activeCursorYRatio !== null && Math.abs(activeCursorYRatio - yRatio) * panel.height < 1;
|
|
941
|
+
const axisVisible = !cursorCovers && (level.axisLabel?.length ?? 0) <= axisWidth && !!axisMarkers?.some((marker) => marker.id === level.id);
|
|
942
|
+
const label = levelLabelText(level, axisVisible);
|
|
943
|
+
const labelStyle = isDesktopWeb ? {
|
|
944
|
+
...levelLabelStyle(entry, colors.background), left: "auto", right: (rightAxisWidth + (rightAxisWidth ? axisGap : 0) + 1) * cellWidthPx,
|
|
945
|
+
maxWidth: Math.max(1, plotWidth - 2) * cellWidthPx,
|
|
946
|
+
} : undefined;
|
|
947
|
+
const labelWidth = Math.min(plotWidth - 2, label.length + 2);
|
|
948
|
+
return <Box key={`annotation:${level.id}`} position="absolute" left={leftAxisWidth + (leftAxisWidth ? axisGap : 0) + plotWidth - labelWidth - 1} top={yRatio * Math.max(0, panel.height - 1)} height={1}
|
|
949
|
+
width={labelWidth} flexDirection="row" justifyContent="flex-end" backgroundColor={colors.background} zIndex={20}
|
|
950
|
+
style={labelStyle} data-gloom-role="chart-level-label">
|
|
951
|
+
<Text fg={labelStyle?.color ?? level.color} wrapMode="none" style={isDesktopWeb ? { lineHeight: 1 } : undefined}>{label}</Text>
|
|
952
|
+
</Box>;
|
|
953
|
+
})}
|
|
893
954
|
{rightAxisWidth > 0 ? (
|
|
894
955
|
<>
|
|
895
956
|
<Box width={axisGap} />
|
|
@@ -315,6 +315,12 @@ export interface CompositeChartProps {
|
|
|
315
315
|
interaction: "pan" | "reset" | "zoom",
|
|
316
316
|
) => void;
|
|
317
317
|
onActivate?: () => void;
|
|
318
|
+
/**
|
|
319
|
+
* A press on the chart itself (plot, legend or tool bar), not a wheel turn.
|
|
320
|
+
* The chart consumes the press, so an owner that shares the pane with a
|
|
321
|
+
* field that takes typed keys hears it here to hand the keyboard back.
|
|
322
|
+
*/
|
|
323
|
+
onPress?: () => void;
|
|
318
324
|
onToggleSeries?: (seriesId: string) => void;
|
|
319
325
|
isSeriesToggleable?: (series: ResolvedSeries) => boolean;
|
|
320
326
|
/**
|
|
@@ -424,7 +424,7 @@ export function useCompositeChartKeyboard({
|
|
|
424
424
|
stepLevel(event.name === "[" ? -1 : 1);
|
|
425
425
|
return;
|
|
426
426
|
}
|
|
427
|
-
if (toolsActive && selectedLevel?.editable && levelsEditable && isPlainKey(event, "backspace")) {
|
|
427
|
+
if (toolsActive && selectedLevel?.editable && levelsEditable && isPlainKey(event, "backspace", "delete")) {
|
|
428
428
|
consume();
|
|
429
429
|
removeSelectedLevel();
|
|
430
430
|
return;
|
|
@@ -3,11 +3,26 @@ import type { TickerRecord } from "../../../../types/ticker";
|
|
|
3
3
|
import { isManualPortfolio } from "../../../../portfolio/mutations";
|
|
4
4
|
import type { Command } from "../../commands/registry";
|
|
5
5
|
import type { ResultItem } from "../../list/model";
|
|
6
|
-
import { canRestartToApply } from "../../../../updater";
|
|
6
|
+
import { canRestartToApply, type UpdateProgress } from "../../../../updater";
|
|
7
7
|
import { NIGHTLY_CHANNEL_SUMMARY, resolveUpdateChannel } from "../../../../updater/channel";
|
|
8
|
+
import { updateFailureReason } from "../../../../updater/failure";
|
|
9
|
+
import { t, tf } from "../../../../i18n";
|
|
8
10
|
import { isPrereleaseVersion } from "../../../../utils/semver";
|
|
9
11
|
import { VERSION } from "../../../../version";
|
|
10
12
|
|
|
13
|
+
/** "Update failed: download interrupted", as the header says it. */
|
|
14
|
+
function failedUpdateLabel(progress: UpdateProgress): string {
|
|
15
|
+
const reason = progress.failure ? t(updateFailureReason(progress.failure)) : (progress.error ?? "").replace(/\s+/g, " ").trim();
|
|
16
|
+
return tf("Update failed: {error}", { error: reason });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The whole error under the failed update's row, unless the label already says all of it. */
|
|
20
|
+
function failedUpdateLines(progress: UpdateProgress): ResultItem["lines"] {
|
|
21
|
+
const error = (progress.error ?? "").replace(/\s+/g, " ").trim();
|
|
22
|
+
if (!progress.failure || !error) return undefined;
|
|
23
|
+
return [{ segments: [{ text: error, emphasis: "muted" }] }];
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
interface RootCommandItemBuilderOptions {
|
|
12
27
|
activeCollectionId: string | null;
|
|
13
28
|
activeTickerData: TickerRecord | null | undefined;
|
|
@@ -88,9 +103,12 @@ export function createRootCommandItemBuilder({
|
|
|
88
103
|
return activeTickerSymbol ? `Remove ${activeTickerSymbol} from Portfolio` : command.label;
|
|
89
104
|
case "set-portfolio-position":
|
|
90
105
|
return activeTickerSymbol ? `Set Position for ${activeTickerSymbol}` : command.label;
|
|
91
|
-
// The row only shows its label, so a downloaded update is announced there
|
|
106
|
+
// The row only shows its label, so a downloaded update is announced there,
|
|
107
|
+
// and a failed one with its reason; Enter restarts into the one and retries the other.
|
|
92
108
|
case "check-for-updates":
|
|
93
|
-
|
|
109
|
+
if (state.updateProgress?.phase === "ready") return "Update ready, restart to apply";
|
|
110
|
+
if (state.updateProgress?.phase === "error") return failedUpdateLabel(state.updateProgress);
|
|
111
|
+
return command.label;
|
|
94
112
|
default:
|
|
95
113
|
return command.label;
|
|
96
114
|
}
|
|
@@ -140,7 +158,7 @@ export function createRootCommandItemBuilder({
|
|
|
140
158
|
return state.updateProgress.message ?? "Update ready, restart to apply";
|
|
141
159
|
}
|
|
142
160
|
if (state.updateProgress?.phase === "error") {
|
|
143
|
-
return
|
|
161
|
+
return `${failedUpdateLabel(state.updateProgress)}. ${state.updateProgress.error ?? ""}`.trim();
|
|
144
162
|
}
|
|
145
163
|
if (state.updateCheckInProgress) return "Checking releases now";
|
|
146
164
|
if (state.updateAvailable) {
|
|
@@ -198,8 +216,11 @@ export function createRootCommandItemBuilder({
|
|
|
198
216
|
// "update" is a whole word here so it lands on this command before Set
|
|
199
217
|
// Portfolio Position (whose keywords say "update position") and Release
|
|
200
218
|
// Channel, which keeps the word further back.
|
|
219
|
+
// Ready and failed rename the row; its own name still finds it.
|
|
201
220
|
case "check-for-updates":
|
|
202
|
-
|
|
221
|
+
if (state.updateProgress?.phase === "ready") return "update check for updates restart relaunch";
|
|
222
|
+
if (state.updateProgress?.phase === "error") return "update check for updates retry";
|
|
223
|
+
return "update";
|
|
203
224
|
case "release-channel":
|
|
204
225
|
return "nightly stable beta prerelease update updates channel";
|
|
205
226
|
default:
|
|
@@ -218,9 +239,14 @@ export function createRootCommandItemBuilder({
|
|
|
218
239
|
right: smartRight(command, arg),
|
|
219
240
|
shortcutQuery: command.prefix || undefined,
|
|
220
241
|
searchText: smartSearchText(command),
|
|
242
|
+
...(command.id === "check-for-updates" && state.updateProgress?.phase === "error"
|
|
243
|
+
? { lines: failedUpdateLines(state.updateProgress) }
|
|
244
|
+
: {}),
|
|
245
|
+
// Busy while it checks, downloads or installs; a failed update is retried from here.
|
|
221
246
|
disabled:
|
|
222
247
|
command.id === "check-for-updates" &&
|
|
223
|
-
(state.updateCheckInProgress
|
|
248
|
+
(state.updateCheckInProgress
|
|
249
|
+
|| (!!state.updateProgress && !canRestartToApply(state.updateProgress) && state.updateProgress.phase !== "error")),
|
|
224
250
|
action: () => runDirectCommand(command, arg),
|
|
225
251
|
};
|
|
226
252
|
};
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type TickerSearchInstrumentClass,
|
|
18
18
|
} from "../../../../tickers/search";
|
|
19
19
|
import type { ResultItem } from "../../list/model";
|
|
20
|
+
import { delistingLabel, parseListingDelisting } from "../../../../market-data/quotes/delisting";
|
|
20
21
|
import {
|
|
21
22
|
buildTickerSearchCacheKey,
|
|
22
23
|
createQuickLookTickerCandidates,
|
|
@@ -74,6 +75,9 @@ export function useCommandBarTickerSearchActions({
|
|
|
74
75
|
// The candidate's detail leads with the name; the rest repeats the badge or venue.
|
|
75
76
|
const leadingName = candidate.detail.split(" | ")[0]?.trim();
|
|
76
77
|
const name = leadingName && leadingName.toUpperCase() !== candidate.label.toUpperCase() ? leadingName : undefined;
|
|
78
|
+
// A listing that no longer trades says so under its row, with its last session.
|
|
79
|
+
const delisting = parseListingDelisting(candidate.result?.delisting);
|
|
80
|
+
const lines = delisting ? [{ segments: [{ text: delistingLabel(delisting), emphasis: "muted" as const }] }] : undefined;
|
|
77
81
|
|
|
78
82
|
if (candidate.kind === "ticker" && candidate.ticker) {
|
|
79
83
|
return {
|
|
@@ -86,6 +90,7 @@ export function useCommandBarTickerSearchActions({
|
|
|
86
90
|
badge,
|
|
87
91
|
name,
|
|
88
92
|
right,
|
|
93
|
+
...(lines ? { lines } : {}),
|
|
89
94
|
category: candidate.category,
|
|
90
95
|
kind: "ticker",
|
|
91
96
|
resolveTicker: async () => candidate.ticker!,
|
|
@@ -112,6 +117,7 @@ export function useCommandBarTickerSearchActions({
|
|
|
112
117
|
badge,
|
|
113
118
|
name,
|
|
114
119
|
right,
|
|
120
|
+
...(lines ? { lines } : {}),
|
|
115
121
|
category: candidate.category,
|
|
116
122
|
kind: "search",
|
|
117
123
|
resolveTicker: () => resolveSearchTicker(candidate.result!, publicTickerKey(
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import type { ScrollBoxRenderable } from "../../ui";
|
|
12
12
|
import { useBeforeShortcut, useShortcut } from "../../react/input";
|
|
13
13
|
import { isPlainKey, isPlainKeyboardEvent } from "../../utils/keyboard";
|
|
14
|
-
import { DataTable, type DataTableColumn, type DataTableProps } from "../ui";
|
|
14
|
+
import { DataTable, PaneStatusBody, type DataTableColumn, type DataTableProps, type PaneStatusBodyProps } from "../ui";
|
|
15
15
|
import { useDataTableSortMenu } from "./sort-menu";
|
|
16
16
|
import {
|
|
17
17
|
TableViewFrame,
|
|
@@ -89,6 +89,19 @@ export type DataTableSelection<T> =
|
|
|
89
89
|
) => void;
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* What the table's body shows instead of rows while it has none to show: its
|
|
94
|
+
* first page loading after a tab or filter change, a failed load, a state
|
|
95
|
+
* with nothing to list. The same states `PaneStatusBody` draws, in the
|
|
96
|
+
* table's place under `rootBefore`, so the query bar and figures above stay
|
|
97
|
+
* on screen (and a search field keeps its focus) while the body waits.
|
|
98
|
+
*/
|
|
99
|
+
export type DataTableBodyStatus = Omit<PaneStatusBodyProps, "children" | "width" | "height" | "align">;
|
|
100
|
+
|
|
101
|
+
function bodyStatusShown(status: DataTableBodyStatus | undefined): status is DataTableBodyStatus {
|
|
102
|
+
return !!status && (!!status.error || status.loading === true || status.empty === true);
|
|
103
|
+
}
|
|
104
|
+
|
|
92
105
|
interface SelectionCommitTarget {
|
|
93
106
|
index: number;
|
|
94
107
|
id?: string;
|
|
@@ -124,6 +137,12 @@ export interface DataTableViewProps<
|
|
|
124
137
|
isNavigable?: (item: T, index: number) => boolean;
|
|
125
138
|
rootBefore?: ReactNode;
|
|
126
139
|
rootAfter?: ReactNode;
|
|
140
|
+
/**
|
|
141
|
+
* Replaces the table (header and rows) with a loading, error or empty
|
|
142
|
+
* state while `rootBefore` stays. Precedence is the caller's, as with
|
|
143
|
+
* `PaneStatusBody`: pass `loading` only while there is nothing to show.
|
|
144
|
+
*/
|
|
145
|
+
status?: DataTableBodyStatus;
|
|
127
146
|
rootWidth?: number;
|
|
128
147
|
rootHeight?: number;
|
|
129
148
|
rootBackgroundColor?: string;
|
|
@@ -165,6 +184,7 @@ export function DataTableView<
|
|
|
165
184
|
isNavigable,
|
|
166
185
|
rootBefore,
|
|
167
186
|
rootAfter,
|
|
187
|
+
status,
|
|
168
188
|
rootWidth,
|
|
169
189
|
rootHeight,
|
|
170
190
|
rootBackgroundColor,
|
|
@@ -728,7 +748,7 @@ export function DataTableView<
|
|
|
728
748
|
before={rootBefore}
|
|
729
749
|
after={rootAfter}
|
|
730
750
|
>
|
|
731
|
-
<DataTable<T, C>
|
|
751
|
+
{bodyStatusShown(status) ? <PaneStatusBody {...status} /> : <DataTable<T, C>
|
|
732
752
|
{...tableProps}
|
|
733
753
|
headerScrollRef={effectiveHeaderScrollRef}
|
|
734
754
|
scrollRef={effectiveScrollRef}
|
|
@@ -741,7 +761,7 @@ export function DataTableView<
|
|
|
741
761
|
onActivate={handleTableActivate}
|
|
742
762
|
onRowMouseDown={handleRowMouseDown}
|
|
743
763
|
onRowContextMenu={handleRowContextMenu}
|
|
744
|
-
/>
|
|
764
|
+
/>}
|
|
745
765
|
</TableViewFrame>
|
|
746
766
|
);
|
|
747
767
|
}
|