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
|
@@ -75,6 +75,33 @@ function capLayerSelection(ordered: readonly GeoLayerInfo[]): GeoLayerInfo[] {
|
|
|
75
75
|
return kept;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/** Layers a plain map opens with: the server's default layers that can serve, two at most. */
|
|
79
|
+
const MAX_DEFAULT_LAYERS = 2;
|
|
80
|
+
|
|
81
|
+
function defaultLayerIds(layers: readonly GeoLayerInfo[] | null | undefined): string[] {
|
|
82
|
+
if (!layers?.length) return [];
|
|
83
|
+
const defaults = layers.filter((layer) => layer.defaultVisible && layer.status !== "unavailable");
|
|
84
|
+
return capLayerSelection(defaults.slice(0, MAX_DEFAULT_LAYERS)).map((layer) => layer.id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A pane that never chose its layers or venues: a plain `MAP`, or a map
|
|
89
|
+
* saved before layers existed. It shows venues and the server's default
|
|
90
|
+
* layers. A preset or an edit in the layer picker writes both settings, and
|
|
91
|
+
* from then on the pane keeps exactly what was chosen.
|
|
92
|
+
*/
|
|
93
|
+
export function isPlainMap(settings: Record<string, unknown> | undefined): boolean {
|
|
94
|
+
return settings?.[LAYERS_SETTING_KEY] == null && typeof settings?.[VENUES_SETTING_KEY] !== "boolean";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The layer ids or groups a pane shows: what it chose, or the defaults on a plain map. */
|
|
98
|
+
export function activeLayerTokens(
|
|
99
|
+
settings: Record<string, unknown> | undefined,
|
|
100
|
+
layers: readonly GeoLayerInfo[] | null | undefined,
|
|
101
|
+
): string[] {
|
|
102
|
+
return isPlainMap(settings) ? defaultLayerIds(layers) : parseLayerTokens(settings?.[LAYERS_SETTING_KEY]);
|
|
103
|
+
}
|
|
104
|
+
|
|
78
105
|
/** Expands groups to their layers, drops what the server does not list, and applies the caps. */
|
|
79
106
|
export function resolveActiveLayers(tokens: readonly string[], layers: readonly GeoLayerInfo[] | null | undefined): GeoLayerInfo[] {
|
|
80
107
|
if (!layers?.length || !tokens.length) return [];
|
|
@@ -98,18 +125,23 @@ export interface MapPreset {
|
|
|
98
125
|
venues: boolean;
|
|
99
126
|
}
|
|
100
127
|
|
|
101
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* `MAP ships` and friends: the typed words become layer tokens, resolved once
|
|
130
|
+
* the catalog is known. `MAP venues` alone is the venue map without layers.
|
|
131
|
+
*/
|
|
102
132
|
export function parseMapPreset(arg: string | null | undefined): MapPreset | null {
|
|
103
133
|
const tokens = parseLayerTokens(arg ?? "");
|
|
104
|
-
|
|
105
|
-
return { layers:
|
|
134
|
+
const venues = /\bvenues\b/i.test(arg ?? "");
|
|
135
|
+
if (!tokens.length) return venues ? { layers: [], venues: true } : null;
|
|
136
|
+
return { layers: tokens, venues };
|
|
106
137
|
}
|
|
107
138
|
|
|
108
139
|
export const MAP_PRESET_OPTIONS = [
|
|
140
|
+
{ value: "venues", label: "Trading venues only" },
|
|
109
141
|
{ value: "ships", label: "Ships and chokepoints" },
|
|
110
142
|
{ value: "ports", label: "Ports" },
|
|
111
143
|
{ value: "energy", label: "Pipelines, fields and terminals" },
|
|
112
|
-
{ value: "air", label: "Airports" },
|
|
144
|
+
{ value: "air", label: "Airports and flights" },
|
|
113
145
|
] as const;
|
|
114
146
|
|
|
115
147
|
function shortDate(iso: string): string | null {
|
|
@@ -152,8 +184,8 @@ function groupsOf(layers: readonly GeoLayerInfo[]): string[] {
|
|
|
152
184
|
export function readVenuesSetting(settings: Record<string, unknown> | undefined, activeLayers: number): boolean {
|
|
153
185
|
const value = settings?.[VENUES_SETTING_KEY];
|
|
154
186
|
if (typeof value === "boolean") return value;
|
|
155
|
-
// A
|
|
156
|
-
return activeLayers === 0;
|
|
187
|
+
// A plain MAP shows venues under its default layers; a preset opens on its layers alone.
|
|
188
|
+
return settings?.[LAYERS_SETTING_KEY] == null || activeLayers === 0;
|
|
157
189
|
}
|
|
158
190
|
|
|
159
191
|
/**
|
|
@@ -166,7 +198,7 @@ export function buildMapSettingsDef(
|
|
|
166
198
|
layers: readonly GeoLayerInfo[] | null | undefined,
|
|
167
199
|
): PaneSettingsDef | undefined {
|
|
168
200
|
if (!layers?.length) return undefined;
|
|
169
|
-
const active = resolveActiveLayers(
|
|
201
|
+
const active = resolveActiveLayers(activeLayerTokens(settings, layers), layers);
|
|
170
202
|
const values: Record<string, unknown> = { [VENUES_SETTING_KEY]: readVenuesSetting(settings, active.length) };
|
|
171
203
|
const fields: PaneSettingField[] = [{
|
|
172
204
|
key: VENUES_SETTING_KEY,
|
|
@@ -199,12 +231,16 @@ export function applyMapSetting(
|
|
|
199
231
|
value: unknown,
|
|
200
232
|
layers: readonly GeoLayerInfo[],
|
|
201
233
|
): Record<string, unknown> {
|
|
202
|
-
|
|
203
|
-
|
|
234
|
+
const before = resolveActiveLayers(activeLayerTokens(settings, layers), layers);
|
|
235
|
+
// The first edit of a plain map writes down what it showed, so only the edited part changes.
|
|
236
|
+
const chosen = isPlainMap(settings)
|
|
237
|
+
? { ...settings, [LAYERS_SETTING_KEY]: before.map((layer) => layer.id), [VENUES_SETTING_KEY]: readVenuesSetting(settings, before.length) }
|
|
238
|
+
: settings;
|
|
239
|
+
if (key === VENUES_SETTING_KEY) return { ...chosen, [VENUES_SETTING_KEY]: value === true };
|
|
240
|
+
if (!key.startsWith(GROUP_PREFIX)) return { ...chosen, [key]: value };
|
|
204
241
|
const group = key.slice(GROUP_PREFIX.length);
|
|
205
242
|
const members = new Set(layers.filter((layer) => String(layer.group || "other") === group).map((layer) => layer.id));
|
|
206
243
|
const picked = Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string" && members.has(entry)) : [];
|
|
207
|
-
const before = resolveActiveLayers(parseLayerTokens(settings[LAYERS_SETTING_KEY]), layers);
|
|
208
244
|
// Other groups keep their order; this group's picks follow, newest last, so the caps drop the oldest.
|
|
209
245
|
const kept = before.filter((layer) => !members.has(layer.id) || picked.includes(layer.id));
|
|
210
246
|
const added = picked.filter((id) => !kept.some((layer) => layer.id === id))
|
|
@@ -253,6 +289,28 @@ export function geoViewForViewport(
|
|
|
253
289
|
};
|
|
254
290
|
}
|
|
255
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Zoom an opened entity is shown at: 16 times the world (the server's zoom 4,
|
|
294
|
+
* where dense point layers stop clustering) for a ship or a port, wider for a
|
|
295
|
+
* pipeline or a field, and never short of the zoom its layer draws from
|
|
296
|
+
* (flights from the server's zoom 5, 32 times the world).
|
|
297
|
+
*/
|
|
298
|
+
export function entityOpenZoom(layer: Pick<GeoLayerInfo, "geometry" | "minZoom">): number {
|
|
299
|
+
const base = layer.geometry === "point" ? 16 : 4;
|
|
300
|
+
return layer.minZoom === undefined ? base : Math.max(base, 2 ** layer.minZoom);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* The area a layer's table reads. A layer drawn only from a zoom (live
|
|
305
|
+
* flights) lists what the map shows once it is that close; further out, and
|
|
306
|
+
* for every other layer, the table asks without one and the server answers
|
|
307
|
+
* for the whole layer or, for flights, one busy area.
|
|
308
|
+
*/
|
|
309
|
+
export function entityTableBbox(layer: Pick<GeoLayerInfo, "minZoom">, view: GeoView): GeoBbox | undefined {
|
|
310
|
+
if (layer.minZoom === undefined || view.zoom < layer.minZoom) return undefined;
|
|
311
|
+
return view.bbox;
|
|
312
|
+
}
|
|
313
|
+
|
|
256
314
|
export function geoViewKey(view: GeoView): string {
|
|
257
315
|
return `${view.bbox.join(",")}@${Math.floor(view.zoom)}`;
|
|
258
316
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
MAX_WORLD_MAP_ZOOM,
|
|
17
17
|
panWorldMapViewport,
|
|
18
18
|
projectWorldPoint,
|
|
19
|
+
savedWorldMapViewport,
|
|
19
20
|
unprojectWorldPoint,
|
|
20
21
|
zoomWorldMapViewport,
|
|
21
22
|
type WorldMapPoint,
|
|
@@ -26,7 +27,8 @@ import { WORLD_OUTLINES } from "./world-outlines";
|
|
|
26
27
|
import { drawGeoBitmap, hitTestGeo, rasterGeoCells, type GeoHit, type GeoMapOverlay } from "./geo-draw";
|
|
27
28
|
import { GeoSvgLayers, MapIconSwatch, toneColor, type MapMatrix, type SymbolTones } from "./geo-svg";
|
|
28
29
|
import { BasemapLayer, type BasemapPalette } from "./basemap-layer";
|
|
29
|
-
import type
|
|
30
|
+
import { loadCountryLabels, type CountryLabel, type GeoBox } from "./basemap";
|
|
31
|
+
import { COUNTRY_LABEL_FONT_PX, layoutCountryLabels, type LabelObstacle, type PlacedCountryLabel } from "./country-labels";
|
|
30
32
|
import { layerSymbol, shipClass, usesClassTones, type MapSymbolId, type SymbolTone } from "./map-symbols";
|
|
31
33
|
import { geoViewForViewport, sameGeoView, WORLD_GEO_VIEW, type GeoView } from "./layers";
|
|
32
34
|
|
|
@@ -52,6 +54,13 @@ interface WorldVenueMapProps {
|
|
|
52
54
|
focus?: WorldMapFocus | null;
|
|
53
55
|
/** How far the map may zoom; geo layers go closer than venues. */
|
|
54
56
|
maxZoom?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Where the map was when this pane last drew it (saved pane state). A pane
|
|
59
|
+
* docked, undocked, popped out or reloaded mounts a new map; it opens here.
|
|
60
|
+
*/
|
|
61
|
+
savedViewport?: unknown;
|
|
62
|
+
/** The view once it has rested for a moment, for the pane to keep. */
|
|
63
|
+
onViewportSettled?: (viewport: WorldMapViewport) => void;
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
function clusterVenue(cluster: WorldVenueCluster, selectedMic: string | null): CloudWorldVenuePayload {
|
|
@@ -306,6 +315,75 @@ const MAP_CLICK_HIT_PX = 14;
|
|
|
306
315
|
const MAP_DOUBLE_CLICK_ZOOM = 1.8;
|
|
307
316
|
/** Share of the plot a selected venue may sit inside before the map pans to it. */
|
|
308
317
|
const MAP_FOLLOW_MARGIN = 0.06;
|
|
318
|
+
/** How long the view rests before the pane keeps it. */
|
|
319
|
+
const VIEWPORT_SETTLE_MS = 400;
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Whether a map of this many cells can be drawn. A pane mid-layout (a new
|
|
323
|
+
* window, a dock move) can hand the map a width of one cell for a frame; a
|
|
324
|
+
* projection from that has no scale, and a focus applied to it loses its
|
|
325
|
+
* centre.
|
|
326
|
+
*/
|
|
327
|
+
function mapHasArea(width: number, height: number): boolean {
|
|
328
|
+
return width > 2 && height > 1;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** A venue dot's radius in view units, larger for a cluster of venues. */
|
|
332
|
+
function venueDotRadius(count: number): number {
|
|
333
|
+
return Math.max(0.55, Math.min(1.5, 0.45 + Math.sqrt(count) * 0.22));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Country names are laid out again once the view has rested this long, not on every drag frame. */
|
|
337
|
+
const LABEL_SETTLE_MS = 150;
|
|
338
|
+
/** Room a selected or hovered marker keeps clear of country names, in pixels. */
|
|
339
|
+
const LABEL_MARKER_CLEARANCE_PX = 14;
|
|
340
|
+
|
|
341
|
+
/** The country names, from their lazy chunk; none until it arrives, none in a build without map data. */
|
|
342
|
+
function useCountryLabels(): readonly CountryLabel[] | null {
|
|
343
|
+
const [labels, setLabels] = useState<readonly CountryLabel[] | null>(null);
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
let current = true;
|
|
346
|
+
loadCountryLabels().then((loaded) => {
|
|
347
|
+
if (current) setLabels(loaded);
|
|
348
|
+
}, () => {});
|
|
349
|
+
return () => {
|
|
350
|
+
current = false;
|
|
351
|
+
};
|
|
352
|
+
}, []);
|
|
353
|
+
return labels;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Country names under the markers: small, quiet, never in the way of a click. */
|
|
357
|
+
function CountryNames({ labels, project, unitPx, color, halo }: {
|
|
358
|
+
labels: readonly PlacedCountryLabel[];
|
|
359
|
+
project: (longitude: number, latitude: number) => WorldMapPoint;
|
|
360
|
+
unitPx: number;
|
|
361
|
+
color: string;
|
|
362
|
+
halo: string;
|
|
363
|
+
}) {
|
|
364
|
+
if (!labels.length) return null;
|
|
365
|
+
const fontSize = COUNTRY_LABEL_FONT_PX / unitPx;
|
|
366
|
+
return createElement(
|
|
367
|
+
"g",
|
|
368
|
+
{
|
|
369
|
+
"data-gloom-role": "country-labels",
|
|
370
|
+
fill: color,
|
|
371
|
+
stroke: halo,
|
|
372
|
+
strokeWidth: 3 / unitPx,
|
|
373
|
+
strokeOpacity: 0.8,
|
|
374
|
+
strokeLinejoin: "round",
|
|
375
|
+
paintOrder: "stroke",
|
|
376
|
+
fontSize,
|
|
377
|
+
fontFamily: "inherit",
|
|
378
|
+
textAnchor: "middle",
|
|
379
|
+
pointerEvents: "none",
|
|
380
|
+
},
|
|
381
|
+
...labels.map((label) => {
|
|
382
|
+
const at = project(label.longitude, label.latitude);
|
|
383
|
+
return createElement("text", { key: label.name, x: at.x, y: at.y, dy: "0.35em" }, label.name);
|
|
384
|
+
}),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
309
387
|
|
|
310
388
|
function wheelZoomFactor(event: WheelEvent): number {
|
|
311
389
|
const delta = event.deltaMode === 1 ? event.deltaY * 16 : event.deltaY;
|
|
@@ -386,7 +464,10 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
|
|
|
386
464
|
const maxZoom = props.maxZoom ?? MAX_WORLD_MAP_ZOOM;
|
|
387
465
|
const surfaceRef = useRef<HTMLDivElement | null>(null);
|
|
388
466
|
const surfaceSize = useElementSize(surfaceRef);
|
|
389
|
-
const [viewport, setViewport] = useState<WorldMapViewport>(
|
|
467
|
+
const [viewport, setViewport] = useState<WorldMapViewport>(
|
|
468
|
+
() => savedWorldMapViewport(props.savedViewport, maxZoom) ?? DEFAULT_WORLD_MAP_VIEWPORT,
|
|
469
|
+
);
|
|
470
|
+
const sized = mapHasArea(props.width, props.height);
|
|
390
471
|
const viewportRef = useRef(viewport);
|
|
391
472
|
viewportRef.current = viewport;
|
|
392
473
|
const [dragging, setDragging] = useState(false);
|
|
@@ -431,6 +512,7 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
|
|
|
431
512
|
land: blendHex(colors.bg, colors.text, dark ? 0.1 : 0.07),
|
|
432
513
|
coast: blendHex(colors.bg, colors.textDim, dark ? 0.85 : 0.75),
|
|
433
514
|
border: blendHex(colors.bg, colors.textDim, dark ? 0.42 : 0.35),
|
|
515
|
+
label: blendHex(colors.bg, colors.textDim, dark ? 0.7 : 0.8),
|
|
434
516
|
}), [colors.bg, colors.text, colors.textDim, dark]);
|
|
435
517
|
// Five colours in all: the layer's own for cargo, amber tankers, bright
|
|
436
518
|
// passenger ships, green fishing boats, and one quiet grey for the rest.
|
|
@@ -439,6 +521,42 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
|
|
|
439
521
|
return { tanker: colors.warning, passenger: colors.textBright, fishing: colors.positive, service: quiet, other: quiet };
|
|
440
522
|
}, [colors.positive, colors.text, colors.textBright, colors.textDim, colors.warning]);
|
|
441
523
|
|
|
524
|
+
// Names are placed for the view once it rests; a drag moves the ones already placed.
|
|
525
|
+
const countryLabels = useCountryLabels();
|
|
526
|
+
const [labelViewport, setLabelViewport] = useState(viewport);
|
|
527
|
+
useEffect(() => {
|
|
528
|
+
if (labelViewport === viewport) return;
|
|
529
|
+
const timer = setTimeout(() => setLabelViewport(viewport), LABEL_SETTLE_MS);
|
|
530
|
+
return () => clearTimeout(timer);
|
|
531
|
+
}, [labelViewport, viewport]);
|
|
532
|
+
const selectedPoint = props.overlay?.selected ?? null;
|
|
533
|
+
const selectedVenuePoint = props.venues.find((venue) => venue.mic === props.selectedMic) ?? null;
|
|
534
|
+
const hoverAnchor = hover?.hit.kind === "feature" && hover.hit.feature.geometry.type === "Point" ? hover.hit.feature.geometry.coordinates : null;
|
|
535
|
+
const placedLabels = useMemo(() => {
|
|
536
|
+
if (!countryLabels?.length) return [];
|
|
537
|
+
const at = (longitude: number, latitude: number) => {
|
|
538
|
+
const point = projectWorldPoint(longitude, latitude, props.width, plotHeight, 1, labelViewport);
|
|
539
|
+
return { x: point.x * unitPx, y: point.y * unitPx };
|
|
540
|
+
};
|
|
541
|
+
const markers = [
|
|
542
|
+
selectedPoint ? [selectedPoint.longitude, selectedPoint.latitude] : null,
|
|
543
|
+
selectedVenuePoint ? [selectedVenuePoint.longitude, selectedVenuePoint.latitude] : null,
|
|
544
|
+
hoverAnchor,
|
|
545
|
+
].filter((point): point is [number, number] => point !== null);
|
|
546
|
+
const obstacles: LabelObstacle[] = markers.map(([longitude, latitude]) => ({ ...at(longitude, latitude), radius: LABEL_MARKER_CLEARANCE_PX }));
|
|
547
|
+
// Venue dots are few and sit on land, where the names are: a name never runs under one.
|
|
548
|
+
for (const cluster of clusterWorldVenues(props.venues, props.width, plotHeight, 1, labelViewport)) {
|
|
549
|
+
obstacles.push({ x: cluster.x * unitPx, y: cluster.y * unitPx, radius: (venueDotRadius(cluster.venues.length) + 0.2) * unitPx });
|
|
550
|
+
}
|
|
551
|
+
return layoutCountryLabels(countryLabels, {
|
|
552
|
+
pxPerDegree: (at(1, 0).x - at(0, 0).x),
|
|
553
|
+
toPixels: at,
|
|
554
|
+
widthPx: props.width * unitPx,
|
|
555
|
+
heightPx: plotHeight * unitPx,
|
|
556
|
+
obstacles,
|
|
557
|
+
});
|
|
558
|
+
}, [countryLabels, hoverAnchor?.[0], hoverAnchor?.[1], labelViewport, plotHeight, props.venues, props.width, selectedPoint?.latitude, selectedPoint?.longitude, selectedVenuePoint, unitPx]);
|
|
559
|
+
|
|
442
560
|
const hitRadius = useCallback((element: HTMLElement) => {
|
|
443
561
|
const rect = element.getBoundingClientRect();
|
|
444
562
|
return rect.height <= 0 ? 2.4 : (MAP_CLICK_HIT_PX / rect.height) * plotHeight;
|
|
@@ -464,24 +582,39 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
|
|
|
464
582
|
const onViewChange = props.onViewChange;
|
|
465
583
|
const reportedViewRef = useRef<GeoView | null>(null);
|
|
466
584
|
useEffect(() => {
|
|
467
|
-
if (!onViewChange) return;
|
|
585
|
+
if (!onViewChange || !sized) return;
|
|
468
586
|
const next = geoViewForViewport(viewport, props.width, plotHeight);
|
|
469
587
|
if (reportedViewRef.current && sameGeoView(reportedViewRef.current, next)) return;
|
|
470
588
|
reportedViewRef.current = next;
|
|
471
589
|
onViewChange(next);
|
|
472
|
-
}, [onViewChange, plotHeight, props.width, viewport]);
|
|
590
|
+
}, [onViewChange, plotHeight, props.width, sized, viewport]);
|
|
473
591
|
|
|
474
|
-
//
|
|
592
|
+
// The pane keeps the view once it rests, so a remount opens where this left off.
|
|
593
|
+
const onViewportSettled = props.onViewportSettled;
|
|
594
|
+
const settledViewportRef = useRef(viewport);
|
|
595
|
+
useEffect(() => {
|
|
596
|
+
if (!onViewportSettled || settledViewportRef.current === viewport) return;
|
|
597
|
+
const timer = setTimeout(() => {
|
|
598
|
+
settledViewportRef.current = viewport;
|
|
599
|
+
onViewportSettled(viewport);
|
|
600
|
+
}, VIEWPORT_SETTLE_MS);
|
|
601
|
+
return () => clearTimeout(timer);
|
|
602
|
+
}, [onViewportSettled, viewport]);
|
|
603
|
+
|
|
604
|
+
// A new focus (an entity picked in the table) centres the map on it, once
|
|
605
|
+
// the map has an area to centre it in.
|
|
475
606
|
const focusKey = props.focus?.key ?? null;
|
|
607
|
+
const appliedFocusRef = useRef<string | null>(null);
|
|
476
608
|
useEffect(() => {
|
|
477
609
|
const focus = props.focus;
|
|
478
|
-
if (!focus) return;
|
|
610
|
+
if (!focus || !sized || appliedFocusRef.current === focus.key) return;
|
|
611
|
+
appliedFocusRef.current = focus.key;
|
|
479
612
|
setViewport((current) => {
|
|
480
613
|
const zoom = Math.max(current.zoom, focus.zoom ?? 1);
|
|
481
614
|
if (zoom <= 1) return current;
|
|
482
615
|
return clampWorldMapViewport({ zoom, centerLongitude: focus.longitude, centerLatitude: focus.latitude }, props.width, plotHeight, 1, maxZoom);
|
|
483
616
|
});
|
|
484
|
-
}, [focusKey]);
|
|
617
|
+
}, [focusKey, sized]);
|
|
485
618
|
|
|
486
619
|
const selectedVenue = props.venues.find((venue) => venue.mic === props.selectedMic) ?? null;
|
|
487
620
|
const selectedVenueRef = useRef(selectedVenue);
|
|
@@ -637,60 +770,63 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
|
|
|
637
770
|
background: colors.bg,
|
|
638
771
|
}}
|
|
639
772
|
>
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
<
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
<
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
773
|
+
{sized ? (
|
|
774
|
+
<svg
|
|
775
|
+
viewBox={`0 0 ${props.width} ${plotHeight}`}
|
|
776
|
+
width="100%"
|
|
777
|
+
height="100%"
|
|
778
|
+
aria-hidden="true"
|
|
779
|
+
style={{ display: "block", background: colors.bg, pointerEvents: "none" }}
|
|
780
|
+
>
|
|
781
|
+
<BasemapLayer matrix={matrix} view={view} pxPerDegree={pxPerDegree} palette={palette} />
|
|
782
|
+
<CountryNames labels={placedLabels} project={project} unitPx={unitPx} color={palette.label} halo={colors.bg} />
|
|
783
|
+
{props.overlay ? (
|
|
784
|
+
<GeoSvgLayers
|
|
785
|
+
overlay={props.overlay}
|
|
786
|
+
matrix={matrix}
|
|
787
|
+
project={project}
|
|
788
|
+
view={view}
|
|
789
|
+
unitPx={unitPx}
|
|
790
|
+
mapWidthPx={surfaceSize?.width ?? props.width * 8}
|
|
791
|
+
background={colors.bg}
|
|
792
|
+
selectedColor={colors.selectedText}
|
|
793
|
+
textColor={colors.textBright}
|
|
794
|
+
tones={tones}
|
|
795
|
+
hovered={hovered?.hit ?? null}
|
|
796
|
+
/>
|
|
797
|
+
) : null}
|
|
798
|
+
{clusters.map((cluster) => {
|
|
799
|
+
const selected = isSelectedCluster(cluster, props.selectedMic);
|
|
800
|
+
const venue = clusterVenue(cluster, props.selectedMic);
|
|
801
|
+
const radius = venueDotRadius(cluster.venues.length);
|
|
802
|
+
return (
|
|
803
|
+
<g key={cluster.id}>
|
|
804
|
+
<title>{`${cluster.venues.length} venue${cluster.venues.length === 1 ? "" : "s"} near ${venue.city}: ${cluster.venues.map((item) => `${item.mic} ${item.name}`).join(", ")}`}</title>
|
|
805
|
+
<circle
|
|
806
|
+
cx={cluster.x}
|
|
807
|
+
cy={cluster.y}
|
|
808
|
+
r={radius + (selected ? 0.24 : 0)}
|
|
809
|
+
fill={cluster.isOpen ? colors.positive : colors.textMuted}
|
|
810
|
+
stroke={selected ? colors.selectedText : colors.bg}
|
|
811
|
+
strokeWidth={selected ? 0.35 : 0.16}
|
|
812
|
+
vectorEffect="non-scaling-stroke"
|
|
813
|
+
/>
|
|
814
|
+
{cluster.venues.length > 1 ? createElement("text", {
|
|
815
|
+
x: cluster.x,
|
|
816
|
+
y: cluster.y,
|
|
817
|
+
fill: colors.bg,
|
|
818
|
+
dy: "0.34em",
|
|
819
|
+
fontSize: Math.max(0.62, Math.min(0.95, radius * 0.78)),
|
|
820
|
+
fontWeight: "700",
|
|
821
|
+
fontFamily: "inherit",
|
|
822
|
+
textAnchor: "middle",
|
|
823
|
+
pointerEvents: "none",
|
|
824
|
+
}, cluster.venues.length) : null}
|
|
825
|
+
</g>
|
|
826
|
+
);
|
|
827
|
+
})}
|
|
828
|
+
</svg>
|
|
829
|
+
) : null}
|
|
694
830
|
{tooltip && hovered ? (
|
|
695
831
|
<MapTooltip
|
|
696
832
|
title={tooltip.title}
|
|
@@ -700,7 +836,7 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
|
|
|
700
836
|
width={surfaceSize?.width ?? 0}
|
|
701
837
|
/>
|
|
702
838
|
) : null}
|
|
703
|
-
{props.overlay ? <GeoLegend overlay={props.overlay} venues={props.venues.length > 0} tones={tones} /> : null}
|
|
839
|
+
{props.overlay && sized ? <GeoLegend overlay={props.overlay} venues={props.venues.length > 0} tones={tones} /> : null}
|
|
704
840
|
</div>
|
|
705
841
|
</Box>
|
|
706
842
|
);
|
|
@@ -97,6 +97,9 @@ export function clampWorldMapViewport(
|
|
|
97
97
|
if (zoom <= 1) return DEFAULT_WORLD_MAP_VIEWPORT;
|
|
98
98
|
|
|
99
99
|
const { availableWidth, effectiveHeight, latitudeSpan } = mapExtent(width, height, yUnitAspect);
|
|
100
|
+
// A plot with no area yet (a pane mid-layout) says nothing about what fits:
|
|
101
|
+
// keep the centre rather than resetting it to the middle of the world.
|
|
102
|
+
if (availableWidth <= 0 || effectiveHeight <= 0) return { ...viewport, zoom };
|
|
100
103
|
const scale = fittedWorldMapScale(width, height, yUnitAspect) * zoom;
|
|
101
104
|
const halfLongitude = availableWidth / (2 * scale);
|
|
102
105
|
const halfLatitude = effectiveHeight / (2 * scale);
|
|
@@ -116,6 +119,19 @@ export function clampWorldMapViewport(
|
|
|
116
119
|
};
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
/** A viewport read back from saved pane state, or null when it is not one. */
|
|
123
|
+
export function savedWorldMapViewport(value: unknown, maxZoom = MAX_WORLD_MAP_ZOOM): WorldMapViewport | null {
|
|
124
|
+
if (!value || typeof value !== "object") return null;
|
|
125
|
+
const { zoom, centerLongitude, centerLatitude } = value as Partial<WorldMapViewport>;
|
|
126
|
+
if (![zoom, centerLongitude, centerLatitude].every((part) => typeof part === "number" && Number.isFinite(part))) return null;
|
|
127
|
+
if (zoom! <= 1) return DEFAULT_WORLD_MAP_VIEWPORT;
|
|
128
|
+
return {
|
|
129
|
+
zoom: Math.min(zoom!, maxZoom),
|
|
130
|
+
centerLongitude: clamp(centerLongitude!, -180, 180),
|
|
131
|
+
centerLatitude: clamp(centerLatitude!, MIN_LATITUDE, MAX_LATITUDE),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
119
135
|
export function zoomWorldMapViewport(
|
|
120
136
|
viewport: WorldMapViewport,
|
|
121
137
|
width: number,
|
|
@@ -4,6 +4,9 @@ import type { DetailTile } from "../basemap";
|
|
|
4
4
|
/** @knipignore Loaded through the map's data specifier, which only view builds define (see ../basemap.ts). */
|
|
5
5
|
export const loadWorld = () => import("./world-50m.chunk.js");
|
|
6
6
|
|
|
7
|
+
/** @knipignore Loaded through the map's data specifier, which only view builds define (see ../basemap.ts). */
|
|
8
|
+
export const loadLabels = () => import("./labels.chunk.js");
|
|
9
|
+
|
|
7
10
|
export const DETAIL_TILES: readonly DetailTile[] = [
|
|
8
11
|
{ id: "w090s60", box: [-90.005, -60, -74.995, -44.995], load: () => import("./tiles/w090s60.chunk.js") },
|
|
9
12
|
{ id: "w075s60", box: [-75.005, -60, -59.995, -44.995], load: () => import("./tiles/w075s60.chunk.js") },
|