gloomberb 0.16.2 → 0.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/account-cache.ts +45 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/command-options.ts +74 -0
- package/src/cli/commands/command-utils.ts +29 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +524 -148
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +223 -42
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +200 -73
- package/src/cli/desktop-pane-shot.ts +100 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +40 -7
- package/src/cli/history-rows.ts +187 -15
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +21 -13
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +55 -7
- package/src/cli/pane-functions/capabilities.ts +13 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +89 -28
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +71 -24
- package/src/cli/pane-functions/headless.ts +168 -35
- package/src/cli/pane-functions/index.ts +12 -2
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +12 -1
- package/src/cli/pane-functions/resolver.ts +32 -8
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +109 -23
- package/src/cli/registry.ts +8 -4
- package/src/cli/report-tables.ts +460 -0
- package/src/cli/result.ts +63 -16
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/export.ts +6 -51
- package/src/components/data-table/view.tsx +31 -10
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +67 -35
- package/src/components/layout/shell/cursor-occlusion.ts +6 -5
- package/src/components/layout/shell/drag/index.ts +16 -7
- package/src/components/layout/shell/drag/live.ts +193 -0
- package/src/components/layout/shell/drag/overlays.tsx +179 -35
- package/src/components/layout/shell/drag/runtime.ts +62 -50
- package/src/components/layout/shell/drag/slide.ts +10 -0
- package/src/components/layout/shell/index.tsx +84 -57
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
- package/src/components/layout/shell/native/surfaces.ts +30 -14
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +461 -180
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/data/json-persistence.ts +17 -3
- package/src/data/json-storage.ts +2 -0
- package/src/data/memory-resource-store.ts +46 -0
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +156 -11
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/freshness.ts +5 -0
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/pane-model.ts +10 -6
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/catalysts/detail.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/client.ts +3 -6
- package/src/plugins/builtin/holders/format.ts +31 -1
- package/src/plugins/builtin/holders/headless.ts +32 -6
- package/src/plugins/builtin/holders/pane.tsx +25 -13
- package/src/plugins/builtin/holders/report-header.ts +46 -5
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/members/pane.tsx +10 -0
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/perps/pane.tsx +1 -1
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
- package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +260 -0
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/themes/pane.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
- package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
- package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -6
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
- package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
- package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
- package/src/plugins/builtin/world-venue-map/model.ts +16 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/browser/app-services.ts +3 -1
- package/src/renderers/dom/data-table/dom.ts +31 -0
- package/src/renderers/dom/data-table/index.tsx +5 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +113 -17
- package/src/renderers/dom/host/drag-shield.ts +35 -0
- package/src/renderers/dom/host/live-frame.ts +62 -0
- package/src/renderers/dom/host/mouse.ts +8 -1
- package/src/renderers/dom/host/scroll-box.tsx +6 -5
- package/src/renderers/dom/host/tabs.tsx +3 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +26 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
- package/src/renderers/electrobun/shared/protocol.ts +12 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/host/native.ts +0 -10
- package/src/renderers/electrobun/view/host/window-move.ts +28 -0
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
- package/src/renderers/electrobun/view/ui-host.tsx +2 -5
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/renderers/opentui/ui-host.tsx +7 -1
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/batches.ts +3 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +13 -7
- package/src/sources/provider-router/financials.ts +106 -16
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +24 -6
- package/src/sources/sec-edgar.ts +68 -8
- package/src/state/pane-scroll-registry.tsx +67 -35
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/margin-account.ts +108 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolution.ts +15 -0
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/financials.ts +32 -3
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +10 -3
- package/src/ui/scroll-box-axis.ts +26 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/currency-units.ts +8 -3
- package/src/utils/depositary-receipt.ts +54 -10
- package/src/utils/display-number.ts +63 -0
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fundamentals.ts +17 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history-integrity.ts +19 -6
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -2,10 +2,22 @@ import type { DataProvider } from "../../../types/data-provider";
|
|
|
2
2
|
import type { Quote } from "../../../types/financials";
|
|
3
3
|
import type { IndexEntry } from "./indices";
|
|
4
4
|
import { errorMessage } from "../../../utils/errors";
|
|
5
|
+
import { isNoProviderMessage } from "../../../sources/provider-errors";
|
|
6
|
+
|
|
7
|
+
/** What a row says in place of figures the feed does not carry. */
|
|
8
|
+
export const NOT_IN_FEED = "not available from the feed";
|
|
9
|
+
|
|
10
|
+
/** Whether the feed has no quote for a symbol, as opposed to a quote that failed to load. */
|
|
11
|
+
export function isFeedGap(error: string | null | undefined): boolean {
|
|
12
|
+
return !!error && isNoProviderMessage(error);
|
|
13
|
+
}
|
|
5
14
|
|
|
6
15
|
export interface WorldIndexQuoteResult {
|
|
7
16
|
quotes: Map<string, Quote | null>;
|
|
17
|
+
/** Failures to load: a timeout, an outage. A symbol the feed simply lacks is in `gaps`. */
|
|
8
18
|
errors: string[];
|
|
19
|
+
/** Symbols the feed has no quote for. They are a data gap, so their rows say so rather than carry an error. */
|
|
20
|
+
gaps: Set<string>;
|
|
9
21
|
}
|
|
10
22
|
|
|
11
23
|
export async function loadWorldIndexQuotes(
|
|
@@ -14,6 +26,12 @@ export async function loadWorldIndexQuotes(
|
|
|
14
26
|
): Promise<WorldIndexQuoteResult> {
|
|
15
27
|
const quotes = new Map<string, Quote | null>();
|
|
16
28
|
const errors: string[] = [];
|
|
29
|
+
const gaps = new Set<string>();
|
|
30
|
+
const fail = (symbol: string, error: unknown) => {
|
|
31
|
+
const message = errorMessage(error);
|
|
32
|
+
if (isFeedGap(message)) gaps.add(symbol);
|
|
33
|
+
else errors.push(`${symbol}: ${message}`);
|
|
34
|
+
};
|
|
17
35
|
if (provider.getQuotesBatch) {
|
|
18
36
|
const results = await provider.getQuotesBatch(
|
|
19
37
|
entries.map((entry) => ({ symbol: entry.symbol, exchange: "" })),
|
|
@@ -22,10 +40,10 @@ export async function loadWorldIndexQuotes(
|
|
|
22
40
|
for (const entry of entries) {
|
|
23
41
|
const result = bySymbol.get(entry.symbol);
|
|
24
42
|
quotes.set(entry.symbol, result?.quote ?? null);
|
|
25
|
-
if (result?.error)
|
|
43
|
+
if (result?.error) fail(entry.symbol, result.error);
|
|
26
44
|
else if (!result?.quote) errors.push(`${entry.symbol}: quote unavailable`);
|
|
27
45
|
}
|
|
28
|
-
return { quotes, errors };
|
|
46
|
+
return { quotes, errors, gaps };
|
|
29
47
|
}
|
|
30
48
|
|
|
31
49
|
await Promise.all(entries.map(async (entry) => {
|
|
@@ -33,8 +51,8 @@ export async function loadWorldIndexQuotes(
|
|
|
33
51
|
quotes.set(entry.symbol, await provider.getQuote(entry.symbol, ""));
|
|
34
52
|
} catch (error) {
|
|
35
53
|
quotes.set(entry.symbol, null);
|
|
36
|
-
|
|
54
|
+
fail(entry.symbol, error);
|
|
37
55
|
}
|
|
38
56
|
}));
|
|
39
|
-
return { quotes, errors };
|
|
57
|
+
return { quotes, errors, gaps };
|
|
40
58
|
}
|
|
@@ -5,9 +5,9 @@ import type {
|
|
|
5
5
|
HeadlessPaneLoadArgs,
|
|
6
6
|
} from "../../../types/plugin";
|
|
7
7
|
import { formatNumber, formatPercentRaw } from "../../../utils/format";
|
|
8
|
-
import { marketStateLabel } from "../../../market-data/market/status";
|
|
8
|
+
import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, marketStateLabel, type ExtendedSession } from "../../../market-data/market/status";
|
|
9
9
|
import type { MarketState } from "../../../types/financials";
|
|
10
|
-
import { loadWorldIndexQuotes, type WorldIndexQuoteResult } from "./client";
|
|
10
|
+
import { loadWorldIndexQuotes, NOT_IN_FEED, type WorldIndexQuoteResult } from "./client";
|
|
11
11
|
import {
|
|
12
12
|
getIndicesByRegion,
|
|
13
13
|
REGION_LABELS,
|
|
@@ -17,14 +17,17 @@ import {
|
|
|
17
17
|
} from "./indices";
|
|
18
18
|
import { quoteFreshnessFields } from "../shared/report-freshness";
|
|
19
19
|
|
|
20
|
+
/** A row for an index the feed does not carry has no figures to print; its last column says why. */
|
|
21
|
+
const isGap = (row: Record<string, unknown>) => row.unavailable === true;
|
|
22
|
+
|
|
20
23
|
const COLUMNS = [
|
|
21
|
-
{ key: "shortName", header: "Index" },
|
|
24
|
+
{ key: "shortName", header: "Index", shrink: false },
|
|
22
25
|
{ key: "name", header: "Name" },
|
|
23
26
|
{
|
|
24
27
|
key: "price",
|
|
25
28
|
header: "Last",
|
|
26
29
|
align: "right" as const,
|
|
27
|
-
format: (value: unknown) => value == null
|
|
30
|
+
format: (value: unknown, row: Record<string, unknown>) => isGap(row) ? "" : value == null
|
|
28
31
|
? "-"
|
|
29
32
|
: formatNumber(Number(value), 2),
|
|
30
33
|
},
|
|
@@ -32,58 +35,82 @@ const COLUMNS = [
|
|
|
32
35
|
key: "change",
|
|
33
36
|
header: "Change",
|
|
34
37
|
align: "right" as const,
|
|
35
|
-
format: (value: unknown) => value == null ? "-" : `${Number(value) >= 0 ? "+" : ""}${formatNumber(Number(value), 2)}`,
|
|
38
|
+
format: (value: unknown, row: Record<string, unknown>) => isGap(row) ? "" : value == null ? "-" : `${Number(value) >= 0 ? "+" : ""}${formatNumber(Number(value), 2)}`,
|
|
36
39
|
},
|
|
37
40
|
{
|
|
38
41
|
key: "changePercent",
|
|
39
42
|
header: "Change %",
|
|
40
43
|
align: "right" as const,
|
|
41
|
-
format: (value: unknown) => value == null ? "-" : formatPercentRaw(Number(value)),
|
|
44
|
+
format: (value: unknown, row: Record<string, unknown>) => isGap(row) ? "" : value == null ? "-" : formatPercentRaw(Number(value)),
|
|
42
45
|
},
|
|
43
46
|
{
|
|
44
47
|
key: "marketState",
|
|
45
48
|
header: "Session",
|
|
46
|
-
|
|
49
|
+
shrink: false,
|
|
50
|
+
format: (value: unknown, row: Record<string, unknown>) => isGap(row) ? "" : typeof value === "string" && value ? marketStateLabel(value as MarketState) ?? value : "-",
|
|
47
51
|
},
|
|
48
52
|
{
|
|
49
53
|
key: "lastUpdated",
|
|
50
54
|
header: "Updated",
|
|
51
|
-
|
|
55
|
+
shrink: false,
|
|
56
|
+
format: (value: unknown, row: Record<string, unknown>) => {
|
|
52
57
|
const time = value == null ? Number.NaN : Number(value);
|
|
53
|
-
return Number.isFinite(time) ? `${new Date(time).toISOString().slice(0, 16).replace("T", " ")} UTC` : "-";
|
|
58
|
+
return Number.isFinite(time) ? `${new Date(time).toISOString().slice(0, 16).replace("T", " ")} UTC` : isGap(row) ? NOT_IN_FEED : "-";
|
|
54
59
|
},
|
|
55
60
|
},
|
|
56
61
|
];
|
|
57
62
|
|
|
63
|
+
/** A pre-market or after-hours print and its move from the regular close, for a row that has one. */
|
|
64
|
+
function extendedColumn(session: ExtendedSession) {
|
|
65
|
+
return {
|
|
66
|
+
key: "extendedPrice",
|
|
67
|
+
header: EXTENDED_SESSION_LABELS[session],
|
|
68
|
+
align: "right" as const,
|
|
69
|
+
format: (value: unknown, row: Record<string, unknown>) => row.extendedSession === session && typeof value === "number"
|
|
70
|
+
? `${formatNumber(value, 2)} ${formatPercentRaw(typeof row.extendedChangePercent === "number" ? row.extendedChangePercent : undefined)}`
|
|
71
|
+
: isGap(row) ? "" : "-",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The table's columns, with an extended column after Change % only where a row has such a print. */
|
|
76
|
+
function sectionColumns(rows: readonly Record<string, unknown>[]) {
|
|
77
|
+
const sessions = (["PRE", "POST"] as const).filter((session) => rows.some((row) => row.extendedSession === session));
|
|
78
|
+
return sessions.length === 0 ? COLUMNS : [...COLUMNS.slice(0, 5), ...sessions.map(extendedColumn), ...COLUMNS.slice(5)];
|
|
79
|
+
}
|
|
80
|
+
|
|
58
81
|
function projectWorldIndicesHeadless(
|
|
59
82
|
entries: readonly IndexEntry[],
|
|
60
83
|
loaded: WorldIndexQuoteResult,
|
|
61
84
|
): HeadlessBundleResult {
|
|
62
85
|
const grouped = getIndicesByRegion(entries);
|
|
86
|
+
const isGapEntry = (entry: IndexEntry) => loaded.gaps.has(entry.symbol) && !loaded.quotes.get(entry.symbol);
|
|
63
87
|
return {
|
|
64
88
|
sections: REGION_ORDER.flatMap((region) => {
|
|
65
89
|
const regionEntries = grouped.get(region) ?? [];
|
|
66
90
|
if (regionEntries.length === 0) return [];
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
91
|
+
const rows = regionEntries.map((entry) => {
|
|
92
|
+
const quote = loaded.quotes.get(entry.symbol);
|
|
93
|
+
// Last and Change are the regular session, as `ticker` reads them.
|
|
94
|
+
const { price, change, changePercent, ...extended } = getQuoteSessionFields(quote);
|
|
95
|
+
return {
|
|
96
|
+
...quoteFreshnessFields(quote),
|
|
97
|
+
...entry,
|
|
98
|
+
price,
|
|
99
|
+
unit: "index points",
|
|
100
|
+
currency: quote?.currency ?? null,
|
|
101
|
+
change,
|
|
102
|
+
changePercent,
|
|
103
|
+
...extended,
|
|
104
|
+
marketState: quote?.marketState ?? null,
|
|
105
|
+
lastUpdated: quote?.lastUpdated ?? null,
|
|
106
|
+
...(isGapEntry(entry) ? { unavailable: true } : {}),
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
return [{ title: REGION_LABELS[region], columns: sectionColumns(rows), rows }];
|
|
85
110
|
}),
|
|
86
111
|
errors: loaded.errors,
|
|
112
|
+
// A symbol the feed lacks is a gap in the data, not a failure: its row says so, and the report is not whole.
|
|
113
|
+
...(entries.some(isGapEntry) ? { unavailableSymbols: entries.filter(isGapEntry).map((entry) => entry.symbol) } : {}),
|
|
87
114
|
metadata: {
|
|
88
115
|
requested: entries.length,
|
|
89
116
|
available: [...loaded.quotes.values()].filter(Boolean).length,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MarketState } from "../../../types/financials";
|
|
2
2
|
import { compareSortValues, type SortDirection } from "../../../utils/sort-values";
|
|
3
|
-
import type
|
|
3
|
+
import { boardHeadlineQuote, type BoardQuoteMap } from "../shared/use-quote-board";
|
|
4
4
|
import { REGION_ORDER, type IndexEntry } from "./indices";
|
|
5
5
|
|
|
6
6
|
export type WorldIndexTableRow =
|
|
@@ -40,7 +40,9 @@ function getSortValue(
|
|
|
40
40
|
entry: IndexEntry,
|
|
41
41
|
quotes: BoardQuoteMap,
|
|
42
42
|
): string | number | null {
|
|
43
|
-
const
|
|
43
|
+
const sent = quotes.get(entry.symbol)?.quote;
|
|
44
|
+
// Sorted by the figures the rows show.
|
|
45
|
+
const quote = sent ? boardHeadlineQuote(sent) : sent;
|
|
44
46
|
|
|
45
47
|
switch (columnId) {
|
|
46
48
|
case "status":
|
|
@@ -3,7 +3,8 @@ import { colors } from "../../../theme/colors";
|
|
|
3
3
|
import type { Quote } from "../../../types/financials";
|
|
4
4
|
import { TextAttributes } from "../../../ui";
|
|
5
5
|
import { formatNumber } from "../../../utils/format";
|
|
6
|
-
import { renderQuoteBoardCell, type BoardQuoteMap } from "../shared/use-quote-board";
|
|
6
|
+
import { renderQuoteBoardCell, type BoardQuoteMap, type BoardQuoteState } from "../shared/use-quote-board";
|
|
7
|
+
import { isFeedGap, NOT_IN_FEED } from "./client";
|
|
7
8
|
import type {
|
|
8
9
|
WorldIndexColumnId,
|
|
9
10
|
WorldIndexTableRow,
|
|
@@ -66,6 +67,11 @@ const formatIndexPrice = (quote: Quote) => formatNumber(quote.price, 2);
|
|
|
66
67
|
const formatIndexChange = (quote: Quote) =>
|
|
67
68
|
`${quote.change >= 0 ? "+" : "-"}${formatNumber(Math.abs(quote.change), 2)}`;
|
|
68
69
|
|
|
70
|
+
/** An index the feed has no quote for: not loading, and no figures to show. */
|
|
71
|
+
function isUnavailableInFeed(state: BoardQuoteState | undefined): boolean {
|
|
72
|
+
return !!state && !state.quote && !state.loading && isFeedGap(state.error);
|
|
73
|
+
}
|
|
74
|
+
|
|
69
75
|
export function renderWorldIndexCell(
|
|
70
76
|
row: WorldIndexTableRow,
|
|
71
77
|
column: WorldIndexColumn,
|
|
@@ -75,6 +81,8 @@ export function renderWorldIndexCell(
|
|
|
75
81
|
if (row.type === "header") return { text: "" };
|
|
76
82
|
|
|
77
83
|
const { entry } = row;
|
|
84
|
+
// The figures are blank and the name cell says why, ahead of the name so a narrow pane clips the name, not the reason.
|
|
85
|
+
const unavailable = isUnavailableInFeed(quotes.get(entry.symbol));
|
|
78
86
|
switch (column.id) {
|
|
79
87
|
case "symbol":
|
|
80
88
|
return {
|
|
@@ -83,8 +91,11 @@ export function renderWorldIndexCell(
|
|
|
83
91
|
attributes: TextAttributes.BOLD,
|
|
84
92
|
};
|
|
85
93
|
case "name":
|
|
86
|
-
return
|
|
94
|
+
return unavailable
|
|
95
|
+
? { text: `${NOT_IN_FEED} · ${entry.name}`, color: colors.textDim }
|
|
96
|
+
: { text: entry.name };
|
|
87
97
|
default:
|
|
98
|
+
if (unavailable) return { text: "" };
|
|
88
99
|
return renderQuoteBoardCell(column.id, quotes.get(entry.symbol), {
|
|
89
100
|
sessionText: options?.sessionText,
|
|
90
101
|
formatPrice: formatIndexPrice,
|
|
@@ -138,9 +138,13 @@ export function tilesInView(tiles: readonly DetailTile[], view: GeoBox, margin =
|
|
|
138
138
|
return tiles.filter(({ box }) => box[0] < east && box[2] > west && box[1] < north && box[3] > south);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/** A country name at its label point, with the web-map zoom it first shows at and its rank. */
|
|
142
|
+
export type CountryLabel = readonly [name: string, longitude: number, latitude: number, minZoom: number, rank: number];
|
|
143
|
+
|
|
141
144
|
/** What `natural-earth/index.chunk.ts` exports. */
|
|
142
145
|
interface BasemapIndex {
|
|
143
146
|
loadWorld: () => Promise<BasemapChunk>;
|
|
147
|
+
loadLabels: () => Promise<{ COUNTRY_LABELS: readonly CountryLabel[] }>;
|
|
144
148
|
DETAIL_TILES: readonly DetailTile[];
|
|
145
149
|
}
|
|
146
150
|
|
|
@@ -175,6 +179,20 @@ export function loadWorldBasemap(): Promise<BasemapPaths> {
|
|
|
175
179
|
return worldPaths;
|
|
176
180
|
}
|
|
177
181
|
|
|
182
|
+
let countryLabels: Promise<readonly CountryLabel[]> | null = null;
|
|
183
|
+
|
|
184
|
+
/** Country names, most prominent first; a lazy chunk like the land. */
|
|
185
|
+
export function loadCountryLabels(): Promise<readonly CountryLabel[]> {
|
|
186
|
+
countryLabels ??= loadIndex()
|
|
187
|
+
.then((index) => index.loadLabels())
|
|
188
|
+
.then((chunk) => chunk.COUNTRY_LABELS)
|
|
189
|
+
.catch((error: unknown) => {
|
|
190
|
+
countryLabels = null;
|
|
191
|
+
throw error;
|
|
192
|
+
});
|
|
193
|
+
return countryLabels;
|
|
194
|
+
}
|
|
195
|
+
|
|
178
196
|
export function loadDetailIndex(): Promise<readonly DetailTile[]> {
|
|
179
197
|
return loadIndex().then((index) => index.DETAIL_TILES);
|
|
180
198
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which country names the desktop and web map write, and where. Names come
|
|
3
|
+
* from Natural Earth's label points, most prominent first, each with the
|
|
4
|
+
* web-map zoom it first shows at. A name shows once the map is that close (a
|
|
5
|
+
* little before), sits wholly inside the map, and keeps clear of the names
|
|
6
|
+
* already placed and of the selected or hovered marker. Same view, same names.
|
|
7
|
+
*/
|
|
8
|
+
import type { CountryLabel } from "./basemap";
|
|
9
|
+
|
|
10
|
+
export interface PlacedCountryLabel {
|
|
11
|
+
name: string;
|
|
12
|
+
longitude: number;
|
|
13
|
+
latitude: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A marker the names keep clear of, in pixels from the map's top left. */
|
|
17
|
+
export interface LabelObstacle {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
radius: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const COUNTRY_LABEL_FONT_PX = 10;
|
|
24
|
+
/** The map's font is monospaced: every character advances this much of the font size. */
|
|
25
|
+
const CHARACTER_EM = 0.6;
|
|
26
|
+
/** Room kept around a name, so two never touch. */
|
|
27
|
+
const GAP_PX = 6;
|
|
28
|
+
/** Names show at Natural Earth's own zoom; this moves the threshold earlier (positive) or later. */
|
|
29
|
+
const ZOOM_LEAD = 0;
|
|
30
|
+
/** The largest countries are named on any world view, room permitting. */
|
|
31
|
+
const WORLD_VIEW_MIN_ZOOM = 1.7;
|
|
32
|
+
/** Never more names than this on one view. */
|
|
33
|
+
const MAX_LABELS = 60;
|
|
34
|
+
|
|
35
|
+
/** The web-map zoom (256 px tiles for 360 degrees) of a map drawn at this many pixels per degree. */
|
|
36
|
+
export function webMapZoom(pxPerDegree: number): number {
|
|
37
|
+
return Math.log2((Math.max(pxPerDegree, 1e-9) * 360) / 256);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function layoutCountryLabels(
|
|
41
|
+
labels: readonly CountryLabel[],
|
|
42
|
+
options: {
|
|
43
|
+
pxPerDegree: number;
|
|
44
|
+
/** Where a longitude and latitude fall, in pixels from the map's top left. */
|
|
45
|
+
toPixels: (longitude: number, latitude: number) => { x: number; y: number };
|
|
46
|
+
widthPx: number;
|
|
47
|
+
heightPx: number;
|
|
48
|
+
obstacles?: readonly LabelObstacle[];
|
|
49
|
+
fontPx?: number;
|
|
50
|
+
},
|
|
51
|
+
): PlacedCountryLabel[] {
|
|
52
|
+
const { toPixels, widthPx, heightPx, obstacles = [] } = options;
|
|
53
|
+
const fontPx = options.fontPx ?? COUNTRY_LABEL_FONT_PX;
|
|
54
|
+
if (!(widthPx > 0 && heightPx > 0 && options.pxPerDegree > 0)) return [];
|
|
55
|
+
const threshold = Math.max(webMapZoom(options.pxPerDegree) + ZOOM_LEAD, WORLD_VIEW_MIN_ZOOM);
|
|
56
|
+
const taken: [number, number, number, number][] = [];
|
|
57
|
+
const placed: PlacedCountryLabel[] = [];
|
|
58
|
+
for (const [name, longitude, latitude, minZoom] of labels) {
|
|
59
|
+
if (placed.length >= MAX_LABELS) break;
|
|
60
|
+
if (minZoom > threshold) continue;
|
|
61
|
+
const { x, y } = toPixels(longitude, latitude);
|
|
62
|
+
const halfWidth = (name.length * fontPx * CHARACTER_EM) / 2 + GAP_PX / 2;
|
|
63
|
+
const halfHeight = fontPx / 2 + GAP_PX / 4;
|
|
64
|
+
const box: [number, number, number, number] = [x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight];
|
|
65
|
+
if (box[0] < 0 || box[1] < 0 || box[2] > widthPx || box[3] > heightPx) continue;
|
|
66
|
+
if (taken.some((other) => box[0] < other[2] && box[2] > other[0] && box[1] < other[3] && box[3] > other[1])) continue;
|
|
67
|
+
if (obstacles.some((marker) => circleMeetsBox(marker, box))) continue;
|
|
68
|
+
taken.push(box);
|
|
69
|
+
placed.push({ name, longitude, latitude });
|
|
70
|
+
}
|
|
71
|
+
return placed;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function circleMeetsBox(circle: LabelObstacle, box: readonly [number, number, number, number]): boolean {
|
|
75
|
+
const nearestX = Math.max(box[0], Math.min(circle.x, box[2]));
|
|
76
|
+
const nearestY = Math.max(box[1], Math.min(circle.y, box[3]));
|
|
77
|
+
return Math.hypot(circle.x - nearestX, circle.y - nearestY) < circle.radius;
|
|
78
|
+
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "../../../api-client/geo";
|
|
14
14
|
import { chartSeriesProvider, type ChartSeriesCapability, type ChartSeriesCatalogItem } from "../../../capabilities";
|
|
15
15
|
import { subtractTimeRange } from "../../../time-series/date-window";
|
|
16
|
-
import { SERIES_COLORS } from "../../../
|
|
16
|
+
import { SERIES_COLORS } from "../../../theme/series-colors";
|
|
17
17
|
import type { ChartViewportSpec, ResolvedSeries, TimeSeriesPoint } from "../../../time-series/types";
|
|
18
18
|
import { GEO_SERIES_CAPABILITY_ID } from "../chart-composer/series-expression";
|
|
19
19
|
import { cloudGeoRequest } from "./client";
|
|
@@ -357,7 +357,8 @@ export function GeoSvgLayers({ overlay, matrix, project, view, unitPx, mapWidthP
|
|
|
357
357
|
const selectedLayer = overlay.selected ? overlay.layers.find((layer) => layer.id === overlay.selected!.layerId) ?? null : null;
|
|
358
358
|
const selectedFeature = selectedLayer?.features.find((feature) => feature.id === overlay.selected!.id) ?? null;
|
|
359
359
|
const pxPerDegree = matrix.a * unitPx;
|
|
360
|
-
|
|
360
|
+
// A map with no scale draws nothing; never hand the icons a counter-scale of 1/epsilon.
|
|
361
|
+
const iconScale = { [ICON_SCALE_VAR]: pxPerDegree > 0 && Number.isFinite(pxPerDegree) ? 1 / pxPerDegree : 0 } as CSSProperties;
|
|
361
362
|
const trailPath = overlay.trail && overlay.trail.length > 1 ? degreePath([overlay.trail], false) : null;
|
|
362
363
|
const points = new Map(overlay.layers.filter((layer) => layer.geometry === "point").map((layer) => {
|
|
363
364
|
const landmark = layer.features.length > 0 && MAP_SYMBOLS[featureSymbol(layer.id, layer.features[0]!).id].landmark === true;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { peekGeoCatalog } from "../../../api-client/geo";
|
|
2
|
-
import { SERIES_COLORS } from "../../../
|
|
2
|
+
import { SERIES_COLORS } from "../../../theme/series-colors";
|
|
3
3
|
import { CHART_COMPOSER_PANE_ID } from "../../../types/config";
|
|
4
4
|
import type { PaneTemplateDef } from "../../../types/plugin";
|
|
5
5
|
import { chartHeadless } from "../chart-composer/headless";
|
|
@@ -49,7 +49,7 @@ export const worldVenueMapModule: PluginModule = {
|
|
|
49
49
|
panes: [
|
|
50
50
|
{
|
|
51
51
|
id: WORLD_VENUE_MAP_PANE_ID,
|
|
52
|
-
name: "World
|
|
52
|
+
name: "World Map",
|
|
53
53
|
icon: "M",
|
|
54
54
|
component: WorldVenueMapPane,
|
|
55
55
|
defaultPosition: "right",
|
|
@@ -63,9 +63,9 @@ export const worldVenueMapModule: PluginModule = {
|
|
|
63
63
|
{
|
|
64
64
|
id: "world-venue-map-pane",
|
|
65
65
|
paneId: WORLD_VENUE_MAP_PANE_ID,
|
|
66
|
-
label: "World
|
|
67
|
-
description: "
|
|
68
|
-
keywords: ["world", "map", "venue", "venues", "exchange", "mic", "market hours", "open markets", "ships", "vessels", "ports", "chokepoints", "pipelines", "airports", "energy", "layers"],
|
|
66
|
+
label: "World Map",
|
|
67
|
+
description: "A world map with trading venues and their hours; ships, ports, energy and airports as layers.",
|
|
68
|
+
keywords: ["world", "map", "globe", "countries", "venue", "venues", "exchange", "mic", "market hours", "open markets", "ships", "vessels", "ports", "chokepoints", "pipelines", "airports", "energy", "layers"],
|
|
69
69
|
shortcut: {
|
|
70
70
|
prefix: "MAP",
|
|
71
71
|
argPlaceholder: "layers",
|
|
@@ -78,7 +78,7 @@ export const worldVenueMapModule: PluginModule = {
|
|
|
78
78
|
const preset = parseMapPreset(options?.arg ?? options?.values?.layers);
|
|
79
79
|
if (!preset) return { placement: "floating" };
|
|
80
80
|
return {
|
|
81
|
-
title: `Map · ${preset.layers.map(groupTitle).join(", ")}
|
|
81
|
+
title: preset.layers.length ? `Map · ${preset.layers.map(groupTitle).join(", ")}` : "Map · Venues",
|
|
82
82
|
placement: "floating",
|
|
83
83
|
settings: { layers: preset.layers, venues: preset.venues },
|
|
84
84
|
};
|
|
@@ -49,10 +49,13 @@ import type { GeoLayerState } from "./feed";
|
|
|
49
49
|
import { geoLayerColor, type GeoMapLayer, type GeoMapOverlay, type GeoMapSelection } from "./geo-draw";
|
|
50
50
|
import { WORLD_VENUE_MAP_PANE_ID } from "./ids";
|
|
51
51
|
import {
|
|
52
|
+
entityOpenZoom,
|
|
53
|
+
entityTableBbox,
|
|
52
54
|
featureAnchor,
|
|
53
55
|
formatGeoValue,
|
|
54
56
|
formatTickerLinks,
|
|
55
57
|
isChangeColumn,
|
|
58
|
+
LAYERS_SETTING_KEY,
|
|
56
59
|
readVenuesSetting,
|
|
57
60
|
sameGeoView,
|
|
58
61
|
tickerLinkKey,
|
|
@@ -71,6 +74,8 @@ interface LayeredMapViewProps extends PaneProps {
|
|
|
71
74
|
layers: GeoLayerInfo[];
|
|
72
75
|
/** The layer ids or groups the pane was asked for. */
|
|
73
76
|
tokens: readonly string[];
|
|
77
|
+
/** A plain MAP: venues show unless the pane turned them off. */
|
|
78
|
+
plain?: boolean;
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
interface OpenEntity {
|
|
@@ -80,15 +85,9 @@ interface OpenEntity {
|
|
|
80
85
|
|
|
81
86
|
type SortState = { key: string | null; dir: "asc" | "desc" };
|
|
82
87
|
|
|
83
|
-
/**
|
|
84
|
-
* Zoom an opened entity is shown at: 16 times the world (the server's zoom 4,
|
|
85
|
-
* where dense point layers stop clustering) for a ship or a port, wider for a
|
|
86
|
-
* pipeline or a field.
|
|
87
|
-
*/
|
|
88
|
-
function openZoom(layer: GeoLayerInfo): number {
|
|
89
|
-
return layer.geometry === "point" ? 16 : 4;
|
|
90
|
-
}
|
|
91
88
|
const NO_SORT: SortState = { key: null, dir: "asc" };
|
|
89
|
+
/** How long the map rests before a table that follows it asks again. */
|
|
90
|
+
const TABLE_VIEW_SETTLE_MS = 600;
|
|
92
91
|
/** Layers go closer than venues: 64 times the world is the server's zoom 6, where flights and single ports show. */
|
|
93
92
|
const LAYERED_MAX_ZOOM = 64;
|
|
94
93
|
|
|
@@ -165,7 +164,7 @@ const SelectedEntityHeader = memo(function SelectedEntityHeader({
|
|
|
165
164
|
);
|
|
166
165
|
});
|
|
167
166
|
|
|
168
|
-
export function LayeredMapView({ focused, width, height, layers, tokens }: LayeredMapViewProps) {
|
|
167
|
+
export function LayeredMapView({ focused, width, height, layers, tokens, plain = false }: LayeredMapViewProps) {
|
|
169
168
|
const colors = useThemeColors();
|
|
170
169
|
const dark = relativeLuminance(colors.text) > relativeLuminance(colors.bg);
|
|
171
170
|
const { nativePaneChrome } = useUiCapabilities();
|
|
@@ -174,7 +173,7 @@ export function LayeredMapView({ focused, width, height, layers, tokens }: Layer
|
|
|
174
173
|
const { hasProAccess } = usePlanAccess();
|
|
175
174
|
const upgrade = useCloudUpgradeAction("map-layers");
|
|
176
175
|
const [venuesSetting] = usePaneSettingValue<boolean | undefined>(VENUES_SETTING_KEY, undefined);
|
|
177
|
-
const venuesOn = readVenuesSetting({ [VENUES_SETTING_KEY]: venuesSetting }, layers.length);
|
|
176
|
+
const venuesOn = readVenuesSetting(plain ? { [VENUES_SETTING_KEY]: venuesSetting } : { [VENUES_SETTING_KEY]: venuesSetting, [LAYERS_SETTING_KEY]: tokens }, layers.length);
|
|
178
177
|
const { data: venueData } = useAsyncResource(venuesOn ? loadWorldVenues : null);
|
|
179
178
|
const [now, setNow] = useState(Date.now());
|
|
180
179
|
useEffect(() => {
|
|
@@ -214,16 +213,28 @@ export function LayeredMapView({ focused, width, height, layers, tokens }: Layer
|
|
|
214
213
|
const [selectedMic, setSelectedMic] = useState<string | null>(null);
|
|
215
214
|
const [headerSubject, setHeaderSubject] = useState<"entity" | "venue">("entity");
|
|
216
215
|
const [focus, setFocus] = useState<WorldMapFocus | null>(null);
|
|
216
|
+
// Kept per pane, so docking, undocking, popping out or reloading reopens the map where it was.
|
|
217
|
+
const [savedViewport, setSavedViewport] = usePluginPaneState<unknown>("map:viewport", null);
|
|
217
218
|
const { active: searchFocused, focus: focusSearch, searchProps } = useQueryBarSearch();
|
|
218
219
|
const scrollRef = useRef<ScrollBoxRenderable>(null);
|
|
219
220
|
const detailScrollRef = useRef<ScrollBoxRenderable>(null);
|
|
220
221
|
|
|
221
222
|
const tableLocked = !canAccess(tableLayer) || tableState?.phase === "locked";
|
|
222
223
|
const tableReadable = tableLayer.status !== "unavailable" && tableState?.phase !== "unavailable" && !tableLocked;
|
|
224
|
+
// The table follows the map once the view rests, not every frame of a pan.
|
|
225
|
+
const [tableView, setTableView] = useState<GeoView>(view);
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
if (sameGeoView(tableView, view)) return;
|
|
228
|
+
const timer = setTimeout(() => setTableView(view), TABLE_VIEW_SETTLE_MS);
|
|
229
|
+
return () => clearTimeout(timer);
|
|
230
|
+
}, [tableView, view]);
|
|
231
|
+
const tableBbox = entityTableBbox(tableLayer, tableView);
|
|
232
|
+
const tableBboxKey = tableBbox?.join(",") ?? "";
|
|
223
233
|
const loadPage = useCallback(async ({ offset, signal }: PageRequest) => {
|
|
224
234
|
const page = await loadEntityPage(tableLayer.id, {
|
|
225
235
|
q: query,
|
|
226
236
|
...(sort.key ? { sort: sort.key, dir: sort.dir } : {}),
|
|
237
|
+
...(tableBbox ? { bbox: tableBbox } : {}),
|
|
227
238
|
offset,
|
|
228
239
|
}, signal);
|
|
229
240
|
const rows = page.rows ?? [];
|
|
@@ -234,7 +245,7 @@ export function LayeredMapView({ focused, width, height, layers, tokens }: Layer
|
|
|
234
245
|
hasMore: rows.length >= ENTITY_PAGE_SIZE && (total === null || offset + rows.length < total),
|
|
235
246
|
nextOffset: offset + rows.length,
|
|
236
247
|
};
|
|
237
|
-
}, [query, sort.dir, sort.key, tableLayer.id]);
|
|
248
|
+
}, [query, sort.dir, sort.key, tableBboxKey, tableLayer.id]);
|
|
238
249
|
const pages = usePagedRows(tableReadable ? loadPage : null, { getId: (row) => row.id });
|
|
239
250
|
const rows = pages.rows;
|
|
240
251
|
useWorldMapEvidence(tokens, states, rows.length, pages.loading);
|
|
@@ -301,7 +312,7 @@ export function LayeredMapView({ focused, width, height, layers, tokens }: Layer
|
|
|
301
312
|
const lats = trail.map(([, lat]) => lat);
|
|
302
313
|
const span = Math.max(Math.max(...lons) - Math.min(...lons), (Math.max(...lats) - Math.min(...lats)) * 2, 0.05);
|
|
303
314
|
const last = trail.at(-1)!;
|
|
304
|
-
setFocus({ key: trailKey, longitude: last[0], latitude: last[1], zoom: Math.min(LAYERED_MAX_ZOOM, Math.max(
|
|
315
|
+
setFocus({ key: trailKey, longitude: last[0], latitude: last[1], zoom: Math.min(LAYERED_MAX_ZOOM, Math.max(entityOpenZoom(tableLayer), 360 / (span * 3))) });
|
|
305
316
|
}, [trailKey]);
|
|
306
317
|
const overlay = useMemo<GeoMapOverlay>(() => ({ layers: mapLayers, selected: selection, trail }), [mapLayers, selection, trail]);
|
|
307
318
|
|
|
@@ -472,7 +483,7 @@ export function LayeredMapView({ focused, width, height, layers, tokens }: Layer
|
|
|
472
483
|
setHeaderSubject("entity");
|
|
473
484
|
selectRow(row.id);
|
|
474
485
|
setOpen({ layer: tableLayer.id, id: row.id });
|
|
475
|
-
setFocus({ key: `open:${tableLayer.id}:${row.id}`, longitude: row.lon, latitude: row.lat, zoom:
|
|
486
|
+
setFocus({ key: `open:${tableLayer.id}:${row.id}`, longitude: row.lon, latitude: row.lat, zoom: entityOpenZoom(tableLayer) });
|
|
476
487
|
}}
|
|
477
488
|
detailOpen={detailOpen}
|
|
478
489
|
onBack={() => setOpen(null)}
|
|
@@ -497,7 +508,7 @@ export function LayeredMapView({ focused, width, height, layers, tokens }: Layer
|
|
|
497
508
|
);
|
|
498
509
|
|
|
499
510
|
const map = (
|
|
500
|
-
<Box flexDirection="column" width={mapWidth} height={mapSectionHeight} overflow="hidden">
|
|
511
|
+
<Box key="map" flexDirection="column" width={mapWidth} height={mapSectionHeight} overflow="hidden">
|
|
501
512
|
{headerSubject === "venue" && selectedVenue && venueData ? (
|
|
502
513
|
<SelectedVenueHeader venue={selectedVenue} checkedAt={venueData.checkedAt} now={now} width={mapWidth} />
|
|
503
514
|
) : (
|
|
@@ -517,6 +528,8 @@ export function LayeredMapView({ focused, width, height, layers, tokens }: Layer
|
|
|
517
528
|
onSelectGeo={onSelectGeo}
|
|
518
529
|
onViewChange={onViewChange}
|
|
519
530
|
focus={focus}
|
|
531
|
+
savedViewport={savedViewport}
|
|
532
|
+
onViewportSettled={setSavedViewport}
|
|
520
533
|
/>
|
|
521
534
|
</Box>
|
|
522
535
|
);
|