gloomberb 0.18.1 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +6 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +281 -154
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +100 -50
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +4 -1
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +37 -2
- package/src/cli/pane-functions/catalog.ts +3 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +9 -4
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +6 -5
- package/src/cli/pane-functions/index.ts +3 -2
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +2 -1
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/series-search.ts +2 -1
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +11 -2
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +58 -8
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/index.ts +2 -0
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/status.tsx +17 -3
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +9 -3
- package/src/i18n/ja.ts +9 -3
- package/src/i18n/ko.ts +9 -3
- package/src/i18n/zh-cn.ts +10 -4
- package/src/i18n/zh-tw.ts +10 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +20 -5
- package/src/plugins/builtin/equity-screener/model.ts +40 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +37 -16
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/insider/base-values.ts +72 -0
- package/src/plugins/builtin/insider/headless.ts +29 -4
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/view.tsx +77 -22
- package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
- package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +62 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +39 -7
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +6 -0
- package/src/renderers/dom/styles.css +12 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +50 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +12 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +2 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
|
@@ -5,7 +5,7 @@ import { t } from "../../../../i18n";
|
|
|
5
5
|
import { ShortcutHint } from "../../../ui/shortcut-hint";
|
|
6
6
|
import { Button } from "../../../ui/button";
|
|
7
7
|
import { ChoiceDialog } from "../../../ui/choice-dialog";
|
|
8
|
-
import { IconButton } from "../../../ui/icon";
|
|
8
|
+
import { Icon, IconButton } from "../../../ui/icon";
|
|
9
9
|
import { useRemoteUiNode } from "../../../../remote/semantic-tree";
|
|
10
10
|
import { useOptionalDialog, type PromptContext } from "../../../../ui/dialog";
|
|
11
11
|
import { nativePaneFooterRows } from "../sizing";
|
|
@@ -23,7 +23,10 @@ import {
|
|
|
23
23
|
|
|
24
24
|
export {
|
|
25
25
|
datesPaneData,
|
|
26
|
+
DELAYED_SEGMENT,
|
|
27
|
+
delayedFooterSegment,
|
|
26
28
|
hasPaneFooterContent,
|
|
29
|
+
splitDelayedStatus,
|
|
27
30
|
type CombinedPaneFooter,
|
|
28
31
|
type PaneFooterPressEvent,
|
|
29
32
|
type PaneFooterSegment,
|
|
@@ -105,6 +108,21 @@ function SegmentView({ segment, focused }: { segment: PaneFooterSegment; focused
|
|
|
105
108
|
else stopMouseEvent(event);
|
|
106
109
|
};
|
|
107
110
|
|
|
111
|
+
if (nativePaneChrome && segment.icon && !segment.onPress) {
|
|
112
|
+
// A status, not a control: the icon button's box, with its detail on hover.
|
|
113
|
+
return (
|
|
114
|
+
<Box
|
|
115
|
+
role="img"
|
|
116
|
+
aria-label={label}
|
|
117
|
+
title={segment.title ?? label}
|
|
118
|
+
data-gloom-role="pane-footer-icon"
|
|
119
|
+
data-icon={segment.icon}
|
|
120
|
+
>
|
|
121
|
+
<Icon name={segment.icon} size={14} color={segment.disabled ? colors.textMuted : footerToneColor(segment.parts[0] ?? { text: "" })} />
|
|
122
|
+
</Box>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
108
126
|
if (nativePaneChrome && segment.icon) {
|
|
109
127
|
return (
|
|
110
128
|
<IconButton
|
|
@@ -247,7 +265,7 @@ function FooterContent({
|
|
|
247
265
|
: undefined;
|
|
248
266
|
// A segment with shorter forms takes the longest that fits beside the hints, gaps and terminal keys included.
|
|
249
267
|
const keysWidth = nativePaneChrome ? 0 : footer.info.reduce((total, segment) => total + terminalSegmentKey(segment, focused).length, 0);
|
|
250
|
-
const info = infoWidth != null ? fitPaneFooterInfo(footer.info, infoWidth - keysWidth) : footer.info;
|
|
268
|
+
const info = infoWidth != null ? fitPaneFooterInfo(footer.info, infoWidth - keysWidth, { besideHints: hasHints }) : footer.info;
|
|
251
269
|
|
|
252
270
|
if (!hasInfo && !hasHints) {
|
|
253
271
|
return <Box flexGrow={1} height={1} />;
|
|
@@ -273,7 +291,8 @@ function FooterContent({
|
|
|
273
291
|
{...(infoWidth != null ? { width: infoWidth } : {})}
|
|
274
292
|
>
|
|
275
293
|
{info.map((segment, index) => (
|
|
276
|
-
|
|
294
|
+
// An icon is a few cells that mean nothing cut: the text beside it gives way first.
|
|
295
|
+
<Box key={segment.id} flexDirection="row" flexShrink={segment.icon ? 0 : undefined} marginRight={index === info.length - 1 ? 0 : 1}>
|
|
277
296
|
<SegmentView segment={segment} focused={focused} />
|
|
278
297
|
</Box>
|
|
279
298
|
))}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ContextMenuItem } from "../../../../types/context-menu";
|
|
2
2
|
import { t } from "../../../../i18n";
|
|
3
|
-
import { displayWidth } from "../../../../utils/format";
|
|
3
|
+
import { clipToDisplayWidth, displayWidth } from "../../../../utils/format";
|
|
4
4
|
import { getShortcutHintWidth } from "../../../ui/shortcut-hint";
|
|
5
5
|
|
|
6
6
|
export interface PaneFooterRegistration {
|
|
@@ -23,8 +23,11 @@ export interface PaneFooterRegistration {
|
|
|
23
23
|
export interface PaneFooterSegment {
|
|
24
24
|
id: string;
|
|
25
25
|
parts: PaneFooterPart[];
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Desktop affordance; parts retain the terminal representation. `clock` is
|
|
28
|
+
* data a source delivers late: build it with {@link delayedFooterSegment}.
|
|
29
|
+
*/
|
|
30
|
+
icon?: "warning" | "clock";
|
|
28
31
|
/**
|
|
29
32
|
* The segment only dates the pane's data (`as of 2026-10-05`). A `gloomberb
|
|
30
33
|
* shot` whose status line already dates that data leaves it out, so the
|
|
@@ -52,6 +55,68 @@ export interface PaneFooterSegment {
|
|
|
52
55
|
disabled?: boolean;
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
/** The terminal's mark for data that arrives late, as quote ages carry it (`◷15s`). */
|
|
59
|
+
const DELAYED_FOOTER_GLYPH = "◷";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The footer's one form for data a source delivers late: a yellow clock with
|
|
63
|
+
* `detail` in its tooltip on the desktop, and the clock with the delay
|
|
64
|
+
* (`◷15m`) in the terminal, which has no tooltip, so it takes a few cells
|
|
65
|
+
* rather than a phrase. `onPress` makes it a button, such as an upgrade.
|
|
66
|
+
*/
|
|
67
|
+
export function delayedFooterSegment({
|
|
68
|
+
id,
|
|
69
|
+
detail,
|
|
70
|
+
delay,
|
|
71
|
+
onPress,
|
|
72
|
+
quoteStatus,
|
|
73
|
+
}: {
|
|
74
|
+
id: string;
|
|
75
|
+
/** What is late and by how much, in words: the tooltip and the accessible name. */
|
|
76
|
+
detail: string;
|
|
77
|
+
/** How late, when known (`15m`, `12h`); the terminal draws it after the clock. */
|
|
78
|
+
delay?: string;
|
|
79
|
+
onPress?: () => void;
|
|
80
|
+
quoteStatus?: boolean;
|
|
81
|
+
}): PaneFooterSegment {
|
|
82
|
+
return {
|
|
83
|
+
id,
|
|
84
|
+
icon: "clock",
|
|
85
|
+
label: detail,
|
|
86
|
+
title: detail,
|
|
87
|
+
parts: [{ text: `${DELAYED_FOOTER_GLYPH}${delay ?? ""}`, tone: "warning" }],
|
|
88
|
+
...(onPress ? { onPress } : {}),
|
|
89
|
+
...(quoteStatus ? { quoteStatus } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A pane whose market data is all delayed, by no one figure: the clock alone, its tooltip saying so. */
|
|
94
|
+
export const DELAYED_SEGMENT: PaneFooterSegment = delayedFooterSegment({ id: "delayed", detail: "Delayed market data" });
|
|
95
|
+
|
|
96
|
+
/** A status that opens with how late its data is: `15m delayed`, `mixed real-time and delayed`, `delayed`. */
|
|
97
|
+
const DELAYED_LEAD = /^(\d+[smhd] delayed|mixed real-time and delayed|delayed)(?: · |$)/;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A status segment whose text opens with how late its data is (`15m delayed ·
|
|
101
|
+
* as of 14:47 UTC`) as the footer's clock (see {@link delayedFooterSegment})
|
|
102
|
+
* and the rest of its text; any other segment as it is.
|
|
103
|
+
*/
|
|
104
|
+
export function splitDelayedStatus(segment: PaneFooterSegment): PaneFooterSegment[] {
|
|
105
|
+
const [first, ...more] = segment.parts;
|
|
106
|
+
const match = first ? DELAYED_LEAD.exec(first.text) : null;
|
|
107
|
+
if (!first || !match) return [segment];
|
|
108
|
+
const lead = match[1]!;
|
|
109
|
+
const clock = delayedFooterSegment({
|
|
110
|
+
id: `${segment.id}-delay`,
|
|
111
|
+
detail: lead,
|
|
112
|
+
delay: /^\d/.test(lead) ? lead.slice(0, lead.indexOf(" ")) : undefined,
|
|
113
|
+
quoteStatus: segment.quoteStatus,
|
|
114
|
+
});
|
|
115
|
+
const rest = first.text.slice(match[0].length);
|
|
116
|
+
const parts = rest ? [{ ...first, text: rest }, ...more] : more;
|
|
117
|
+
return parts.length ? [clock, { ...segment, parts }] : [clock];
|
|
118
|
+
}
|
|
119
|
+
|
|
55
120
|
/** Whether the segment only dates the pane's data: marked `asOf`, or reading `as of <date>`. */
|
|
56
121
|
export function datesPaneData(segment: Pick<PaneFooterSegment, "asOf" | "parts">): boolean {
|
|
57
122
|
return segment.asOf ?? /^as of\b/i.test(segment.parts[0]?.text ?? "");
|
|
@@ -127,7 +192,7 @@ function infoTextWidth(segments: readonly PaneFooterSegment[]): number {
|
|
|
127
192
|
* The info segments as they fit `width`: segments with `variants` step down
|
|
128
193
|
* together, one variant at a time, until the row fits or none is left.
|
|
129
194
|
*/
|
|
130
|
-
export function fitPaneFooterInfo(info: PaneFooterSegment[], width: number): PaneFooterSegment[] {
|
|
195
|
+
export function fitPaneFooterInfo(info: PaneFooterSegment[], width: number, { besideHints = true }: { besideHints?: boolean } = {}): PaneFooterSegment[] {
|
|
131
196
|
const stages = Math.max(0, ...info.map((segment) => segment.variants?.length ?? 0));
|
|
132
197
|
let fitted = info;
|
|
133
198
|
for (let stage = 0; stage < stages && infoTextWidth(fitted) > width; stage += 1) {
|
|
@@ -140,9 +205,39 @@ export function fitPaneFooterInfo(info: PaneFooterSegment[], width: number): Pan
|
|
|
140
205
|
} : segment;
|
|
141
206
|
});
|
|
142
207
|
}
|
|
208
|
+
// A trailing status cut mid-word reads as part of the hints beside it ("quote 12h t[x] put"): one that does not
|
|
209
|
+
// fit ends in an ellipsis a column short of them, or is left out when less than half of it would show. A
|
|
210
|
+
// warning, an icon or a pressable segment stays as it is, clipped by the row if it must be. With no hints beside it
|
|
211
|
+
// (a pane picture's footer), the row clips it, so a frame that fits its own status line sees it overflow.
|
|
212
|
+
while (besideHints && fitted.length > 1 && infoTextWidth(fitted) > width) {
|
|
213
|
+
const last = fitted.at(-1)!;
|
|
214
|
+
if (last.icon || last.onPress || last.parts.some((part) => part.tone === "warning" || part.tone === "negative")) break;
|
|
215
|
+
const room = width - infoTextWidth(fitted.slice(0, -1)) - 2;
|
|
216
|
+
if (room * 2 >= infoTextWidth([last])) return [...fitted.slice(0, -1), clipSegment(last, room)];
|
|
217
|
+
fitted = fitted.slice(0, -1);
|
|
218
|
+
}
|
|
143
219
|
return fitted;
|
|
144
220
|
}
|
|
145
221
|
|
|
222
|
+
/** A segment cut to `width` behind an ellipsis, each part keeping its tone; the full text stays as its title. */
|
|
223
|
+
function clipSegment(segment: PaneFooterSegment, width: number): PaneFooterSegment {
|
|
224
|
+
const full = segment.parts.map((part) => part.text).join(" ");
|
|
225
|
+
const clipped = clipToDisplayWidth(full, width);
|
|
226
|
+
if (clipped === full) return segment;
|
|
227
|
+
const body = clipped.slice(0, -1);
|
|
228
|
+
const parts: PaneFooterPart[] = [];
|
|
229
|
+
let offset = 0;
|
|
230
|
+
for (const part of segment.parts) {
|
|
231
|
+
if (offset >= body.length) break;
|
|
232
|
+
parts.push({ ...part, text: body.slice(offset, offset + part.text.length) });
|
|
233
|
+
offset += part.text.length + 1;
|
|
234
|
+
}
|
|
235
|
+
const last = parts.at(-1);
|
|
236
|
+
if (!last) return { ...segment, title: segment.title ?? full, parts: [{ ...segment.parts[0]!, text: clipped }] };
|
|
237
|
+
parts[parts.length - 1] = { ...last, text: `${last.text}…` };
|
|
238
|
+
return { ...segment, title: segment.title ?? full, parts };
|
|
239
|
+
}
|
|
240
|
+
|
|
146
241
|
function moreControl(budget: number): { label: string; width: number } {
|
|
147
242
|
const label = t("More");
|
|
148
243
|
const width = displayWidth(label) + 2;
|
|
@@ -63,10 +63,24 @@ export function NestedPaneTabs({ children }: { children: ReactNode }) {
|
|
|
63
63
|
return <PaneHeaderTabsContext.Provider value={null}>{children}</PaneHeaderTabsContext.Provider>;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/** One id per artwork element, so a strip redraws when a tab's `leading` is a new element and not otherwise. */
|
|
67
|
+
const leadingIds = new WeakMap<object, number>();
|
|
68
|
+
let nextLeadingId = 1;
|
|
69
|
+
function leadingSignature(leading: ReactNode): string | number {
|
|
70
|
+
if (leading === undefined || leading === null) return "";
|
|
71
|
+
if (typeof leading !== "object") return String(leading);
|
|
72
|
+
let id = leadingIds.get(leading);
|
|
73
|
+
if (id === undefined) {
|
|
74
|
+
id = nextLeadingId++;
|
|
75
|
+
leadingIds.set(leading, id);
|
|
76
|
+
}
|
|
77
|
+
return id;
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
function registrationSignature(registration: PaneHeaderTabsRegistration | null): string {
|
|
67
81
|
if (!registration) return "";
|
|
68
82
|
return JSON.stringify([
|
|
69
|
-
registration.tabs.map((tab) => [tab.value, tab.label, tab.disabled === true, !!tab.onClose, tab.fg ?? "", tab.reorderable !== false]),
|
|
83
|
+
registration.tabs.map((tab) => [tab.value, tab.label, tab.disabled === true, !!tab.onClose, tab.fg ?? "", tab.reorderable !== false, leadingSignature(tab.leading)]),
|
|
70
84
|
registration.activeValue,
|
|
71
85
|
registration.focused === true,
|
|
72
86
|
registration.keyboardNavigation !== false,
|
|
@@ -7,6 +7,7 @@ import { useThemeColors } from "../../theme/theme-context";
|
|
|
7
7
|
import { t } from "../../i18n";
|
|
8
8
|
import { Button, ListView, type ListViewItem } from "../ui";
|
|
9
9
|
import type { ButtonProps } from "../ui/button";
|
|
10
|
+
import { ListRowLines } from "../ui/list-row-lines";
|
|
10
11
|
import { TITLEBAR_OVERLAY_HEIGHT_PX } from "../layout/titlebar-overlay";
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -525,17 +526,14 @@ export function OnboardingChoiceList({
|
|
|
525
526
|
selectOnHover
|
|
526
527
|
renderRow={(item, state) => (
|
|
527
528
|
<Box flexDirection="row" alignItems="center" width="100%" minWidth={0}>
|
|
528
|
-
<Box flexDirection="
|
|
529
|
-
<
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
{
|
|
535
|
-
|
|
536
|
-
{item.description ? (
|
|
537
|
-
<Text fg={colors.textMuted} wrapText>{item.description}</Text>
|
|
538
|
-
) : null}
|
|
529
|
+
<Box flexDirection="row" flexGrow={1} minWidth={0}>
|
|
530
|
+
<ListRowLines
|
|
531
|
+
label={item.label}
|
|
532
|
+
description={item.description}
|
|
533
|
+
leading={item.leading}
|
|
534
|
+
labelColor={state.selected ? colors.textBright : colors.text}
|
|
535
|
+
labelBold={state.selected}
|
|
536
|
+
/>
|
|
539
537
|
</Box>
|
|
540
538
|
{item.detail ? (
|
|
541
539
|
<Text fg={colors.borderFocused} attributes={TextAttributes.BOLD} style={{ marginLeft: 10 }}>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createElement,
|
|
2
3
|
useCallback,
|
|
3
4
|
useEffect,
|
|
4
5
|
useMemo,
|
|
@@ -16,6 +17,7 @@ import { tf } from "../../i18n";
|
|
|
16
17
|
import type { useAppLanguage } from "../../i18n/react";
|
|
17
18
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
18
19
|
import type { ListViewItem } from "../ui";
|
|
20
|
+
import { LogoMark } from "../ui/logo-mark";
|
|
19
21
|
import type { PortfolioSub } from "./onboarding-steps";
|
|
20
22
|
import { brokerSetupGuideUrl } from "./portfolio-step/broker-setup-panel";
|
|
21
23
|
import type { useOnboardingBrokerSync } from "./wizard-broker-sync";
|
|
@@ -42,14 +44,15 @@ export function useOnboardingBrokerFields({
|
|
|
42
44
|
(): BrokerOption[] => getConnectableBrokerOptions(buildBrokerDirectory({
|
|
43
45
|
signedIn: signedInBrokers,
|
|
44
46
|
adapters: pluginRegistry.brokers.values(),
|
|
45
|
-
})),
|
|
46
|
-
[pluginRegistry.brokers, signedInBrokers],
|
|
47
|
+
}), pluginRegistry.getBrokerLogo),
|
|
48
|
+
[pluginRegistry.brokers, pluginRegistry.getBrokerLogo, signedInBrokers],
|
|
47
49
|
);
|
|
48
50
|
const brokerChoices = useMemo<ListViewItem[]>(() => brokerOptions.map((broker) => ({
|
|
49
51
|
id: broker.id,
|
|
50
52
|
label: tf("Connect {broker}", { broker: broker.name }),
|
|
51
53
|
// A broker offered two ways shows which way each choice connects.
|
|
52
54
|
description: broker.methodLabel ?? tf("Import positions from {broker}", { broker: broker.name }),
|
|
55
|
+
leading: createElement(LogoMark, { src: broker.logo, name: broker.name, size: "lead" }),
|
|
53
56
|
})), [brokerOptions, language]);
|
|
54
57
|
const activeBrokerFields = useMemo((): BrokerConfigField[] => {
|
|
55
58
|
if (!selectedBrokerId) return [];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { brokerMethodLabel, type BrokerDirectoryEntry } from "../../brokers/directory";
|
|
2
|
+
import { brokerDirectoryLogo, type BrokerPluginLogo } from "../../brokers/logos";
|
|
2
3
|
import type { SignedInBroker } from "../../brokers/signed-in/client";
|
|
3
4
|
import type { BrokerAdapter, BrokerPosition } from "../../types/broker";
|
|
4
5
|
import type { AppConfig, OnboardingProgress, OnboardingStage } from "../../types/config";
|
|
@@ -47,19 +48,25 @@ export type BrokerOption = {
|
|
|
47
48
|
name: string;
|
|
48
49
|
/** How this choice connects, when the same broker is also offered another way. */
|
|
49
50
|
methodLabel?: string;
|
|
51
|
+
/** The broker's logo for the desktop and the web (brokers/logos.ts); null draws its initial. */
|
|
52
|
+
logo?: string | null;
|
|
50
53
|
} & (
|
|
51
54
|
| { adapter: BrokerAdapter; signedIn?: undefined }
|
|
52
55
|
| { signedIn: SignedInBroker; adapter?: undefined }
|
|
53
56
|
);
|
|
54
57
|
|
|
55
|
-
export function getConnectableBrokerOptions(
|
|
58
|
+
export function getConnectableBrokerOptions(
|
|
59
|
+
directory: readonly BrokerDirectoryEntry[],
|
|
60
|
+
pluginLogo: BrokerPluginLogo = () => null,
|
|
61
|
+
): BrokerOption[] {
|
|
56
62
|
const options: BrokerOption[] = [];
|
|
57
63
|
for (const entry of directory) {
|
|
64
|
+
const logo = brokerDirectoryLogo(entry, pluginLogo);
|
|
58
65
|
for (const method of entry.methods) {
|
|
59
66
|
const methodLabel = entry.methods.length > 1 ? brokerMethodLabel(entry, method) : undefined;
|
|
60
67
|
options.push(method.kind === "signed-in"
|
|
61
|
-
? { id: `signed-in:${method.broker.id}`, name: entry.name, methodLabel, signedIn: method.broker }
|
|
62
|
-
: { id: method.adapter.id, name: method.adapter.name, methodLabel, adapter: method.adapter });
|
|
68
|
+
? { id: `signed-in:${method.broker.id}`, name: entry.name, methodLabel, logo, signedIn: method.broker }
|
|
69
|
+
: { id: method.adapter.id, name: method.adapter.name, methodLabel, logo, adapter: method.adapter });
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
72
|
return options;
|
|
@@ -13,9 +13,10 @@ import { useScopedButtonAction } from "./action-scope";
|
|
|
13
13
|
* still reads as a control next to a primary one. `plain` is text that happens
|
|
14
14
|
* to be clickable: no border, no fill. Anything shaped like a row, a table
|
|
15
15
|
* cell, a status bar chip or a link is `plain` — a box drawn around it reads as
|
|
16
|
-
* a control nested in the surface instead of part of it.
|
|
16
|
+
* a control nested in the surface instead of part of it. Use `quiet` for text
|
|
17
|
+
* actions that also need a filled selection/focus state without a frame.
|
|
17
18
|
*/
|
|
18
|
-
export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "plain";
|
|
19
|
+
export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "plain" | "quiet";
|
|
19
20
|
|
|
20
21
|
export interface ButtonProps {
|
|
21
22
|
label: string;
|
|
@@ -25,6 +26,8 @@ export interface ButtonProps {
|
|
|
25
26
|
expanded?: boolean;
|
|
26
27
|
onPress?: () => void;
|
|
27
28
|
variant?: ButtonVariant;
|
|
29
|
+
/** Semantic fill for a primary action, retained while keyboard focused. */
|
|
30
|
+
tone?: "positive" | "negative" | "neutral" | "warning";
|
|
28
31
|
disabled?: boolean;
|
|
29
32
|
/**
|
|
30
33
|
* The action it started is still running (a retry waiting for its answer).
|
|
@@ -56,13 +59,15 @@ export interface ButtonProps {
|
|
|
56
59
|
stopPropagation?: boolean;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
function resolveButtonColors(variant: ButtonVariant, active: boolean, disabled: boolean, colors: ThemeColors): { bg?: string; fg: string } {
|
|
62
|
+
function resolveButtonColors(variant: ButtonVariant, active: boolean, disabled: boolean, colors: ThemeColors, tone?: ButtonProps["tone"]): { bg?: string; fg: string } {
|
|
63
|
+
if (variant === "quiet") return { bg: active ? colors.selected : undefined, fg: disabled ? colors.textMuted : active ? colors.textBright : colors.textDim };
|
|
60
64
|
if (variant === "plain") {
|
|
61
65
|
return { fg: disabled ? colors.textMuted : active ? colors.textBright : colors.textDim };
|
|
62
66
|
}
|
|
63
67
|
if (disabled) {
|
|
64
68
|
return { bg: colors.panel, fg: colors.textMuted };
|
|
65
69
|
}
|
|
70
|
+
if (tone && variant === "primary") return { bg: colors[tone], fg: colors.bg };
|
|
66
71
|
if (active) {
|
|
67
72
|
return { bg: colors.selected, fg: colors.selectedText };
|
|
68
73
|
}
|
|
@@ -96,6 +101,7 @@ export function Button({
|
|
|
96
101
|
expanded,
|
|
97
102
|
onPress: pressAction,
|
|
98
103
|
variant = "secondary",
|
|
104
|
+
tone,
|
|
99
105
|
disabled = false,
|
|
100
106
|
pending = false,
|
|
101
107
|
pendingLabel,
|
|
@@ -138,6 +144,7 @@ export function Button({
|
|
|
138
144
|
expanded={expanded}
|
|
139
145
|
onPress={onPress}
|
|
140
146
|
variant={variant}
|
|
147
|
+
tone={tone}
|
|
141
148
|
disabled={inert}
|
|
142
149
|
pending={pending}
|
|
143
150
|
active={active}
|
|
@@ -152,7 +159,7 @@ export function Button({
|
|
|
152
159
|
);
|
|
153
160
|
}
|
|
154
161
|
|
|
155
|
-
const palette = resolveButtonColors(variant, active, inert, colors);
|
|
162
|
+
const palette = resolveButtonColors(variant, active, inert, colors, tone);
|
|
156
163
|
|
|
157
164
|
return (
|
|
158
165
|
<Box
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from "react";
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Box, Text, useUiHost } from "../../ui";
|
|
3
3
|
import { type PromptContext, useDialogKeyboard } from "../../ui/dialog";
|
|
4
4
|
import { useThemeColors } from "../../theme/theme-context";
|
|
@@ -61,6 +61,9 @@ export function ChoiceDialog({
|
|
|
61
61
|
);
|
|
62
62
|
const selectedIndex = clampIndex(index, choices.length, -1);
|
|
63
63
|
const selectedChoice = selectedIndex >= 0 ? choices[selectedIndex] : undefined;
|
|
64
|
+
// Terminal input can deliver navigation and Enter before React renders.
|
|
65
|
+
const cursor = useRef(selectedIndex);
|
|
66
|
+
const selectIndex = (next: number) => { cursor.current = next; setIndex(next); };
|
|
64
67
|
const items = useMemo<ListViewItem[]>(() => choices.map((choice) => ({
|
|
65
68
|
id: choice.id,
|
|
66
69
|
label: choice.label,
|
|
@@ -72,7 +75,7 @@ export function ChoiceDialog({
|
|
|
72
75
|
const width = useMemo(() => choiceDialogWidth(title, choices), [choices, title]);
|
|
73
76
|
|
|
74
77
|
useEffect(() => {
|
|
75
|
-
|
|
78
|
+
selectIndex(clampIndex(cursor.current, choices.length, -1));
|
|
76
79
|
}, [choices.length]);
|
|
77
80
|
|
|
78
81
|
const activateChoice = (choice: ChoiceDialogChoice | undefined) => {
|
|
@@ -84,9 +87,9 @@ export function ChoiceDialog({
|
|
|
84
87
|
event.stopPropagation();
|
|
85
88
|
const move = listCursorMove(event, MAX_VISIBLE_CHOICE_ROWS - 1);
|
|
86
89
|
if (move) {
|
|
87
|
-
|
|
90
|
+
selectIndex(move(choices, cursor.current));
|
|
88
91
|
} else if (event.name === "enter" || event.name === "return") {
|
|
89
|
-
activateChoice(
|
|
92
|
+
activateChoice(choices[clampIndex(cursor.current, choices.length, -1)]);
|
|
90
93
|
} else if (event.name === "escape") {
|
|
91
94
|
resolve("");
|
|
92
95
|
}
|
|
@@ -106,7 +109,7 @@ export function ChoiceDialog({
|
|
|
106
109
|
height={Math.min(Math.max(items.length, 1), MAX_VISIBLE_CHOICE_ROWS)}
|
|
107
110
|
scrollable={items.length > MAX_VISIBLE_CHOICE_ROWS}
|
|
108
111
|
selectOnHover
|
|
109
|
-
onSelect={
|
|
112
|
+
onSelect={selectIndex}
|
|
110
113
|
onActivate={(_, nextIndex) => activateChoice(choices[nextIndex])}
|
|
111
114
|
/>
|
|
112
115
|
<Box height={1} />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { t } from "../../i18n";
|
|
3
3
|
import { blendHex, hoverBg } from "../../theme/colors";
|
|
4
|
+
import { blendForContrast, higherContrast } from "../../theme/color-utils";
|
|
4
5
|
import { useThemeColors } from "../../theme/theme-context";
|
|
5
6
|
import { Box, Text, TextAttributes, useUiCapabilities } from "../../ui";
|
|
6
7
|
import { truncateToDisplayWidth } from "../../utils/format";
|
|
@@ -62,6 +63,7 @@ export interface KeyValueRowProps {
|
|
|
62
63
|
width?: number;
|
|
63
64
|
labelWidth?: number;
|
|
64
65
|
emphasis?: boolean;
|
|
66
|
+
align?: "left" | "right";
|
|
65
67
|
/**
|
|
66
68
|
* Opens what the value names (a security's DES, a function): the whole row is the target, and inside a
|
|
67
69
|
* `PaneLinkMenu` the pane menu lists it as `link.label`, so the keyboard reaches what a click opens.
|
|
@@ -118,7 +120,7 @@ export function KeyValueRow(props: KeyValueRowProps) {
|
|
|
118
120
|
* width in characters, the value and detail taking the rest of the row and
|
|
119
121
|
* clipped in CSS where they run out of it.
|
|
120
122
|
*/
|
|
121
|
-
function NativeKeyValueRow({ label, value, detail, tone, color, width, labelWidth, emphasis = true, link }: KeyValueRowProps) {
|
|
123
|
+
function NativeKeyValueRow({ label, value, detail, tone, color, width, labelWidth, emphasis = true, align = "left", link }: KeyValueRowProps) {
|
|
122
124
|
const colors = useThemeColors();
|
|
123
125
|
usePaneLinkMenuEntry(link ? `key-value:${label}:${value}` : null, link?.label ?? "", () => link?.onOpen());
|
|
124
126
|
const target = link ? {
|
|
@@ -137,7 +139,7 @@ function NativeKeyValueRow({ label, value, detail, tone, color, width, labelWidt
|
|
|
137
139
|
<Text
|
|
138
140
|
fg={color ?? statToneColor(tone, colors) ?? colors.text}
|
|
139
141
|
attributes={emphasis ? TextAttributes.BOLD : undefined}
|
|
140
|
-
style={{ ...NATIVE_CLIP_STYLE, flex: detail ? "0 1 auto" : "1 1 auto" }}
|
|
142
|
+
style={{ ...NATIVE_CLIP_STYLE, flex: detail ? "0 1 auto" : "1 1 auto", textAlign: align }}
|
|
141
143
|
>
|
|
142
144
|
{value}
|
|
143
145
|
</Text>
|
|
@@ -146,7 +148,7 @@ function NativeKeyValueRow({ label, value, detail, tone, color, width, labelWidt
|
|
|
146
148
|
);
|
|
147
149
|
}
|
|
148
150
|
|
|
149
|
-
function TerminalKeyValueRow({ label, value, detail, tone, color, width, labelWidth, emphasis = true, link }: KeyValueRowProps) {
|
|
151
|
+
function TerminalKeyValueRow({ label, value, detail, tone, color, width, labelWidth, emphasis = true, align = "left", link }: KeyValueRowProps) {
|
|
150
152
|
const colors = useThemeColors();
|
|
151
153
|
usePaneLinkMenuEntry(link ? `key-value:${label}:${value}` : null, link?.label ?? "", () => link?.onOpen());
|
|
152
154
|
const target = link ? {
|
|
@@ -169,7 +171,7 @@ function TerminalKeyValueRow({ label, value, detail, tone, color, width, labelWi
|
|
|
169
171
|
<Box width={labelColumns} flexShrink={0} overflow="hidden">
|
|
170
172
|
<Text fg={colors.textDim}>{t(label)}</Text>
|
|
171
173
|
</Box>
|
|
172
|
-
<Box width={valueWidth} flexShrink={0} overflow="hidden">
|
|
174
|
+
<Box width={valueWidth} flexShrink={0} overflow="hidden" alignItems={align === "right" ? "flex-end" : undefined}>
|
|
173
175
|
<Text fg={color ?? statToneColor(tone, colors) ?? colors.text} attributes={emphasis ? TextAttributes.BOLD : undefined}>
|
|
174
176
|
{valueWidth === undefined ? value : truncateToDisplayWidth(value, valueWidth)}
|
|
175
177
|
</Text>
|
|
@@ -207,10 +209,13 @@ export function Badge({ label, tone = "neutral", color, variant = "subtle" }: Ba
|
|
|
207
209
|
const accent = color ?? (tone === "neutral" ? colors.textDim : tone === "accent" ? colors.borderFocused : colors[tone]);
|
|
208
210
|
const solid = variant === "solid";
|
|
209
211
|
const neutral = solid && tone === "neutral" && !color;
|
|
212
|
+
const background = neutral ? colors.selected : solid ? accent : blendHex(colors.bg, accent, BADGE_TINT);
|
|
213
|
+
const baseForeground = neutral ? colors.selectedText : solid ? colors.bg : accent;
|
|
214
|
+
const foreground = blendForContrast(baseForeground, background, higherContrast("#000000", "#ffffff", background), 4.5);
|
|
210
215
|
const chip = (
|
|
211
|
-
<Box height={nativePaneChrome ? undefined : 1} paddingX={1} backgroundColor={
|
|
216
|
+
<Box height={nativePaneChrome ? undefined : 1} paddingX={1} backgroundColor={background}
|
|
212
217
|
data-gloom-ui="badge" justifyContent={nativePaneChrome ? "center" : undefined} style={nativePaneChrome ? NATIVE_BADGE_STYLE : undefined}>
|
|
213
|
-
<Text fg={
|
|
218
|
+
<Text fg={foreground} attributes={TextAttributes.BOLD} style={nativePaneChrome ? NATIVE_BADGE_TEXT_STYLE : undefined}>{t(label)}</Text>
|
|
214
219
|
</Box>
|
|
215
220
|
);
|
|
216
221
|
return nativePaneChrome ? <Box height={1} flexShrink={0} justifyContent="center">{chip}</Box> : chip;
|