@gloom-sh/terminal 0.19.1 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +58 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +6 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +49 -6
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +6 -4
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ/detail-view.tsx +16 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +19 -3
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/client.ts +4 -6
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +64 -42
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +5 -2
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/electrobun/view/ui-host.tsx +9 -2
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/time-series/studies.ts +0 -1628
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* search, the plugin directory and chat suggestions, and its two screener
|
|
4
4
|
* fields (ATTN HRS, ATTN Z) in the EQS field and metric pickers.
|
|
5
5
|
*
|
|
6
|
-
* Research attention publishes an hour only once enough
|
|
6
|
+
* Research attention publishes an hour only once enough contributing users
|
|
7
7
|
* researched in it, and it has published nothing for weeks, so ATTN stays
|
|
8
8
|
* unlisted until it does. Typing ATTN still opens it, a screen that already
|
|
9
9
|
* uses its fields keeps them, and the Attention Counts setting and its
|
|
@@ -14,7 +14,7 @@ export const ATTENTION_SERIES: readonly SeriesDefinition[] = [{
|
|
|
14
14
|
frequency: "intraday",
|
|
15
15
|
group: "Alternative data",
|
|
16
16
|
tags: ["attention", "research", "trending", "researcher-hours"],
|
|
17
|
-
description: "Rounded researcher-hours
|
|
17
|
+
description: "Rounded researcher-hours Gloom users spent on the ticker in each published hour, as ATTN charts them; an hour below the privacy threshold is a gap. Needs Pro.",
|
|
18
18
|
input: "ticker",
|
|
19
19
|
load: async ({ ticker }) => {
|
|
20
20
|
const data = await fetchAttention("now", ticker!);
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import type { PluginModule } from "../plugin-module";
|
|
2
2
|
import { awardDetailCache, awardsCache } from "./client";
|
|
3
3
|
import { awardsHeadless } from "./headless";
|
|
4
|
-
import { AwardsPane } from "./pane";
|
|
5
4
|
import { AWARD_SERIES } from "./series";
|
|
6
5
|
|
|
7
6
|
export const awardsModule: PluginModule = {
|
|
8
7
|
series: AWARD_SERIES,
|
|
9
8
|
setup(ctx) { awardsCache.attach(ctx.persistence); awardDetailCache.attach(ctx.persistence); },
|
|
10
9
|
dispose() { awardsCache.reset(); awardDetailCache.reset(); },
|
|
11
|
-
panes: [{ id: "awards", name: "Government Awards", icon: "A", component: AwardsPane, defaultPosition: "right", defaultMode: "floating",
|
|
10
|
+
panes: [{ id: "awards", name: "Government Awards", icon: "A", get component(): typeof import("./pane").AwardsPane { const { AwardsPane } = require("./pane"); return AwardsPane; }, defaultPosition: "right", defaultMode: "floating",
|
|
12
11
|
defaultFloatingSize: { width: 132, height: 34 }, tickerFollower: true, instruments: ["equity"], tableExport: true, headless: awardsHeadless,
|
|
13
12
|
quickSettings: [{ type: "toggle", key: "awardAlerts", icon: "zap", label: "New awards above 1% of annual revenue" }],
|
|
14
13
|
settings: { title: "Government awards", fields: [{ type: "toggle", key: "awardAlerts", label: "New awards above 1% of annual revenue", description: "Notify while this pane is visible. Starts with the next refresh; historical backfills are excluded." }] } }],
|
|
@@ -62,6 +62,7 @@ export function toBacktestBars(
|
|
|
62
62
|
low: isFiniteNumber(point.low) && point.low > 0 ? point.low : null,
|
|
63
63
|
close: point.close,
|
|
64
64
|
...(point.partial === true ? { partial: true as const } : {}),
|
|
65
|
+
...(point.unsettled === true ? { unsettled: true as const } : {}),
|
|
65
66
|
});
|
|
66
67
|
}
|
|
67
68
|
const bars = [...byDate.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import type { PluginModule } from "../plugin-module";
|
|
2
2
|
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
3
3
|
import { backtestHeadless } from "./headless";
|
|
4
|
-
import { BacktestPane } from "./pane";
|
|
5
4
|
import { BACKTEST_PRESETS, LOOKBACK_OPTIONS } from "./presets";
|
|
6
5
|
import { BACKTEST_FRESHNESS } from "./report";
|
|
7
6
|
|
|
8
7
|
export const backtestModule: PluginModule = {
|
|
9
8
|
panes: [{
|
|
10
|
-
id: "backtest", name: "Backtest", icon: "B", component: BacktestPane,
|
|
9
|
+
id: "backtest", name: "Backtest", icon: "B", get component(): typeof import("./pane").BacktestPane { const { BacktestPane } = require("./pane"); return BacktestPane; },
|
|
11
10
|
defaultPosition: "right", tickerFollower: true, defaultMode: "floating", defaultFloatingSize: { width: 120, height: 34 },
|
|
12
11
|
tableExport: true,
|
|
13
12
|
// A capture is dated as `fn BT` dates the run, by its last session, never by the ticker's live quote.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ema, rsi, sma, type IndexedValue } from "../../../time-series/studies";
|
|
1
|
+
import { ema, rsi, sma, type IndexedValue } from "../../../time-series/studies/indicators";
|
|
2
2
|
|
|
3
3
|
/** One daily bar as the rules see it. Open is optional; fills fall back to the close. */
|
|
4
4
|
export interface BacktestBar {
|
|
@@ -9,6 +9,8 @@ export interface BacktestBar {
|
|
|
9
9
|
close: number;
|
|
10
10
|
/** The bar's day had not ended when it was served: its close is the latest price. */
|
|
11
11
|
partial?: true;
|
|
12
|
+
/** A future's session that closed before its settlement was in: its close is the last trade. */
|
|
13
|
+
unsettled?: true;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export type Operand =
|
|
@@ -4,6 +4,7 @@ import type { BacktestHistory } from "./client";
|
|
|
4
4
|
import { runBacktest, type BacktestOptions, type BacktestResult } from "./engine";
|
|
5
5
|
import { delistingLabel } from "../../../market-data/quotes/delisting";
|
|
6
6
|
import type { BacktestBar, Rule } from "./rules";
|
|
7
|
+
import { formingBarsState, isFormingBar } from "../shared/forming-bars";
|
|
7
8
|
|
|
8
9
|
/** Which served sessions a run tests. */
|
|
9
10
|
export interface BacktestWindowOptions {
|
|
@@ -62,17 +63,17 @@ export function backtestWindow(
|
|
|
62
63
|
if (history.undatedSkipped > 0) {
|
|
63
64
|
warnings.push(`${history.undatedSkipped} price ${history.undatedSkipped === 1 ? "point" : "points"} without a date ${history.undatedSkipped === 1 ? "was" : "were"} left out.`);
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
const
|
|
66
|
+
// A future's session that has not settled moves too: its close becomes the settlement.
|
|
67
|
+
const forming = inWindow.filter(isFormingBar);
|
|
68
|
+
const bars = forming.length === 0 || options.includePartial ? inWindow : inWindow.filter((bar) => !isFormingBar(bar));
|
|
67
69
|
const through = bars.at(-1)?.date;
|
|
68
70
|
if (forming.length > 0) {
|
|
69
|
-
const dates = listDates(forming.map((bar) => bar.date))
|
|
71
|
+
const dates = `${listDates(forming.map((bar) => bar.date))} ${formingBarsState(forming)}`;
|
|
70
72
|
warnings.push(options.includePartial
|
|
71
|
-
? `${dates}
|
|
72
|
-
: `${dates} ${
|
|
73
|
-
+ `${through ? `: the test runs through ${through}, the last complete session` : ""}.`);
|
|
73
|
+
? `${dates} and kept: the last close is ${forming.every((bar) => bar.partial) ? "the latest price" : "not final"}, so the results move until the session ends.`
|
|
74
|
+
: `${dates} and left out${through ? `: the test runs through ${through}, the last complete session` : ""}.`);
|
|
74
75
|
}
|
|
75
|
-
const newestComplete = history.bars.findLast((bar) => !bar
|
|
76
|
+
const newestComplete = history.bars.findLast((bar) => !isFormingBar(bar))?.date;
|
|
76
77
|
const delisting = history.delisting;
|
|
77
78
|
if (delisting) {
|
|
78
79
|
const listing = `${history.symbol ? `${parsePublicTickerKey(history.symbol).symbol}: ` : ""}${delistingLabel(delisting)}`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, type Ref } from "react";
|
|
2
|
-
import { Box, ScrollBox, Text, TextAttributes, type BoxRenderable, type ScrollBoxRenderable } from "../../../ui";
|
|
2
|
+
import { Box, ScrollBox, Text, TextAttributes, useNativeRenderer, useUiCapabilities, type BoxRenderable, type ScrollBoxRenderable } from "../../../ui";
|
|
3
3
|
import { Button, FieldLabel, NumberField, SectionHeading, SegmentedControl, TextField, useFieldRing } from "../../../components";
|
|
4
4
|
import { afterLayout, revealInScrollBox } from "../../../components/ui/reveal-in-scroll-box";
|
|
5
5
|
import {
|
|
@@ -294,10 +294,23 @@ export function BrokerDetailContent({
|
|
|
294
294
|
// Enter saves from any field, so what stopped the save can be below the
|
|
295
295
|
// fold; bring it and the buttons into view.
|
|
296
296
|
const actionsRef = useRef<BoxRenderable | null>(null);
|
|
297
|
+
const desktop = useUiCapabilities().nativePaneChrome === true;
|
|
298
|
+
const nativeRenderer = useNativeRenderer();
|
|
297
299
|
useEffect(() => {
|
|
298
300
|
if (!editError) return;
|
|
299
|
-
|
|
300
|
-
|
|
301
|
+
const reveal = () => revealInScrollBox(scrollRef.current, actionsRef.current);
|
|
302
|
+
if (desktop) return afterLayout(reveal);
|
|
303
|
+
// The terminal lays the error line out only when it draws a frame, which
|
|
304
|
+
// can come after a 0 ms timer: measured before it, the buttons are one row
|
|
305
|
+
// short and the error pushes Save back below the fold.
|
|
306
|
+
const onFrame = () => {
|
|
307
|
+
nativeRenderer.off("frame", onFrame);
|
|
308
|
+
reveal();
|
|
309
|
+
};
|
|
310
|
+
nativeRenderer.on("frame", onFrame);
|
|
311
|
+
nativeRenderer.requestRender();
|
|
312
|
+
return () => nativeRenderer.off("frame", onFrame);
|
|
313
|
+
}, [desktop, editError, nativeRenderer]);
|
|
301
314
|
|
|
302
315
|
if (!row) return <Box flexGrow={1} />;
|
|
303
316
|
|
|
@@ -365,7 +365,7 @@ export const brokerManagerModule: PluginModule = {
|
|
|
365
365
|
personalData: "account",
|
|
366
366
|
icon: "B",
|
|
367
367
|
component: BrokersPane,
|
|
368
|
-
reportFreshness: { status: "not-a-feed", basis: "broker connections" },
|
|
368
|
+
reportFreshness: { status: "not-a-feed", basis: "broker connections", local: true },
|
|
369
369
|
defaultPosition: "right",
|
|
370
370
|
defaultMode: "floating",
|
|
371
371
|
defaultFloatingSize: { width: 92, height: 24 },
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
activityColor,
|
|
12
12
|
activityLabel,
|
|
13
13
|
criticalityColor,
|
|
14
|
+
listWord,
|
|
14
15
|
metricColor,
|
|
15
16
|
moneyAmount,
|
|
16
17
|
moneyText,
|
|
@@ -115,6 +116,10 @@ export function renderBuildoutCell(
|
|
|
115
116
|
return { text: text(company.return1y), color: metricColor(company.return1y) };
|
|
116
117
|
case "employees":
|
|
117
118
|
return { text: text(company.employeeCount), color: colors.textDim };
|
|
119
|
+
case "listRole":
|
|
120
|
+
return { text: text(listWord(company.listRole)), color: colors.textDim };
|
|
121
|
+
case "listTier":
|
|
122
|
+
return { text: text(listWord(company.listTier)), color: company.listTier === "leader" ? colors.text : colors.textDim };
|
|
118
123
|
}
|
|
119
124
|
}
|
|
120
125
|
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { useMemo, useRef, useState } from "react";
|
|
2
|
+
import type { ChartVectorShape } from "../../../../ui/host";
|
|
3
|
+
import { Box, ChartSurface, Span, Text, TextAttributes, useUiCapabilities } from "../../../../ui";
|
|
4
|
+
import { BADGE_TINT, ClippedText, NATIVE_BADGE_STYLE } from "../../../../components/ui/display";
|
|
5
|
+
import { cardSurfaceStyle } from "../../../../components/ui/card";
|
|
6
|
+
import { useShortcut } from "../../../../react/input";
|
|
7
|
+
import { blendHex } from "../../../../theme/colors";
|
|
8
|
+
import { SERIES_COLORS } from "../../../../theme/series-colors";
|
|
9
|
+
import { useThemeColors } from "../../../../theme/theme-context";
|
|
10
|
+
import { displayWidth } from "../../../../utils/format";
|
|
11
|
+
import { isPlainKey } from "../../../../utils/keyboard";
|
|
12
|
+
import { ribbonY } from "../../supply-chain/flow-layout";
|
|
13
|
+
import type { BuildoutCompanyProfile, BuildoutRelation, RelationRole } from "./model";
|
|
14
|
+
|
|
15
|
+
/** Power and hosting gold, then the supply chain's own supplier, customer and competitor colours. */
|
|
16
|
+
const RELATION_COLORS: Record<RelationRole, string> = {
|
|
17
|
+
power: SERIES_COLORS[2]!,
|
|
18
|
+
supplier: SERIES_COLORS[0]!,
|
|
19
|
+
customer: SERIES_COLORS[1]!,
|
|
20
|
+
competitor: SERIES_COLORS[5]!,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const GROUP_LABELS: Record<RelationRole, string> = { power: "Power", supplier: "Suppliers", customer: "Customers", competitor: "Competitors" };
|
|
24
|
+
const CARD_ROWS = 2;
|
|
25
|
+
const LINK_STEPS = 40;
|
|
26
|
+
const CHIP_LABEL_CELLS = 13;
|
|
27
|
+
/** Below this the columns and the ribbons between them do not fit; the flow reads as a list. */
|
|
28
|
+
export const FLOW_MIN_WIDTH = 64;
|
|
29
|
+
|
|
30
|
+
interface Rect { x: number; y: number; width: number; height: number }
|
|
31
|
+
interface FlowCard extends Rect { relation: BuildoutRelation }
|
|
32
|
+
interface FlowHeader { role: RelationRole; x: number; y: number }
|
|
33
|
+
interface FlowMore { role: RelationRole; x: number; y: number; hidden: number }
|
|
34
|
+
interface FlowLink { id: string; role: RelationRole; x0: number; y0: number; x1: number; y1: number }
|
|
35
|
+
interface FlowChip { relation: BuildoutRelation | null; label: string; x: number; width: number }
|
|
36
|
+
|
|
37
|
+
export interface FlowGeometry {
|
|
38
|
+
center: Rect;
|
|
39
|
+
cards: FlowCard[];
|
|
40
|
+
headers: FlowHeader[];
|
|
41
|
+
more: FlowMore[];
|
|
42
|
+
links: FlowLink[];
|
|
43
|
+
chips: FlowChip[];
|
|
44
|
+
chipRow: number | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface Group { role: RelationRole; items: BuildoutRelation[]; page: number }
|
|
48
|
+
|
|
49
|
+
/** Rows a column of groups takes with `counts` cards each. */
|
|
50
|
+
function columnRows(groups: readonly Group[], counts: readonly number[], gap: number): number {
|
|
51
|
+
return groups.reduce((sum, group, index) => {
|
|
52
|
+
if (!group.items.length) return sum;
|
|
53
|
+
const shown = counts[index]!;
|
|
54
|
+
return sum + (sum ? 1 : 0) + 1 + shown * CARD_ROWS + Math.max(0, shown - 1) * gap + (shown < group.items.length ? 1 : 0);
|
|
55
|
+
}, 0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Places a column's groups in `rows`, the last group giving up cards first, centred when there is room. */
|
|
59
|
+
function placeColumn(groups: readonly Group[], x: number, width: number, rows: number) {
|
|
60
|
+
const cards: FlowCard[] = [];
|
|
61
|
+
const headers: FlowHeader[] = [];
|
|
62
|
+
const more: FlowMore[] = [];
|
|
63
|
+
for (const gap of [1, 0]) {
|
|
64
|
+
const counts = groups.map((group) => group.items.length);
|
|
65
|
+
while (columnRows(groups, counts, gap) > rows) {
|
|
66
|
+
let index = -1;
|
|
67
|
+
for (let i = counts.length - 1; i >= 0; i--) if (counts[i]! > 1) { index = i; break; }
|
|
68
|
+
if (index < 0) break;
|
|
69
|
+
counts[index]!--;
|
|
70
|
+
}
|
|
71
|
+
const used = columnRows(groups, counts, gap);
|
|
72
|
+
if (used > rows && gap === 1) continue;
|
|
73
|
+
let y = Math.max(0, Math.floor((rows - used) / 2));
|
|
74
|
+
let first = true;
|
|
75
|
+
groups.forEach((group, index) => {
|
|
76
|
+
if (!group.items.length) return;
|
|
77
|
+
if (!first) y += 1;
|
|
78
|
+
first = false;
|
|
79
|
+
headers.push({ role: group.role, x, y });
|
|
80
|
+
y += 1;
|
|
81
|
+
const shown = counts[index]!;
|
|
82
|
+
const pages = Math.max(1, Math.ceil(group.items.length / Math.max(1, shown)));
|
|
83
|
+
const start = shown < group.items.length ? (group.page % pages) * shown : 0;
|
|
84
|
+
group.items.slice(start, start + shown).forEach((relation, offset) => {
|
|
85
|
+
cards.push({ relation, x, y, width, height: CARD_ROWS });
|
|
86
|
+
y += CARD_ROWS + (offset < shown - 1 ? gap : 0);
|
|
87
|
+
});
|
|
88
|
+
if (shown < group.items.length) {
|
|
89
|
+
more.push({ role: group.role, x, y, hidden: group.items.length - shown });
|
|
90
|
+
y += 1;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
return { cards, headers, more };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Power and hosting deals and suppliers on the left, customers on the right,
|
|
100
|
+
* the company between them with a line from every card to its edge, in card
|
|
101
|
+
* order so no two cross; competitors as chips on the last row.
|
|
102
|
+
*/
|
|
103
|
+
function flowGeometry(profile: BuildoutCompanyProfile, width: number, height: number, focusLabel: string, pages: Partial<Record<RelationRole, number>>): FlowGeometry {
|
|
104
|
+
const of = (role: RelationRole) => profile.relations.filter((relation) => relation.role === role);
|
|
105
|
+
const competitors = of("competitor");
|
|
106
|
+
const chipRow = competitors.length ? height - 1 : null;
|
|
107
|
+
const rows = Math.max(CARD_ROWS + 1, (chipRow ?? height) - (chipRow === null ? 0 : 1));
|
|
108
|
+
const cardWidth = Math.max(18, Math.min(38, Math.floor(width * (width < 110 ? 0.31 : 0.27))));
|
|
109
|
+
const left = placeColumn([{ role: "power", items: of("power"), page: pages.power ?? 0 }, { role: "supplier", items: of("supplier"), page: pages.supplier ?? 0 }], 1, cardWidth, rows);
|
|
110
|
+
const right = placeColumn([{ role: "customer", items: of("customer"), page: pages.customer ?? 0 }], width - cardWidth - 1, cardWidth, rows);
|
|
111
|
+
const centerWidth = Math.max(12, Math.min(26, displayWidth(focusLabel) + 6));
|
|
112
|
+
const centerHeight = Math.max(3, Math.min(9, Math.floor(rows * 0.4)));
|
|
113
|
+
const center = { x: Math.floor((width - centerWidth) / 2), y: Math.floor((rows - centerHeight) / 2), width: centerWidth, height: centerHeight };
|
|
114
|
+
const links: FlowLink[] = [];
|
|
115
|
+
const leftCards = left.cards;
|
|
116
|
+
leftCards.forEach((card, index) => links.push({
|
|
117
|
+
id: `${card.relation.role}:${card.relation.id}`, role: card.relation.role,
|
|
118
|
+
x0: card.x + card.width, y0: card.y + 1, x1: center.x, y1: center.y + (center.height * (index + 0.5)) / leftCards.length,
|
|
119
|
+
}));
|
|
120
|
+
right.cards.forEach((card, index) => links.push({
|
|
121
|
+
id: `${card.relation.role}:${card.relation.id}`, role: card.relation.role,
|
|
122
|
+
x0: center.x + center.width, y0: center.y + (center.height * (index + 0.5)) / right.cards.length, x1: card.x, y1: card.y + 1,
|
|
123
|
+
}));
|
|
124
|
+
// Competitors as chips while they fit, then "+N more".
|
|
125
|
+
const chips: FlowChip[] = [];
|
|
126
|
+
let x = CHIP_LABEL_CELLS + 1;
|
|
127
|
+
for (const [index, relation] of competitors.entries()) {
|
|
128
|
+
const label = relation.ticker ?? relation.name;
|
|
129
|
+
const chipWidth = Math.min(22, displayWidth(label) + 2);
|
|
130
|
+
const rest = competitors.length - index - 1;
|
|
131
|
+
if (x + chipWidth + (rest ? 11 : 0) > width - 1) {
|
|
132
|
+
chips.push({ relation: null, label: `+${competitors.length - index} more`, x, width: displayWidth(`+${competitors.length - index} more`) + 2 });
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
chips.push({ relation, label, x, width: chipWidth });
|
|
136
|
+
x += chipWidth + 1;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
center,
|
|
140
|
+
cards: [...left.cards, ...right.cards],
|
|
141
|
+
headers: [...left.headers, ...right.headers],
|
|
142
|
+
more: [...left.more, ...right.more],
|
|
143
|
+
links,
|
|
144
|
+
chips,
|
|
145
|
+
chipRow,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const linkId = (relation: BuildoutRelation) => `${relation.role}:${relation.id}`;
|
|
150
|
+
|
|
151
|
+
export function CompanyFlow({ profile, width, height, focused, onOpen }: {
|
|
152
|
+
profile: BuildoutCompanyProfile;
|
|
153
|
+
width: number;
|
|
154
|
+
height: number;
|
|
155
|
+
focused: boolean;
|
|
156
|
+
onOpen: (relation: BuildoutRelation) => void;
|
|
157
|
+
}) {
|
|
158
|
+
const colors = useThemeColors();
|
|
159
|
+
const desktop = !!useUiCapabilities().nativePaneChrome;
|
|
160
|
+
const [pages, setPages] = useState<Partial<Record<RelationRole, number>>>({});
|
|
161
|
+
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
162
|
+
const [hovered, setHovered] = useState<string | null>(null);
|
|
163
|
+
const surfaceRef = useRef<{ x: number; y: number; absoluteX?: number; absoluteY?: number } | null>(null);
|
|
164
|
+
const focusLabel = profile.ticker ?? profile.name;
|
|
165
|
+
const geometry = useMemo(() => flowGeometry(profile, width, height, focusLabel, pages), [focusLabel, height, pages, profile, width]);
|
|
166
|
+
const { cards, links, center } = geometry;
|
|
167
|
+
const selectable = useMemo(() => [...cards.map((card) => card.relation), ...geometry.chips.flatMap((chip) => chip.relation ? [chip.relation] : [])], [cards, geometry.chips]);
|
|
168
|
+
const selectedIndex = Math.max(0, selectable.findIndex((relation) => linkId(relation) === selectedId));
|
|
169
|
+
const selected = selectable[selectedIndex] ? linkId(selectable[selectedIndex]!) : null;
|
|
170
|
+
const active = hovered ?? (focused ? selected : null);
|
|
171
|
+
const focusTint = blendHex(colors.bg, colors.textBright, 0.55);
|
|
172
|
+
|
|
173
|
+
useShortcut((event) => {
|
|
174
|
+
if (!focused || event.defaultPrevented || selectable.length === 0) return;
|
|
175
|
+
if (isPlainKey(event, "j", "down") || isPlainKey(event, "k", "up")) {
|
|
176
|
+
const step = isPlainKey(event, "j", "down") ? 1 : -1;
|
|
177
|
+
setSelectedId(linkId(selectable[(selectedIndex + step + selectable.length) % selectable.length]!));
|
|
178
|
+
} else if (isPlainKey(event, "return", "enter")) onOpen(selectable[selectedIndex]!);
|
|
179
|
+
else return;
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
event.stopPropagation();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const linkColors = (link: FlowLink) => link.x0 < center.x ? [RELATION_COLORS[link.role], focusTint] : [focusTint, RELATION_COLORS[link.role]];
|
|
185
|
+
const vectors = useMemo<ChartVectorShape[]>(() => links.map((link) => ({
|
|
186
|
+
id: link.id,
|
|
187
|
+
points: Array.from({ length: LINK_STEPS + 1 }, (_, step) => {
|
|
188
|
+
const x = link.x0 + (link.x1 - link.x0) * step / LINK_STEPS;
|
|
189
|
+
return { x: x / width, y: ribbonY(link, x) / height };
|
|
190
|
+
}),
|
|
191
|
+
color: RELATION_COLORS[link.role],
|
|
192
|
+
gradient: linkColors(link),
|
|
193
|
+
strokeWidth: active === link.id ? 2.4 : 1.4,
|
|
194
|
+
opacity: active && active !== link.id ? 0.3 : active === link.id ? 1 : 0.75,
|
|
195
|
+
})), [active, height, links, width, focusTint, center.x]);
|
|
196
|
+
|
|
197
|
+
// The terminal draws the same curves in braille, each coloured from its card to the company.
|
|
198
|
+
const braille = useMemo(() => {
|
|
199
|
+
if (desktop) return null;
|
|
200
|
+
const grid = Array.from({ length: height }, () => Array.from({ length: width }, () => ({ mask: 0, color: colors.textDim })));
|
|
201
|
+
const bits = [[1, 8], [2, 16], [4, 32], [64, 128]];
|
|
202
|
+
for (const link of links) {
|
|
203
|
+
const [from, to] = linkColors(link);
|
|
204
|
+
for (let dx = Math.ceil(link.x0 * 2); dx < Math.floor(link.x1 * 2); dx++) {
|
|
205
|
+
const x = (dx + 0.5) / 2;
|
|
206
|
+
const t = (x - link.x0) / Math.max(1e-6, link.x1 - link.x0);
|
|
207
|
+
const dy = Math.round(ribbonY(link, x) * 4 - 0.5);
|
|
208
|
+
const cell = grid[Math.floor(dy / 4)]?.[Math.floor(dx / 2)];
|
|
209
|
+
if (!cell || dy < 0) continue;
|
|
210
|
+
cell.mask |= bits[dy % 4]![dx % 2]!;
|
|
211
|
+
const color = blendHex(from!, to!, t);
|
|
212
|
+
cell.color = active === link.id ? color : blendHex(colors.bg, color, 0.75);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return grid.map((line) => {
|
|
216
|
+
const spans: { text: string; color: string }[] = [];
|
|
217
|
+
for (const cell of line) {
|
|
218
|
+
const char = cell.mask ? String.fromCharCode(0x2800 + cell.mask) : " ";
|
|
219
|
+
if (spans.at(-1)?.color === cell.color) spans.at(-1)!.text += char;
|
|
220
|
+
else spans.push({ text: char, color: cell.color });
|
|
221
|
+
}
|
|
222
|
+
return spans;
|
|
223
|
+
});
|
|
224
|
+
}, [active, colors.bg, colors.textDim, desktop, height, links, width, focusTint, center.x]);
|
|
225
|
+
|
|
226
|
+
/** The line under the pointer comes forward. */
|
|
227
|
+
const hoverAt = (event: { preciseX?: number; preciseY?: number; x: number; y: number }) => {
|
|
228
|
+
const origin = surfaceRef.current;
|
|
229
|
+
if (!origin) return;
|
|
230
|
+
const x = (event.preciseX ?? event.x) - (origin.absoluteX ?? origin.x);
|
|
231
|
+
const y = (event.preciseY ?? event.y) - (origin.absoluteY ?? origin.y);
|
|
232
|
+
let best: { id: string; distance: number } | null = null;
|
|
233
|
+
for (const link of links) {
|
|
234
|
+
if (x < link.x0 || x > link.x1) continue;
|
|
235
|
+
const distance = Math.abs(ribbonY(link, x) - y);
|
|
236
|
+
if (distance < 0.7 && (!best || distance < best.distance)) best = { id: link.id, distance };
|
|
237
|
+
}
|
|
238
|
+
const next = best?.id ?? null;
|
|
239
|
+
if (next !== hovered) setHovered(next);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<Box width={width} height={height} flexGrow={1} flexBasis={0} minHeight={0} position="relative" overflow="hidden">
|
|
244
|
+
<ChartSurface ref={surfaceRef} width={width} height={height} position="absolute" left={0} top={0}
|
|
245
|
+
vectors={desktop ? vectors : undefined} flexDirection="column"
|
|
246
|
+
onMouseMove={hoverAt} onMouseOut={() => setHovered(null)}
|
|
247
|
+
onMouseDown={() => { const card = hovered ? cards.find((entry) => linkId(entry.relation) === hovered) : null; if (card) onOpen(card.relation); }}
|
|
248
|
+
aria-label={`${profile.name} power, suppliers and customers`}>
|
|
249
|
+
{braille ? braille.map((spans, y) => <Text key={y}>{spans.map((span, i) => <Span key={i} fg={span.color}>{span.text}</Span>)}</Text>) : null}
|
|
250
|
+
</ChartSurface>
|
|
251
|
+
{geometry.headers.map((header) => (
|
|
252
|
+
<Box key={header.role} position="absolute" left={header.x} top={header.y} width={20} height={1}>
|
|
253
|
+
<Text fg={RELATION_COLORS[header.role]} attributes={TextAttributes.BOLD}>{GROUP_LABELS[header.role]}</Text>
|
|
254
|
+
</Box>
|
|
255
|
+
))}
|
|
256
|
+
<FocusCard profile={profile} label={focusLabel} rect={center} desktop={desktop} />
|
|
257
|
+
{cards.map((card) => (
|
|
258
|
+
<RelationCard key={linkId(card.relation)} card={card} desktop={desktop} active={active === linkId(card.relation)}
|
|
259
|
+
onPress={() => { setSelectedId(linkId(card.relation)); onOpen(card.relation); }}
|
|
260
|
+
onHover={(on) => setHovered(on ? linkId(card.relation) : null)} />
|
|
261
|
+
))}
|
|
262
|
+
{geometry.more.map((entry) => (
|
|
263
|
+
<Box key={`more:${entry.role}`} position="absolute" left={entry.x} top={entry.y} width={20} height={1}
|
|
264
|
+
onMouseDown={() => setPages((current) => ({ ...current, [entry.role]: (current[entry.role] ?? 0) + 1 }))}
|
|
265
|
+
style={desktop ? { cursor: "pointer" } : undefined} role="button" aria-label={`Show ${entry.hidden} more ${GROUP_LABELS[entry.role].toLowerCase()}`}>
|
|
266
|
+
<Text fg={colors.textDim}>{`+${entry.hidden} more`}</Text>
|
|
267
|
+
</Box>
|
|
268
|
+
))}
|
|
269
|
+
{geometry.chipRow !== null ? (
|
|
270
|
+
<Box position="absolute" left={1} top={geometry.chipRow} width={CHIP_LABEL_CELLS} height={1}>
|
|
271
|
+
<Text fg={RELATION_COLORS.competitor}>Competitors</Text>
|
|
272
|
+
</Box>
|
|
273
|
+
) : null}
|
|
274
|
+
{geometry.chipRow !== null ? geometry.chips.map((chip) => (
|
|
275
|
+
<CompetitorChip key={chip.label} chip={chip} row={geometry.chipRow!} desktop={desktop}
|
|
276
|
+
active={!!chip.relation && active === linkId(chip.relation)} onPress={() => { if (chip.relation) onOpen(chip.relation); }} />
|
|
277
|
+
)) : null}
|
|
278
|
+
</Box>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function FocusCard({ profile, label, rect, desktop }: { profile: BuildoutCompanyProfile; label: string; rect: Rect; desktop: boolean }) {
|
|
283
|
+
const colors = useThemeColors();
|
|
284
|
+
const showName = profile.name && profile.name.toUpperCase() !== label.toUpperCase() && rect.height >= (desktop ? 3 : 4);
|
|
285
|
+
return (
|
|
286
|
+
<Box position="absolute" left={rect.x} top={rect.y} width={rect.width} height={rect.height} flexDirection="column" alignItems="center" justifyContent="center"
|
|
287
|
+
border={!desktop} borderStyle="rounded" borderColor={colors.textMuted}
|
|
288
|
+
style={desktop ? cardSurfaceStyle({ padding: "0" }) : undefined}>
|
|
289
|
+
<Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{label}</Text>
|
|
290
|
+
{showName ? <ClippedText fg={colors.textDim} text={profile.name} width={rect.width - 4} /> : null}
|
|
291
|
+
</Box>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function RelationCard({ card, desktop, active, onPress, onHover }: {
|
|
296
|
+
card: FlowCard; desktop: boolean; active: boolean; onPress: () => void; onHover: (on: boolean) => void;
|
|
297
|
+
}) {
|
|
298
|
+
const colors = useThemeColors();
|
|
299
|
+
const { relation } = card;
|
|
300
|
+
const accent = RELATION_COLORS[relation.role];
|
|
301
|
+
const inner = Math.max(4, card.width - 2);
|
|
302
|
+
const nameWidth = Math.max(4, inner - (relation.ticker ? displayWidth(relation.ticker) + 1 : 0));
|
|
303
|
+
const listed = !!relation.ticker;
|
|
304
|
+
return (
|
|
305
|
+
<Box position="absolute" left={card.x} top={card.y} width={card.width} height={card.height} flexDirection="column"
|
|
306
|
+
backgroundColor={desktop ? undefined : active ? colors.selected : blendHex(colors.bg, accent, 0.12)}
|
|
307
|
+
style={desktop ? { ...cardSurfaceStyle({ selected: active, accent, padding: "0 var(--gloom-space-sm)" }), cursor: "pointer" } : undefined}
|
|
308
|
+
onMouseDown={onPress} onMouseOver={() => onHover(true)} onMouseOut={() => onHover(false)}
|
|
309
|
+
data-gloom-role="buildout-flow-card" role="button" aria-label={`Open ${relation.name}`}>
|
|
310
|
+
<Box flexDirection="row" height={1} overflow="hidden" style={desktop ? { flexWrap: "wrap" } : undefined}>
|
|
311
|
+
{!desktop ? <Text fg={accent}>▍</Text> : null}
|
|
312
|
+
<ClippedText fg={active && !desktop ? colors.selectedText : listed ? colors.textBright : colors.text}
|
|
313
|
+
attributes={listed ? TextAttributes.BOLD : 0} text={relation.name} width={nameWidth} />
|
|
314
|
+
{relation.ticker && (desktop || displayWidth(relation.name) + displayWidth(relation.ticker) + 1 <= inner)
|
|
315
|
+
? <Text fg={colors.textDim} style={desktop ? { flex: "0 0 auto" } : undefined}>{` ${relation.ticker}`}</Text> : null}
|
|
316
|
+
</Box>
|
|
317
|
+
{relation.figure ? (
|
|
318
|
+
<Box height={1} paddingLeft={desktop ? 0 : 1} overflow="hidden">
|
|
319
|
+
<ClippedText fg={active && !desktop ? colors.selectedText : colors.textDim} text={relation.figure} width={inner - (desktop ? 0 : 1)} />
|
|
320
|
+
</Box>
|
|
321
|
+
) : null}
|
|
322
|
+
</Box>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function CompetitorChip({ chip, row, desktop, active, onPress }: { chip: FlowChip; row: number; desktop: boolean; active: boolean; onPress: () => void }) {
|
|
327
|
+
const colors = useThemeColors();
|
|
328
|
+
const accent = RELATION_COLORS.competitor;
|
|
329
|
+
const more = !chip.relation;
|
|
330
|
+
const background = desktop
|
|
331
|
+
? active ? accent : more ? undefined : blendHex(colors.bg, accent, BADGE_TINT)
|
|
332
|
+
: active ? blendHex(colors.bg, accent, 0.45) : more ? undefined : blendHex(colors.bg, accent, 0.14);
|
|
333
|
+
const text = <ClippedText fg={more ? colors.textDim : active ? desktop ? colors.bg : colors.textBright : accent} text={chip.label} width={chip.width - 2} />;
|
|
334
|
+
return (
|
|
335
|
+
<Box position="absolute" left={chip.x} top={row} width={chip.width} height={1} justifyContent={desktop ? "center" : undefined}
|
|
336
|
+
onMouseDown={onPress} role="button" aria-label={chip.relation ? `Open ${chip.relation.name}` : chip.label}>
|
|
337
|
+
{desktop
|
|
338
|
+
? <Box paddingX={1} minWidth={0} backgroundColor={background} justifyContent="center" style={{ ...NATIVE_BADGE_STYLE, cursor: more ? undefined : "pointer" }}>{text}</Box>
|
|
339
|
+
: <Box paddingX={1} height={1} backgroundColor={background}>{text}</Box>}
|
|
340
|
+
</Box>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** The flow as a list, for a pane too narrow for columns: each group under its name. */
|
|
345
|
+
export function CompanyFlowList({ profile, width, onOpen }: { profile: BuildoutCompanyProfile; width: number; onOpen: (relation: BuildoutRelation) => void }) {
|
|
346
|
+
const colors = useThemeColors();
|
|
347
|
+
const roles: RelationRole[] = ["power", "customer", "supplier", "competitor"];
|
|
348
|
+
return (
|
|
349
|
+
<Box flexDirection="column" width={width} paddingX={1}>
|
|
350
|
+
{roles.map((role) => {
|
|
351
|
+
const items = profile.relations.filter((relation) => relation.role === role);
|
|
352
|
+
if (!items.length) return null;
|
|
353
|
+
return (
|
|
354
|
+
<Box key={role} flexDirection="column" marginBottom={1}>
|
|
355
|
+
<Text fg={RELATION_COLORS[role]} attributes={TextAttributes.BOLD}>{GROUP_LABELS[role]}</Text>
|
|
356
|
+
{items.map((relation) => (
|
|
357
|
+
<Box key={linkId(relation)} flexDirection="column" onMouseDown={() => onOpen(relation)}>
|
|
358
|
+
<ClippedText fg={relation.ticker ? colors.textBright : colors.text} text={`${relation.name}${relation.ticker ? ` ${relation.ticker}` : ""}`} width={width - 2} />
|
|
359
|
+
{relation.figure ? <ClippedText fg={colors.textDim} text={relation.figure} width={width - 2} /> : null}
|
|
360
|
+
</Box>
|
|
361
|
+
))}
|
|
362
|
+
</Box>
|
|
363
|
+
);
|
|
364
|
+
})}
|
|
365
|
+
</Box>
|
|
366
|
+
);
|
|
367
|
+
}
|