gloomberb 0.18.0 → 0.18.2
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/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 +6 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/types.ts +14 -0
- 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/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/fred-series.ts +15 -5
- package/src/cli/commands/market.ts +290 -159
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/overview.ts +31 -6
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +105 -52
- 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/helpers.ts +36 -3
- package/src/cli/index.ts +4 -1
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +37 -2
- package/src/cli/pane-functions/catalog.ts +3 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +9 -4
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +31 -7
- package/src/cli/pane-functions/headless.ts +6 -5
- package/src/cli/pane-functions/index.ts +3 -2
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +2 -1
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +51 -16
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +22 -7
- package/src/components/chart/composite/format.ts +13 -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 +64 -16
- package/src/components/chart/composite/price-axis-labels.tsx +2 -1
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/command-bar/workflow/collection-actions.ts +4 -0
- package/src/components/index.ts +2 -0
- 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/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/onboarding/wizard-positions.ts +8 -0
- 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/status.tsx +17 -3
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- 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 +57 -22
- package/src/i18n/es.ts +9 -3
- package/src/i18n/ja.ts +9 -3
- package/src/i18n/ko.ts +9 -3
- package/src/i18n/zh-cn.ts +10 -4
- package/src/i18n/zh-tw.ts +10 -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/cli.ts +26 -18
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/analytics/index.tsx +1 -1
- package/src/plugins/builtin/analytics/risk-client.ts +44 -11
- package/src/plugins/builtin/analytics/risk-fx.ts +61 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +10 -1
- package/src/plugins/builtin/analytics/risk-model.ts +79 -16
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/analytics/risk-test-data.ts +3 -0
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- 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 +182 -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 +505 -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 +573 -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 +3 -2
- package/src/plugins/builtin/buildout/pane/status.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 +31 -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/cloud/team/pane-sections.tsx +10 -3
- package/src/plugins/builtin/cloud/team/pane.tsx +30 -9
- 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 +36 -5
- 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 +26 -12
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- 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/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/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/custom-view/loader.ts +3 -2
- package/src/plugins/builtin/custom-view/pane.tsx +5 -4
- package/src/plugins/builtin/custom-view/view-spec.ts +1 -0
- 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/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 +38 -2
- package/src/plugins/builtin/econ/fred-catalog.ts +6 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +12 -1
- 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 +20 -5
- package/src/plugins/builtin/equity-screener/model.ts +40 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
- 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/executives/data.ts +24 -8
- package/src/plugins/builtin/executives/index.tsx +2 -18
- package/src/plugins/builtin/executives/pane.tsx +34 -10
- 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 +37 -16
- 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/insider/base-values.ts +72 -0
- package/src/plugins/builtin/insider/headless.ts +29 -4
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- 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 +2 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -3
- package/src/plugins/builtin/market-movers/session-body.tsx +27 -11
- package/src/plugins/builtin/market-movers/session.ts +25 -10
- 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 +33 -13
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/letters/client.ts +1 -0
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +6 -3
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +2 -1
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -1
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/feed-xml.ts +6 -1
- package/src/plugins/builtin/news/wire/rss/managed-feed.ts +1 -0
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +33 -8
- package/src/plugins/builtin/notes/export.ts +49 -3
- package/src/plugins/builtin/notes/files.ts +14 -3
- package/src/plugins/builtin/notes/index.tsx +3 -3
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +45 -10
- package/src/plugins/builtin/notes/store.ts +13 -3
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/view.tsx +77 -22
- package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
- package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -7
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +166 -15
- package/src/plugins/builtin/portfolio-list/cli/render.ts +69 -15
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +2 -2
- package/src/plugins/builtin/portfolio-list/headless.ts +3 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +44 -17
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -1
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +42 -7
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- 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/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/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/broker-reconnect.ts +105 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- 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-feature-wall.tsx +9 -4
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +62 -0
- 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/thirteenf/api.ts +4 -1
- package/src/plugins/builtin/thirteenf/data.ts +5 -1
- package/src/plugins/builtin/thirteenf/headless.ts +3 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +8 -3
- 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/pane.tsx +2 -2
- 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 +61 -0
- package/src/portfolio/mutations.ts +16 -2
- package/src/portfolio/position-metrics.ts +45 -4
- package/src/portfolio/position-quantity.ts +43 -0
- 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 +39 -7
- 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/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +6 -0
- package/src/renderers/dom/styles.css +12 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
- 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/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +27 -2
- package/src/sources/gloomberb-cloud/index.ts +50 -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 +2 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +63 -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/team/collections.ts +2 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/metadata.ts +10 -0
- 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 +5 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +12 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/ticker.ts +9 -0
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +2 -1
- package/src/utils/currency-units.ts +41 -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
|
@@ -22,6 +22,12 @@ export interface ListViewItem {
|
|
|
22
22
|
disabled?: boolean;
|
|
23
23
|
/** Tints the label, e.g. a status in its tone; the words carry the meaning. */
|
|
24
24
|
color?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Artwork before the label on the desktop and the web, such as a broker's
|
|
27
|
+
* logo (`LogoMark`). It is decoration: the terminal does not draw it, and
|
|
28
|
+
* automation and screen readers read only `label`.
|
|
29
|
+
*/
|
|
30
|
+
leading?: ReactNode;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export interface ListRowState {
|
|
@@ -39,6 +45,12 @@ export interface ListViewProps {
|
|
|
39
45
|
renderRow?: (item: ListViewItem, state: ListRowState, index: number) => ReactNode;
|
|
40
46
|
getRowBackgroundColor?: (item: ListViewItem, state: ListRowState, index: number) => string | undefined;
|
|
41
47
|
showSelectedDescription?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Desktop and web: every row sets its `description` on a second line under
|
|
50
|
+
* its label, and is two rows tall unless `rowHeight` says otherwise. The
|
|
51
|
+
* terminal keeps one line per row, with `detail` at its end.
|
|
52
|
+
*/
|
|
53
|
+
rowDescriptions?: boolean;
|
|
42
54
|
emptyMessage?: string;
|
|
43
55
|
bgColor?: string;
|
|
44
56
|
selectedBgColor?: string;
|
|
@@ -173,6 +185,7 @@ export function ListView({
|
|
|
173
185
|
renderRow,
|
|
174
186
|
getRowBackgroundColor,
|
|
175
187
|
showSelectedDescription = false,
|
|
188
|
+
rowDescriptions,
|
|
176
189
|
emptyMessage = "Nothing to show.",
|
|
177
190
|
bgColor,
|
|
178
191
|
selectedBgColor,
|
|
@@ -243,6 +256,7 @@ export function ListView({
|
|
|
243
256
|
renderRow={renderRow}
|
|
244
257
|
getRowBackgroundColor={getRowBackgroundColor}
|
|
245
258
|
showSelectedDescription={showSelectedDescription}
|
|
259
|
+
rowDescriptions={rowDescriptions}
|
|
246
260
|
emptyMessage={emptyMessage}
|
|
247
261
|
bgColor={bgColor}
|
|
248
262
|
selectedBgColor={selectedBgColor}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import { Box, ImageSurface, Text, TextAttributes, useUiCapabilities } from "../../ui";
|
|
3
|
+
import { useThemeColors } from "../../theme/theme-context";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `row`: beside a label in one row (a table cell's `leading`, a list row).
|
|
7
|
+
* `lead`: the art of a two-line list row; it fills the square `ListRowLines`
|
|
8
|
+
* sizes to the two lines beside it.
|
|
9
|
+
*/
|
|
10
|
+
type LogoMarkSize = "row" | "lead";
|
|
11
|
+
|
|
12
|
+
const TILE: Record<LogoMarkSize, { side: string; radius: string; type: string }> = {
|
|
13
|
+
// A pixel above and below in its row, as a chip.
|
|
14
|
+
row: { side: "calc(var(--cell-h) - 2px)", radius: "var(--gloom-radius-sm)", type: "var(--gloom-type-caption)" },
|
|
15
|
+
lead: { side: "100%", radius: "var(--gloom-radius-md)", type: "var(--gloom-type-ui)" },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Sized by its capital and baseline, so flex centring puts the letter in the
|
|
19
|
+
// middle of the tile whatever the font's ascent and descent (as chrome text).
|
|
20
|
+
const LETTER_STYLE: Record<LogoMarkSize, CSSProperties> = {
|
|
21
|
+
row: { fontSize: TILE.row.type, lineHeight: 1, textBox: "trim-both cap alphabetic" } as CSSProperties,
|
|
22
|
+
lead: { fontSize: TILE.lead.type, lineHeight: 1, textBox: "trim-both cap alphabetic" } as CSSProperties,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export interface LogoMarkProps {
|
|
26
|
+
/** The logo, an image URL; without one, or when it fails to load, the tile shows `name`'s initial. */
|
|
27
|
+
src?: string | null;
|
|
28
|
+
/** What the logo stands for, such as a broker. Only its initial is drawn; the label beside it names it. */
|
|
29
|
+
name: string;
|
|
30
|
+
size?: LogoMarkSize;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function initialOf(name: string): string {
|
|
34
|
+
const letter = [...name.trim()].find((character) => /[\p{L}\p{N}]/u.test(character));
|
|
35
|
+
return letter ? letter.toLocaleUpperCase() : "?";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A logo in a square tile, for lists of brokers and the accounts they hold, on
|
|
40
|
+
* the desktop and the web. Decoration: hidden from assistive technology, so
|
|
41
|
+
* the label beside it stays the name. The terminal draws nothing.
|
|
42
|
+
*/
|
|
43
|
+
export function LogoMark({ src, name, size = "row" }: LogoMarkProps) {
|
|
44
|
+
const desktop = useUiCapabilities().nativePaneChrome === true;
|
|
45
|
+
const colors = useThemeColors();
|
|
46
|
+
if (!desktop) return null;
|
|
47
|
+
const tile = TILE[size];
|
|
48
|
+
return (
|
|
49
|
+
<ImageSurface
|
|
50
|
+
src={src ?? undefined}
|
|
51
|
+
alt=""
|
|
52
|
+
objectFit="cover"
|
|
53
|
+
aria-hidden="true"
|
|
54
|
+
data-gloom-role="logo-mark"
|
|
55
|
+
style={{ width: tile.side, height: tile.side, flexShrink: 0, borderRadius: tile.radius }}
|
|
56
|
+
>
|
|
57
|
+
<Box width="100%" height="100%" alignItems="center" justifyContent="center" backgroundColor={colors.border}>
|
|
58
|
+
<Text fg={colors.textBright} attributes={TextAttributes.BOLD} style={LETTER_STYLE[size]}>
|
|
59
|
+
{initialOf(name)}
|
|
60
|
+
</Text>
|
|
61
|
+
</Box>
|
|
62
|
+
</ImageSurface>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Decimal stepping on a zero-based increment grid, without binary rounding drift. */
|
|
2
|
+
function decimal(value: string | number): { units: bigint; places: number } | undefined {
|
|
3
|
+
const text = String(value).trim();
|
|
4
|
+
const match = /^([+-]?)(\d*)(?:\.(\d*))?(?:e([+-]?\d+))?$/i.exec(text);
|
|
5
|
+
if (!match || !`${match[2]}${match[3] ?? ""}` || !Number.isFinite(Number(text))) return;
|
|
6
|
+
const exponent = Number(match[4] ?? 0);
|
|
7
|
+
const places = (match[3]?.length ?? 0) - exponent;
|
|
8
|
+
if (Math.abs(places) > 18 || text.length > 100) return;
|
|
9
|
+
const units = BigInt(`${match[1] === "-" ? "-" : ""}${match[2] || "0"}${match[3] ?? ""}`);
|
|
10
|
+
return places < 0 ? { units: units * 10n ** BigInt(-places), places: 0 } : { units, places };
|
|
11
|
+
}
|
|
12
|
+
function floorDivide(a: bigint, b: bigint): bigint { return a / b - (a < 0n && a % b !== 0n ? 1n : 0n); }
|
|
13
|
+
function ceilDivide(a: bigint, b: bigint): bigint { return -floorDivide(-a, b); }
|
|
14
|
+
function format(units: bigint, places: number): string {
|
|
15
|
+
const digits = (units < 0n ? -units : units).toString().padStart(places + 1, "0");
|
|
16
|
+
const text = places ? `${digits.slice(0, -places)}.${digits.slice(-places)}`.replace(/\.?0+$/, "") : digits;
|
|
17
|
+
return `${units < 0n ? "-" : ""}${text}`;
|
|
18
|
+
}
|
|
19
|
+
export function stepNumber(value: string, direction: 1 | -1, step: number, min?: number, max?: number): string | undefined {
|
|
20
|
+
const current = decimal(value === "" ? 0 : value), increment = decimal(step);
|
|
21
|
+
const lower = min === undefined ? undefined : decimal(min), upper = max === undefined ? undefined : decimal(max);
|
|
22
|
+
if (!current || !increment || increment.units <= 0n || min !== undefined && !lower || max !== undefined && !upper) return;
|
|
23
|
+
const places = Math.max(current.places, increment.places, lower?.places ?? 0, upper?.places ?? 0);
|
|
24
|
+
const scaled = (n: { units: bigint; places: number }) => n.units * 10n ** BigInt(places - n.places);
|
|
25
|
+
const at = scaled(current), by = scaled(increment);
|
|
26
|
+
let ticks = direction > 0 ? floorDivide(at, by) + 1n : ceilDivide(at, by) - 1n;
|
|
27
|
+
const low = lower && ceilDivide(scaled(lower), by), high = upper && floorDivide(scaled(upper), by);
|
|
28
|
+
if (low !== undefined && high !== undefined && low > high) return;
|
|
29
|
+
if (low !== undefined && ticks < low) ticks = low;
|
|
30
|
+
if (high !== undefined && ticks > high) ticks = high;
|
|
31
|
+
const next = ticks * by;
|
|
32
|
+
// A bound must not make a plus go down or a minus go up, except an empty field.
|
|
33
|
+
if (value !== "" && (direction > 0 ? next <= at : next >= at)) return;
|
|
34
|
+
const text = format(next, places), roundTrip = decimal(Number(text));
|
|
35
|
+
if (!roundTrip || roundTrip.places > places || scaled(roundTrip) !== next) return;
|
|
36
|
+
return text;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Smallest whole-number multiple, for brokers combining a step with whole quantities. */
|
|
40
|
+
export function wholeNumberStep(step: number): number | undefined {
|
|
41
|
+
const value = decimal(step);
|
|
42
|
+
if (!value || value.units <= 0n) return;
|
|
43
|
+
let a = value.units, b = 10n ** BigInt(value.places);
|
|
44
|
+
while (b) { const remainder = a % b; a = b; b = remainder; }
|
|
45
|
+
const result = Number(value.units / a);
|
|
46
|
+
return Number.isSafeInteger(result) ? result : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function matchesNumberStep(value: number, step: number): boolean {
|
|
50
|
+
const n = decimal(value), s = decimal(step);
|
|
51
|
+
if (!n || !s || s.units <= 0n) return false;
|
|
52
|
+
const places = Math.max(n.places, s.places);
|
|
53
|
+
return n.units * 10n ** BigInt(places - n.places) % (s.units * 10n ** BigInt(places - s.places)) === 0n;
|
|
54
|
+
}
|
|
@@ -48,9 +48,10 @@ export interface SelectButtonProps<T extends string = string> {
|
|
|
48
48
|
* `inline`: the label and the value in a row, in a toolbar. `field`: a
|
|
49
49
|
* bordered field. `form`: a field in a form of text fields; on the desktop
|
|
50
50
|
* and the web its label sits above it, bold, as a TextField's does; the
|
|
51
|
-
* terminal draws it inline.
|
|
51
|
+
* terminal draws it inline. `well`: a field without its border, on a
|
|
52
|
+
* recessed fill, for a dense panel such as the order ticket.
|
|
52
53
|
*/
|
|
53
|
-
variant?: "inline" | "field" | "form";
|
|
54
|
+
variant?: "inline" | "field" | "form" | "well";
|
|
54
55
|
width?: number;
|
|
55
56
|
onFocus?: () => void;
|
|
56
57
|
/**
|
|
@@ -189,8 +190,8 @@ export function SelectButton<T extends string = string>({
|
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
// The terminal draws a form select as it draws an inline one.
|
|
192
|
-
if (variant === "field") {
|
|
193
|
-
return <Button label={showLabel ? `${label}: ${current?.short ?? current?.label ?? ""}` : current?.short ?? current?.label ?? label} width={width} active={emphasized} disabled={disabled} onPress={openDialog} stopPropagation />;
|
|
193
|
+
if (variant === "field" || variant === "well") {
|
|
194
|
+
return <Button label={showLabel ? `${label}: ${current?.short ?? current?.label ?? ""}` : current?.short ?? current?.label ?? label} width={width} variant={variant === "well" ? "quiet" : undefined} active={emphasized} disabled={disabled} onPress={openDialog} stopPropagation />;
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
return (
|
|
@@ -22,10 +22,10 @@ export interface SelectFieldProps {
|
|
|
22
22
|
width?: number | string;
|
|
23
23
|
height?: number;
|
|
24
24
|
/**
|
|
25
|
-
* `field` is the form control used in dialogs. `
|
|
26
|
-
*
|
|
25
|
+
* `field` is the framed form control used in dialogs. `well` uses a quiet
|
|
26
|
+
* filled surface; `inline` reads as text. All open the same Menu.
|
|
27
27
|
*/
|
|
28
|
-
variant?: "field" | "inline";
|
|
28
|
+
variant?: "field" | "inline" | "well";
|
|
29
29
|
includeUnsetOption?: boolean;
|
|
30
30
|
/**
|
|
31
31
|
* Return focus to the trigger when the menu closes (inside a dialog). A list
|
|
@@ -5,6 +5,7 @@ import { Box, Text, useUiCapabilities } from "../../ui";
|
|
|
5
5
|
import { formatRelativeAge } from "../../utils/datetime-format";
|
|
6
6
|
import { Spinner } from "./loading";
|
|
7
7
|
import { ButtonActionScope } from "./action-scope";
|
|
8
|
+
import { blendForContrast, higherContrast } from "../../theme/color-utils";
|
|
8
9
|
import { Button } from "./button";
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -233,14 +234,27 @@ function EmptyStateBody({ title, message, hint, actions, retry, status = "empty"
|
|
|
233
234
|
export interface NoticeProps {
|
|
234
235
|
children: ReactNode;
|
|
235
236
|
tone?: "muted" | "positive" | "warning" | "negative";
|
|
237
|
+
variant?: "inline" | "callout";
|
|
236
238
|
}
|
|
237
239
|
|
|
238
240
|
/** Inline feedback leaves existing content visible, including stale data after a refresh failure. */
|
|
239
|
-
export function Notice({ children, tone = "warning" }: NoticeProps) {
|
|
241
|
+
export function Notice({ children, tone = "warning", variant = "inline" }: NoticeProps) {
|
|
240
242
|
const colors = useThemeColors();
|
|
243
|
+
const native = useUiCapabilities().nativePaneChrome;
|
|
244
|
+
const color = tone === "muted" ? colors.textDim : colors[tone];
|
|
245
|
+
const status = tone === "negative" ? "error" : "notice";
|
|
246
|
+
// Inline feedback keeps its original single-text shape: a flex row here collapses inside content-sized parents.
|
|
247
|
+
if (variant !== "callout") {
|
|
248
|
+
return (
|
|
249
|
+
<Box data-gloom-status={status} data-gloom-ui="notice">
|
|
250
|
+
<Text fg={color} wrapText>{children}</Text>
|
|
251
|
+
</Box>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
const foreground = blendForContrast(color, colors.panel, higherContrast("#000000", "#ffffff", colors.panel), 4.5);
|
|
241
255
|
return (
|
|
242
|
-
<Box data-gloom-status={tone === "negative" ? "
|
|
243
|
-
<Text fg={
|
|
256
|
+
<Box flexDirection="row" gap={1} paddingX={1} paddingY={native ? 0.35 : 0} backgroundColor={colors.panel} border={["left"]} borderColor={color} data-gloom-status={status} data-gloom-ui="notice" role={native ? tone === "negative" ? "alert" : "status" : undefined} aria-atomic={native ? true : undefined}>
|
|
257
|
+
<Text fg={foreground}>!</Text><Box flexGrow={1} minWidth={0}><Text fg={foreground} wrapText>{children}</Text></Box>
|
|
244
258
|
</Box>
|
|
245
259
|
);
|
|
246
260
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState, type ReactNode } from "react";
|
|
2
2
|
import { usePaneMenuItems, usePaneTabStripKeys } from "../layout/pane/footer/registration";
|
|
3
3
|
import type { ContextMenuItem } from "../../types/context-menu";
|
|
4
4
|
import { useShortcut } from "../../react/input";
|
|
@@ -22,6 +22,14 @@ type TabPointerEvent = {
|
|
|
22
22
|
interface TabItem {
|
|
23
23
|
label: string;
|
|
24
24
|
value: string;
|
|
25
|
+
/**
|
|
26
|
+
* Artwork before the label on the desktop and the web, such as the logo of
|
|
27
|
+
* the broker a portfolio's account is at (`LogoMark`). It is decoration: the
|
|
28
|
+
* terminal does not draw it, and automation and screen readers read only
|
|
29
|
+
* `label`. Keep the same element between renders (useMemo): a title-bar
|
|
30
|
+
* strip redraws when it changes.
|
|
31
|
+
*/
|
|
32
|
+
leading?: ReactNode;
|
|
25
33
|
disabled?: boolean;
|
|
26
34
|
/** Text color while inactive; used for group markers such as a team accent. */
|
|
27
35
|
fg?: string;
|
|
@@ -9,9 +9,12 @@ interface SegmentedControlOption {
|
|
|
9
9
|
label: string;
|
|
10
10
|
value: string;
|
|
11
11
|
disabled?: boolean;
|
|
12
|
+
tone?: "positive" | "negative";
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface SegmentedControlProps {
|
|
16
|
+
/** Name of the choice group for assistive technology and semantic access. */
|
|
17
|
+
accessibleLabel?: string;
|
|
15
18
|
options: SegmentedControlOption[];
|
|
16
19
|
value: string;
|
|
17
20
|
onChange?: (value: string) => void;
|
|
@@ -21,9 +24,13 @@ export interface SegmentedControlProps {
|
|
|
21
24
|
shortcutScope?: string;
|
|
22
25
|
width?: number | "100%";
|
|
23
26
|
wrap?: boolean;
|
|
27
|
+
size?: "default" | "large";
|
|
28
|
+
/** One filled selection without an enclosing frame. */
|
|
29
|
+
variant?: "default" | "flat";
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export function SegmentedControl({
|
|
33
|
+
accessibleLabel,
|
|
27
34
|
options,
|
|
28
35
|
value,
|
|
29
36
|
onChange,
|
|
@@ -32,13 +39,15 @@ export function SegmentedControl({
|
|
|
32
39
|
shortcutScope,
|
|
33
40
|
width,
|
|
34
41
|
wrap = false,
|
|
42
|
+
size = "default",
|
|
43
|
+
variant = "default",
|
|
35
44
|
}: SegmentedControlProps) {
|
|
36
45
|
const colors = useThemeColors();
|
|
37
46
|
const ui = useUiHost();
|
|
38
47
|
const HostSegmentedControl = ui.SegmentedControl as ComponentType<SegmentedControlProps> | undefined;
|
|
39
48
|
useRemoteUiNode({
|
|
40
49
|
role: "select",
|
|
41
|
-
label: "Segmented control",
|
|
50
|
+
label: accessibleLabel ?? "Segmented control",
|
|
42
51
|
actions: {
|
|
43
52
|
select: (input) => {
|
|
44
53
|
const next = typeof input === "string" ? input : (input as { value?: string } | null)?.value;
|
|
@@ -78,12 +87,15 @@ export function SegmentedControl({
|
|
|
78
87
|
if (HostSegmentedControl) {
|
|
79
88
|
return (
|
|
80
89
|
<HostSegmentedControl
|
|
90
|
+
accessibleLabel={accessibleLabel}
|
|
81
91
|
options={options}
|
|
82
92
|
value={value}
|
|
83
93
|
onChange={onChange}
|
|
84
94
|
focused={focused}
|
|
85
95
|
width={width}
|
|
86
96
|
wrap={wrap}
|
|
97
|
+
size={size}
|
|
98
|
+
variant={variant}
|
|
87
99
|
/>
|
|
88
100
|
);
|
|
89
101
|
}
|
|
@@ -100,7 +112,9 @@ export function SegmentedControl({
|
|
|
100
112
|
return (
|
|
101
113
|
<Box
|
|
102
114
|
key={option.value}
|
|
103
|
-
|
|
115
|
+
flexGrow={size === "large" ? 1 : undefined}
|
|
116
|
+
justifyContent="center"
|
|
117
|
+
backgroundColor={option.disabled ? colors.panel : active && option.tone ? colors[option.tone] : active ? (focused ? colors.borderFocused : colors.selected) : colors.panel}
|
|
104
118
|
onMouseDown={() => {
|
|
105
119
|
if (!option.disabled) onChange?.(option.value);
|
|
106
120
|
}}
|
|
@@ -109,7 +123,7 @@ export function SegmentedControl({
|
|
|
109
123
|
<Text
|
|
110
124
|
// The focused fill is the primary button's, so its text is too:
|
|
111
125
|
// selected text on it is unreadable in the white theme.
|
|
112
|
-
fg={option.disabled ? colors.textMuted : active ? (focused ? colors.bg : colors.selectedText) : colors.textDim}
|
|
126
|
+
fg={option.disabled ? colors.textMuted : active && option.tone ? colors.bg : option.tone ? colors[option.tone] : active ? (focused ? colors.bg : colors.selectedText) : colors.textDim}
|
|
113
127
|
attributes={active ? TextAttributes.BOLD : 0}
|
|
114
128
|
>
|
|
115
129
|
{` ${option.label} `}
|
|
@@ -3,7 +3,7 @@ import { findPaneInstance, type PaneBinding } from "../../../types/config";
|
|
|
3
3
|
import type { BrokerContractRef, TickerListingRef } from "../../../types/instrument";
|
|
4
4
|
import type { TickerRecord } from "../../../types/ticker";
|
|
5
5
|
import { instrumentFromTicker, type InstrumentRef } from "../../../market-data/request-types";
|
|
6
|
-
import {
|
|
6
|
+
import { heldOnListing, sameBrokerContract } from "../../../utils/instrument-identity";
|
|
7
7
|
import { isBrokerPortfolioId } from "../../../utils/broker-instances";
|
|
8
8
|
import { searchContractKey } from "../../../tickers/search/identity";
|
|
9
9
|
import { canonicalExchange } from "../../../utils/exchanges";
|
|
@@ -77,10 +77,15 @@ function contractListingExchange(ticker: TickerRecord, instrument: BrokerContrac
|
|
|
77
77
|
export function tickerForInstrument(ticker: TickerRecord | null, instrument: BrokerContractRef | null | undefined, listing?: TickerListingRef): TickerRecord | null {
|
|
78
78
|
if (!ticker || instrument === undefined) return null;
|
|
79
79
|
if (!instrument && !listing && !ticker.metadata.broker_contracts?.length) return ticker;
|
|
80
|
-
|
|
80
|
+
// A position is this contract's however the broker routed it: SPY held through SMART is the ARCX listing's.
|
|
81
81
|
const positions = ticker.metadata.positions.filter((position) => {
|
|
82
82
|
const selected = instrumentFromTicker({ ...ticker, metadata: { ...ticker.metadata, positions: [position] } }, null, { portfolioId: position.portfolio });
|
|
83
|
-
|
|
83
|
+
const held = selected?.instrument;
|
|
84
|
+
if (!selected) return false;
|
|
85
|
+
if (!held) return !instrument;
|
|
86
|
+
// A pane opened on a listing from search has no broker contract; a share held there is still its.
|
|
87
|
+
if (!instrument) return !!listing && heldOnListing(held, listing);
|
|
88
|
+
return held.brokerId === instrument.brokerId && (held.brokerInstanceId ?? "") === (instrument.brokerInstanceId ?? "") && sameBrokerContract(held, instrument);
|
|
84
89
|
});
|
|
85
90
|
return { ...ticker, metadata: {
|
|
86
91
|
...ticker.metadata,
|
|
@@ -24,6 +24,7 @@ import { migrateSavedConfig, type ConfigMigrationHost } from "./migrations";
|
|
|
24
24
|
import { encodeBuiltinDisabledPluginIds, expandBuiltinPluginGroups } from "../../../plugins/ownership";
|
|
25
25
|
import { sanitizeSavedPaneState } from "./pane-state";
|
|
26
26
|
import { isRecord } from "../../../utils/guards";
|
|
27
|
+
import { normalizeStoredBaseCurrency } from "../../../utils/base-currency";
|
|
27
28
|
import { debugLog } from "../../../utils/debug-log";
|
|
28
29
|
import { DEFAULT_THEME, themes } from "../../../theme/themes";
|
|
29
30
|
import { sanitizeStarPrompt } from "../../../app/star-prompt/model";
|
|
@@ -80,7 +81,9 @@ export function normalizeLoadedConfig(
|
|
|
80
81
|
const config: AppConfig = {
|
|
81
82
|
dataDir,
|
|
82
83
|
configVersion: CURRENT_CONFIG_VERSION,
|
|
83
|
-
|
|
84
|
+
// A name or sign an older version saved (Canadian dollar, CA$) reads as its code; nothing turns into USD unasked.
|
|
85
|
+
baseCurrency: typeof candidate.baseCurrency === "string" && candidate.baseCurrency.trim()
|
|
86
|
+
? normalizeStoredBaseCurrency(candidate.baseCurrency) : defaults.baseCurrency,
|
|
84
87
|
refreshIntervalMinutes: typeof candidate.refreshIntervalMinutes === "number" ? candidate.refreshIntervalMinutes : defaults.refreshIntervalMinutes,
|
|
85
88
|
portfolios: sanitizePortfolios(candidate.portfolios, defaults.portfolios),
|
|
86
89
|
watchlists: sanitizeWatchlists(candidate.watchlists, defaults.watchlists),
|
package/src/data/plugin-cache.ts
CHANGED
|
@@ -44,6 +44,24 @@ export function createPluginCache<T, Stored = T>(options: {
|
|
|
44
44
|
source: options.source,
|
|
45
45
|
};
|
|
46
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Listeners by key, kept apart from the queries so they outlive a reset:
|
|
49
|
+
* a pane that subscribed before the plugin attached its store still hears
|
|
50
|
+
* the loads made after.
|
|
51
|
+
*/
|
|
52
|
+
const listeners = new Map<string, Set<() => void>>();
|
|
53
|
+
const emit = (key: string) => {
|
|
54
|
+
for (const listener of [...listeners.get(key) ?? []]) listener();
|
|
55
|
+
};
|
|
56
|
+
const queryFor = (key: string): CachedQuery<T> => {
|
|
57
|
+
let query = queries.get(key);
|
|
58
|
+
if (!query) {
|
|
59
|
+
query = new CachedQuery({ read: (allowExpired) => read(key, allowExpired) });
|
|
60
|
+
query.subscribe(() => emit(key));
|
|
61
|
+
queries.set(key, query);
|
|
62
|
+
}
|
|
63
|
+
return query;
|
|
64
|
+
};
|
|
47
65
|
const reset = () => {
|
|
48
66
|
generation += 1;
|
|
49
67
|
persistence = null;
|
|
@@ -53,21 +71,29 @@ export function createPluginCache<T, Stored = T>(options: {
|
|
|
53
71
|
};
|
|
54
72
|
return {
|
|
55
73
|
attach(next: PluginPersistence) {
|
|
56
|
-
if (persistence
|
|
74
|
+
if (persistence === next) return;
|
|
75
|
+
reset();
|
|
57
76
|
persistence = next;
|
|
77
|
+
// What a listener reads now comes from another store.
|
|
78
|
+
for (const key of [...listeners.keys()]) emit(key);
|
|
58
79
|
},
|
|
59
80
|
reset,
|
|
81
|
+
subscribe(key: string, listener: () => void): () => void {
|
|
82
|
+
const keyed = listeners.get(key) ?? new Set<() => void>();
|
|
83
|
+
listeners.set(key, keyed);
|
|
84
|
+
keyed.add(listener);
|
|
85
|
+
return () => {
|
|
86
|
+
keyed.delete(listener);
|
|
87
|
+
if (keyed.size === 0 && listeners.get(key) === keyed) listeners.delete(key);
|
|
88
|
+
};
|
|
89
|
+
},
|
|
60
90
|
get(key: string, { allowExpired = false } = {}): PluginCacheResult<T> | null {
|
|
61
91
|
const cached = read(key, allowExpired) ?? queries.get(key)?.getSnapshot().result;
|
|
62
92
|
if (!cached || (!allowExpired && cached.expiresAt <= Date.now())) return null;
|
|
63
93
|
return { data: cached.value, fetchedAt: cached.fetchedAt, stale: cached.staleAt <= Date.now(), source: "cache" };
|
|
64
94
|
},
|
|
65
95
|
async load(key: string, loader: () => Promise<T>, { force = false, replace = false } = {}): Promise<PluginCacheResult<T>> {
|
|
66
|
-
|
|
67
|
-
if (!query) {
|
|
68
|
-
query = new CachedQuery({ read: (allowExpired) => read(key, allowExpired) });
|
|
69
|
-
queries.set(key, query);
|
|
70
|
-
}
|
|
96
|
+
const query = queryFor(key);
|
|
71
97
|
const owner = generation;
|
|
72
98
|
const before = query.getSnapshot().result;
|
|
73
99
|
const result = await query.load({ force, replace, fetch: async () => {
|