gloomberb 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -2
- package/bin/gloomberb +1 -1
- package/package.json +1 -1
- package/src/app/runtime/desktop-menu.ts +5 -1
- package/src/app/runtime/ticker-refresh.ts +4 -1
- package/src/app.tsx +20 -8
- package/src/brokers/account-cache.ts +1 -1
- package/src/brokers/sync-broker-instance.test.ts +107 -0
- package/src/brokers/sync-broker-instance.ts +22 -10
- package/src/capabilities/registry.ts +1 -0
- package/src/capabilities/types.ts +23 -0
- package/src/cli/commands/api.ts +119 -0
- package/src/cli/commands/automation.ts +173 -0
- package/src/cli/commands/command-utils.ts +53 -0
- package/src/cli/commands/market.ts +527 -0
- package/src/cli/commands/overview.ts +222 -0
- package/src/cli/commands/search.ts +39 -0
- package/src/cli/commands/system.ts +553 -0
- package/src/cli/commands/ticker.ts +104 -12
- package/src/cli/context.ts +35 -2
- package/src/cli/desktop-pane-shot.ts +2 -1
- package/src/cli/entry.ts +51 -0
- package/src/cli/errors.ts +56 -5
- package/src/cli/index.ts +73 -11
- package/src/cli/options.test.ts +38 -0
- package/src/cli/options.ts +95 -0
- package/src/cli/pane-functions/index.ts +10 -1
- package/src/cli/pane-functions/screenshot.ts +13 -1
- package/src/cli/registry.test.ts +40 -0
- package/src/cli/registry.ts +46 -5
- package/src/cli/result.test.ts +82 -0
- package/src/cli/result.ts +149 -0
- package/src/cli/types.ts +11 -0
- package/src/cli.test.ts +11 -11
- package/src/components/chart/bar-chart-renderer.test.ts +108 -3
- package/src/components/chart/bar-chart-renderer.ts +226 -27
- package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
- package/src/components/chart/comparison-stock-chart/legend.tsx +4 -0
- package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
- package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
- package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
- package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
- package/src/components/chart/comparison-stock-chart/view.tsx +12 -2
- package/src/components/chart/core/pane-settings.test.ts +27 -0
- package/src/components/chart/core/pane-settings.ts +60 -1
- package/src/components/chart/core/pointer.ts +34 -6
- package/src/components/chart/price-axis-labels.tsx +3 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +97 -0
- package/src/components/chart/static/bar-chart-surface.tsx +151 -28
- package/src/components/chart/static/chart/axis-overlays.tsx +243 -0
- package/src/components/chart/static/chart/surface.test.tsx +92 -0
- package/src/components/chart/static/chart/surface.tsx +167 -24
- package/src/components/chart/stock-chart/index.test.ts +25 -0
- package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
- package/src/components/chart/stock-chart/settings.ts +7 -3
- package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
- package/src/components/command-bar/layout-items/current-layout.ts +5 -1
- package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
- package/src/components/command-bar/surface/pane.test.tsx +76 -0
- package/src/components/data-table/view.tsx +13 -2
- package/src/components/feed-data-table/stack-view.tsx +9 -1
- package/src/components/index.ts +1 -1
- package/src/components/input-search-bar.test.tsx +103 -0
- package/src/components/input-search-bar.tsx +16 -0
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/floating-pane.tsx +3 -2
- package/src/components/layout/pane/content.tsx +20 -8
- package/src/components/layout/pane/footer/registration.tsx +6 -2
- package/src/components/layout/pane/frame.tsx +4 -4
- package/src/components/layout/pane/header.tsx +40 -4
- package/src/components/layout/pane/index.tsx +3 -1
- package/src/components/layout/pane/sizing.test.ts +43 -0
- package/src/components/layout/pane/sizing.ts +13 -4
- package/src/components/layout/shell/active-drag.ts +5 -1
- package/src/components/layout/shell/cursor-occlusion.ts +196 -0
- package/src/components/layout/shell/drag/runtime.ts +28 -3
- package/src/components/layout/shell/fullscreen.ts +10 -0
- package/src/components/layout/shell/index.test.tsx +487 -4
- package/src/components/layout/shell/index.tsx +165 -4
- package/src/components/layout/shell/layout-state.test.ts +33 -0
- package/src/components/layout/shell/layout-state.ts +4 -9
- package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
- package/src/components/layout/shell/pane/layers.tsx +34 -16
- package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
- package/src/components/layout/shell/shortcuts.ts +4 -0
- package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
- package/src/components/layout/shell/visible-layout.ts +17 -0
- package/src/components/layout/shell/window-mode/index.ts +11 -3
- package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
- package/src/components/layout/status-bar.test.tsx +111 -1
- package/src/components/layout/status-bar.tsx +37 -7
- package/src/components/layout/transient-layout.tsx +57 -0
- package/src/components/layout/window-edit/mode.test.ts +76 -5
- package/src/components/layout/window-edit/mode.ts +47 -2
- package/src/components/layout/window-edit/presentation.ts +2 -1
- package/src/components/onboarding/onboarding-steps.tsx +8 -28
- package/src/components/onboarding/onboarding-wizard.test.tsx +13 -12
- package/src/components/onboarding/onboarding-wizard.tsx +2 -4
- package/src/components/onboarding/portfolio-step/index.tsx +0 -12
- package/src/components/onboarding/portfolio-step/types.ts +1 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -5
- package/src/components/price-performance.tsx +9 -1
- package/src/components/table-view-shared.tsx +12 -4
- package/src/components/ui/data-table/opentui/index.tsx +25 -12
- package/src/components/ui/data-table/types.ts +4 -0
- package/src/components/ui/fields.tsx +10 -3
- package/src/components/ui/page-stack-view.tsx +6 -2
- package/src/components/ui/table-layout.test.ts +25 -0
- package/src/components/ui/table-layout.ts +22 -6
- package/src/components/ui/tabs.tsx +26 -10
- package/src/components/ui/ui.test.tsx +87 -0
- package/src/core/app-services.ts +7 -4
- package/src/core/state/app/layout-reducer.ts +2 -2
- package/src/data/config/store/index.ts +1 -2
- package/src/plugins/builtin/analytics/index.test.tsx +6 -2
- package/src/plugins/builtin/analytics/pane-model.ts +43 -8
- package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
- package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
- package/src/plugins/builtin/comparison-chart/index.test.tsx +130 -6
- package/src/plugins/builtin/kelly-sizer/constants.ts +3 -0
- package/src/plugins/builtin/kelly-sizer/fields.ts +214 -0
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +197 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +47 -0
- package/src/plugins/builtin/kelly-sizer/model.test.ts +152 -0
- package/src/plugins/builtin/kelly-sizer/model.ts +580 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +558 -0
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +71 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +142 -0
- package/src/plugins/builtin/kelly-sizer/state.ts +67 -0
- package/src/plugins/builtin/kelly-sizer/test-support.tsx +174 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +156 -0
- package/src/plugins/builtin/kelly-sizer/view.test.tsx +77 -0
- package/src/plugins/builtin/kelly-sizer/view.tsx +375 -0
- package/src/plugins/builtin/layout-manager/index.tsx +5 -1
- package/src/plugins/builtin/news/wire/read-state.ts +9 -32
- package/src/plugins/builtin/options/view.test.tsx +141 -4
- package/src/plugins/builtin/options/view.tsx +76 -15
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +26 -1
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +14 -0
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
- package/src/plugins/builtin/portfolio-list/index.test.ts +23 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +3 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
- package/src/plugins/builtin/portfolio-list/summary/index.test.ts +59 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +36 -5
- package/src/plugins/builtin/shared/read-state.ts +43 -0
- package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
- package/src/plugins/builtin/substack/article-detail.tsx +39 -15
- package/src/plugins/builtin/substack/article-stack.tsx +6 -2
- package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
- package/src/plugins/builtin/substack/pane.tsx +8 -3
- package/src/plugins/builtin/substack/read-state.ts +55 -0
- package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +149 -48
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +16 -0
- package/src/plugins/builtin/ticker-detail/index.test.tsx +143 -0
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/ibkr/broker-adapter.ts +19 -0
- package/src/plugins/ibkr/config.test.ts +31 -2
- package/src/plugins/ibkr/config.ts +9 -2
- package/src/plugins/ibkr/flex/index.test.ts +18 -9
- package/src/plugins/ibkr/flex/index.ts +30 -0
- package/src/plugins/ibkr/flex/statement-client.ts +68 -14
- package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
- package/src/plugins/ibkr/gateway/account-summary.ts +1 -0
- package/src/plugins/ibkr/gateway/service/index.test.ts +8 -0
- package/src/plugins/pane-manager/docking.ts +9 -2
- package/src/plugins/pane-manager/gridlock.ts +13 -5
- package/src/plugins/pane-manager/layout-state.ts +25 -0
- package/src/plugins/pane-manager/queries.ts +8 -2
- package/src/plugins/pane-manager.test.ts +33 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +10 -4
- package/src/plugins/prediction-markets/controller/index.ts +2 -0
- package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
- package/src/plugins/prediction-markets/detail/book.tsx +141 -96
- package/src/plugins/prediction-markets/detail/overview.tsx +3 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +71 -64
- package/src/plugins/prediction-markets/detail/shared.tsx +19 -0
- package/src/plugins/prediction-markets/detail/trades.tsx +64 -19
- package/src/plugins/prediction-markets/detail.test.tsx +50 -0
- package/src/plugins/prediction-markets/pane.test.tsx +75 -0
- package/src/plugins/prediction-markets/pane.tsx +71 -23
- package/src/plugins/prediction-markets/test-helpers.tsx +42 -23
- package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/index.ts +13 -0
- package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
- package/src/renderers/electrobun/bun/window/focus.ts +5 -0
- package/src/renderers/electrobun/view/app-services.ts +6 -4
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +13 -2
- package/src/renderers/electrobun/view/data-table/index.tsx +19 -4
- package/src/renderers/electrobun/view/data-table/row.tsx +30 -6
- package/src/renderers/electrobun/view/desktop/controls.tsx +11 -5
- package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
- package/src/renderers/electrobun/view/host/input.tsx +115 -18
- package/src/renderers/electrobun/view/host/tabs.tsx +7 -2
- package/src/renderers/electrobun/view/host/text.test.ts +22 -0
- package/src/renderers/electrobun/view/host/text.tsx +24 -20
- package/src/renderers/electrobun/view/styles.css +11 -0
- package/src/renderers/electrobun/view/ui-host.tsx +1 -1
- package/src/renderers/opentui/host.tsx +17 -1
- package/src/renderers/opentui/start.tsx +13 -5
- package/src/renderers/opentui/ui-host.tsx +57 -25
- package/src/sources/provider-router/batches.ts +16 -2
- package/src/sources/provider-router/financial-routes.ts +5 -0
- package/src/sources/provider-router/financials.ts +11 -23
- package/src/sources/provider-router/index.test.ts +189 -0
- package/src/sources/provider-router/primary.ts +3 -2
- package/src/sources/sec-edgar.test.ts +83 -0
- package/src/sources/sec-edgar.ts +231 -0
- package/src/sources/yahoo-finance.test.ts +51 -0
- package/src/sources/yahoo-finance.ts +49 -1
- package/src/state/app/context/index.test.tsx +5 -8
- package/src/state/app/input-capture.ts +12 -0
- package/src/types/broker.ts +1 -0
- package/src/types/config.ts +10 -0
- package/src/types/plugin.ts +28 -0
- package/src/types/trading.ts +2 -0
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +6 -0
- package/src/utils/financial-statements.ts +23 -0
- package/src/utils/search-focus-navigation.ts +24 -0
- package/src/version.ts +1 -1
- package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +0 -62
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Box, ScrollBox, Text } from "../../../ui";
|
|
2
2
|
import { TextAttributes, type ScrollBoxRenderable } from "../../../ui";
|
|
3
3
|
import type { RefObject } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { Tabs } from "../../../components";
|
|
5
5
|
import { EmptyState } from "../../../components/ui/status";
|
|
6
6
|
import { colors } from "../../../theme/colors";
|
|
7
7
|
import { padTo } from "../../../utils/format";
|
|
@@ -37,8 +37,11 @@ interface MetricCell {
|
|
|
37
37
|
function MetricLabelRow({ metrics }: { metrics: MetricCell[] }) {
|
|
38
38
|
return (
|
|
39
39
|
<Box flexDirection="row" height={1}>
|
|
40
|
-
{metrics.map((metric) => (
|
|
41
|
-
<Box
|
|
40
|
+
{metrics.map((metric, index) => (
|
|
41
|
+
<Box
|
|
42
|
+
key={metric.label}
|
|
43
|
+
width={metric.width + (index === metrics.length - 1 ? 0 : 1)}
|
|
44
|
+
>
|
|
42
45
|
<Text fg={colors.textDim}>{padTo(metric.label, metric.width)}</Text>
|
|
43
46
|
</Box>
|
|
44
47
|
))}
|
|
@@ -49,8 +52,11 @@ function MetricLabelRow({ metrics }: { metrics: MetricCell[] }) {
|
|
|
49
52
|
function MetricValueRow({ metrics }: { metrics: MetricCell[] }) {
|
|
50
53
|
return (
|
|
51
54
|
<Box flexDirection="row" height={1}>
|
|
52
|
-
{metrics.map((metric) => (
|
|
53
|
-
<Box
|
|
55
|
+
{metrics.map((metric, index) => (
|
|
56
|
+
<Box
|
|
57
|
+
key={metric.label}
|
|
58
|
+
width={metric.width + (index === metrics.length - 1 ? 0 : 1)}
|
|
59
|
+
>
|
|
54
60
|
<Text
|
|
55
61
|
fg={metric.color ?? colors.textBright}
|
|
56
62
|
attributes={TextAttributes.BOLD}
|
|
@@ -108,8 +114,6 @@ export function PredictionMarketDetailPane({
|
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
const summaryMetrics = detail?.summary ?? selectedSummary;
|
|
111
|
-
const detailTitle =
|
|
112
|
-
selectedRow?.kind === "group" ? selectedRow.title : summaryMetrics.title;
|
|
113
117
|
const detailSubtitleParts: string[] = [];
|
|
114
118
|
if (selectedRow?.kind === "group") {
|
|
115
119
|
if (summaryMetrics.category) detailSubtitleParts.push(summaryMetrics.category);
|
|
@@ -126,18 +130,18 @@ export function PredictionMarketDetailPane({
|
|
|
126
130
|
);
|
|
127
131
|
if (endsLabel !== "—") detailSubtitleParts.push(endsLabel);
|
|
128
132
|
const detailSubtitle = detailSubtitleParts.join(" · ");
|
|
129
|
-
const
|
|
133
|
+
const metrics: MetricCell[] = [
|
|
130
134
|
{
|
|
131
135
|
label: "YES",
|
|
132
136
|
value: formatPredictionProbability(summaryMetrics.yesPrice),
|
|
133
137
|
color: getPredictionProbabilityColor(summaryMetrics.yesPrice),
|
|
134
|
-
width:
|
|
138
|
+
width: 6,
|
|
135
139
|
},
|
|
136
140
|
{
|
|
137
141
|
label: "NO",
|
|
138
142
|
value: formatPredictionProbability(summaryMetrics.noPrice),
|
|
139
143
|
color: getPredictionProbabilityColor(summaryMetrics.noPrice),
|
|
140
|
-
width:
|
|
144
|
+
width: 6,
|
|
141
145
|
},
|
|
142
146
|
{
|
|
143
147
|
label: "24H VOL",
|
|
@@ -145,7 +149,7 @@ export function PredictionMarketDetailPane({
|
|
|
145
149
|
summaryMetrics.volume24h,
|
|
146
150
|
summaryMetrics.volume24hUnit ?? "usd",
|
|
147
151
|
),
|
|
148
|
-
width:
|
|
152
|
+
width: 8,
|
|
149
153
|
},
|
|
150
154
|
{
|
|
151
155
|
label: "TOTAL VOL",
|
|
@@ -153,28 +157,26 @@ export function PredictionMarketDetailPane({
|
|
|
153
157
|
summaryMetrics.totalVolume,
|
|
154
158
|
summaryMetrics.totalVolumeUnit ?? "usd",
|
|
155
159
|
),
|
|
156
|
-
width:
|
|
160
|
+
width: 9,
|
|
157
161
|
},
|
|
158
|
-
];
|
|
159
|
-
const secondaryMetrics: MetricCell[] = [
|
|
160
162
|
{
|
|
161
163
|
label: "OI",
|
|
162
164
|
value: formatPredictionMetric(
|
|
163
165
|
summaryMetrics.openInterest,
|
|
164
166
|
summaryMetrics.openInterestUnit ?? "usd",
|
|
165
167
|
),
|
|
166
|
-
width:
|
|
168
|
+
width: 7,
|
|
167
169
|
},
|
|
168
170
|
{
|
|
169
171
|
label: "SPREAD",
|
|
170
172
|
value: formatPredictionSpread(summaryMetrics.spread),
|
|
171
|
-
width:
|
|
173
|
+
width: 7,
|
|
172
174
|
},
|
|
173
175
|
{
|
|
174
176
|
label: "LAST",
|
|
175
177
|
value: formatPredictionProbability(summaryMetrics.lastTradePrice),
|
|
176
178
|
color: colors.textBright,
|
|
177
|
-
width:
|
|
179
|
+
width: 7,
|
|
178
180
|
},
|
|
179
181
|
];
|
|
180
182
|
const relatedSiblings =
|
|
@@ -186,33 +188,25 @@ export function PredictionMarketDetailPane({
|
|
|
186
188
|
0,
|
|
187
189
|
Math.max(Math.min(Math.floor((detailWidth - 10) / 18), 3), 0),
|
|
188
190
|
) ?? []);
|
|
189
|
-
const headerHeight = detailSubtitle.length > 0 ?
|
|
191
|
+
const headerHeight = detailSubtitle.length > 0 ? 2 : 0;
|
|
190
192
|
const detailLoading = detailLoadCount > 0 && !detail;
|
|
191
|
-
const titleColor = focused ? colors.textBright : colors.text;
|
|
192
193
|
const detailTextWidth = Math.max(detailWidth, 12);
|
|
193
194
|
|
|
194
195
|
return (
|
|
195
196
|
<>
|
|
196
|
-
|
|
197
|
-
<Box flexDirection="
|
|
198
|
-
<Text fg={titleColor} attributes={TextAttributes.BOLD}>
|
|
199
|
-
{truncatePredictionText(detailTitle, detailTextWidth)}
|
|
200
|
-
</Text>
|
|
201
|
-
</Box>
|
|
202
|
-
{detailSubtitle.length > 0 && (
|
|
197
|
+
{headerHeight > 0 && (
|
|
198
|
+
<Box flexDirection="column" height={headerHeight} paddingBottom={1}>
|
|
203
199
|
<Box flexDirection="row" height={1}>
|
|
204
200
|
<Text fg={colors.textDim}>
|
|
205
201
|
{truncatePredictionText(detailSubtitle, detailTextWidth)}
|
|
206
202
|
</Text>
|
|
207
203
|
</Box>
|
|
208
|
-
|
|
209
|
-
|
|
204
|
+
</Box>
|
|
205
|
+
)}
|
|
210
206
|
|
|
211
|
-
<Box flexDirection="column" height={
|
|
212
|
-
<MetricLabelRow metrics={
|
|
213
|
-
<MetricValueRow metrics={
|
|
214
|
-
<MetricLabelRow metrics={secondaryMetrics} />
|
|
215
|
-
<MetricValueRow metrics={secondaryMetrics} />
|
|
207
|
+
<Box flexDirection="column" height={3} paddingBottom={1}>
|
|
208
|
+
<MetricLabelRow metrics={metrics} />
|
|
209
|
+
<MetricValueRow metrics={metrics} />
|
|
216
210
|
</Box>
|
|
217
211
|
|
|
218
212
|
{relatedSiblings.length > 0 && (
|
|
@@ -260,43 +254,56 @@ export function PredictionMarketDetailPane({
|
|
|
260
254
|
</Box>
|
|
261
255
|
)}
|
|
262
256
|
|
|
263
|
-
|
|
264
|
-
{
|
|
265
|
-
|
|
266
|
-
<
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
summary={summaryMetrics}
|
|
279
|
-
/>
|
|
280
|
-
)}
|
|
257
|
+
{detailTab === "overview" || detailTab === "rules" ? (
|
|
258
|
+
<ScrollBox ref={scrollRef} flexGrow={1} scrollY>
|
|
259
|
+
{detailTab === "overview" && (
|
|
260
|
+
<PredictionMarketOverviewView
|
|
261
|
+
detail={detail}
|
|
262
|
+
detailWidth={detailWidth}
|
|
263
|
+
height={height}
|
|
264
|
+
historyRange={historyRange}
|
|
265
|
+
loading={detailLoading}
|
|
266
|
+
onHistoryRangeChange={onHistoryRangeChange}
|
|
267
|
+
onSelectMarket={onSelectMarket}
|
|
268
|
+
selectedRow={selectedRow}
|
|
269
|
+
summary={summaryMetrics}
|
|
270
|
+
/>
|
|
271
|
+
)}
|
|
281
272
|
|
|
282
|
-
|
|
273
|
+
{detailTab === "rules" && (
|
|
274
|
+
<PredictionMarketRulesView
|
|
275
|
+
detailWidth={detailTextWidth}
|
|
276
|
+
rules={detail?.rules ?? []}
|
|
277
|
+
/>
|
|
278
|
+
)}
|
|
279
|
+
</ScrollBox>
|
|
280
|
+
) : null}
|
|
281
|
+
|
|
282
|
+
{detailTab === "book" && (
|
|
283
|
+
detail ? (
|
|
283
284
|
<PredictionMarketBookView
|
|
284
285
|
detail={detail}
|
|
286
|
+
focused={focused}
|
|
285
287
|
onPreviewOrder={onPreviewOrder}
|
|
288
|
+
width={detailWidth}
|
|
286
289
|
/>
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
) : (
|
|
291
|
+
<Box flexGrow={1} justifyContent="center">
|
|
292
|
+
<EmptyState
|
|
293
|
+
title={detailLoading ? "Loading order book." : "No order book."}
|
|
294
|
+
hint="This venue did not return current order book depth."
|
|
295
|
+
/>
|
|
296
|
+
</Box>
|
|
297
|
+
)
|
|
298
|
+
)}
|
|
292
299
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
+
{detailTab === "trades" && (
|
|
301
|
+
<PredictionMarketTradesView
|
|
302
|
+
focused={focused}
|
|
303
|
+
trades={detail?.trades ?? []}
|
|
304
|
+
width={detailWidth}
|
|
305
|
+
/>
|
|
306
|
+
)}
|
|
300
307
|
</>
|
|
301
308
|
);
|
|
302
309
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { Box } from "../../../ui";
|
|
2
2
|
import { colors } from "../../../theme/colors";
|
|
3
3
|
import { ExternalLinkText } from "../../../components/ui/external-link";
|
|
4
|
+
import type {
|
|
5
|
+
PredictionListRow,
|
|
6
|
+
PredictionMarketDetail,
|
|
7
|
+
PredictionMarketSummary,
|
|
8
|
+
} from "../types";
|
|
4
9
|
|
|
5
10
|
export function truncatePredictionText(
|
|
6
11
|
value: string,
|
|
@@ -11,6 +16,20 @@ export function truncatePredictionText(
|
|
|
11
16
|
return `${value.slice(0, maxLength - 3)}...`;
|
|
12
17
|
}
|
|
13
18
|
|
|
19
|
+
export function resolvePredictionDetailTitle({
|
|
20
|
+
detail,
|
|
21
|
+
selectedRow,
|
|
22
|
+
selectedSummary,
|
|
23
|
+
}: {
|
|
24
|
+
detail: PredictionMarketDetail | null;
|
|
25
|
+
selectedRow: PredictionListRow | null;
|
|
26
|
+
selectedSummary: PredictionMarketSummary | null;
|
|
27
|
+
}): string | undefined {
|
|
28
|
+
if (!selectedSummary) return undefined;
|
|
29
|
+
const summary = detail?.summary ?? selectedSummary;
|
|
30
|
+
return selectedRow?.kind === "group" ? selectedRow.title : summary.title;
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
export function SummaryLink({
|
|
15
34
|
url,
|
|
16
35
|
maxLength,
|
|
@@ -1,31 +1,76 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DataTableView, type DataTableColumn } from "../../../components";
|
|
2
2
|
import { colors } from "../../../theme/colors";
|
|
3
|
-
import { formatNumber
|
|
3
|
+
import { formatNumber } from "../../../utils/format";
|
|
4
4
|
import { formatPredictionProbability } from "../metrics";
|
|
5
5
|
import type { PredictionTrade } from "../types";
|
|
6
6
|
|
|
7
|
+
type TradeColumnId = "time" | "side" | "outcome" | "price" | "size";
|
|
8
|
+
type TradeColumn = DataTableColumn & { id: TradeColumnId };
|
|
9
|
+
|
|
10
|
+
const TRADE_COLUMNS: TradeColumn[] = [
|
|
11
|
+
{ id: "time", label: "TIME", width: 16 },
|
|
12
|
+
{ id: "side", label: "SIDE", width: 6 },
|
|
13
|
+
{ id: "outcome", label: "OUT", width: 4 },
|
|
14
|
+
{ id: "price", label: "PRICE", width: 8, align: "right" },
|
|
15
|
+
{ id: "size", label: "SIZE", width: 10, align: "right" },
|
|
16
|
+
];
|
|
17
|
+
|
|
7
18
|
export function PredictionMarketTradesView({
|
|
19
|
+
focused,
|
|
8
20
|
trades,
|
|
21
|
+
width,
|
|
9
22
|
}: {
|
|
23
|
+
focused: boolean;
|
|
10
24
|
trades: PredictionTrade[];
|
|
25
|
+
width: number;
|
|
11
26
|
}) {
|
|
12
27
|
return (
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
<DataTableView<PredictionTrade, TradeColumn>
|
|
29
|
+
focused={focused}
|
|
30
|
+
keyboardNavigation={false}
|
|
31
|
+
rootWidth={width}
|
|
32
|
+
rootBackgroundColor={colors.panel}
|
|
33
|
+
selection={{ kind: "none" }}
|
|
34
|
+
columns={TRADE_COLUMNS}
|
|
35
|
+
items={trades.slice(0, 30)}
|
|
36
|
+
sortColumnId={null}
|
|
37
|
+
sortDirection="asc"
|
|
38
|
+
onHeaderClick={() => {}}
|
|
39
|
+
getItemKey={(trade) => trade.id}
|
|
40
|
+
renderCell={(trade, column) => {
|
|
41
|
+
const tradeColor = trade.side === "buy" ? colors.positive : colors.negative;
|
|
42
|
+
switch (column.id) {
|
|
43
|
+
case "time":
|
|
44
|
+
return {
|
|
45
|
+
text: new Date(trade.timestamp).toLocaleTimeString("en-US", {
|
|
46
|
+
hour12: false,
|
|
47
|
+
}),
|
|
48
|
+
color: colors.textDim,
|
|
49
|
+
};
|
|
50
|
+
case "side":
|
|
51
|
+
return {
|
|
52
|
+
text: trade.side.toUpperCase(),
|
|
53
|
+
color: tradeColor,
|
|
54
|
+
};
|
|
55
|
+
case "outcome":
|
|
56
|
+
return {
|
|
57
|
+
text: trade.outcome.toUpperCase(),
|
|
58
|
+
color: colors.text,
|
|
59
|
+
};
|
|
60
|
+
case "price":
|
|
61
|
+
return {
|
|
62
|
+
text: formatPredictionProbability(trade.price),
|
|
63
|
+
color: tradeColor,
|
|
64
|
+
};
|
|
65
|
+
case "size":
|
|
66
|
+
return {
|
|
67
|
+
text: formatNumber(trade.size, 0),
|
|
68
|
+
color: colors.textDim,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}}
|
|
72
|
+
emptyStateTitle="No recent trades."
|
|
73
|
+
emptyStateHint="This venue did not return recent prints."
|
|
74
|
+
/>
|
|
30
75
|
);
|
|
31
76
|
}
|
|
@@ -48,4 +48,54 @@ describe("prediction markets detail views", () => {
|
|
|
48
48
|
expect(frame).not.toContain("Kalshi");
|
|
49
49
|
expect(frame).not.toContain("targets");
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
test("keeps grouped outcomes in place while selected detail is loading", async () => {
|
|
53
|
+
testSetup = await testRender(<GroupedDetailHarness loading />, {
|
|
54
|
+
width: 64,
|
|
55
|
+
height: 24,
|
|
56
|
+
});
|
|
57
|
+
await flushFrames(testSetup);
|
|
58
|
+
|
|
59
|
+
const frame = testSetup.captureCharFrame();
|
|
60
|
+
expect(frame).toContain("Outcomes");
|
|
61
|
+
expect(frame).toContain("Above 4.25%");
|
|
62
|
+
expect(frame).toContain("Loading chart...");
|
|
63
|
+
expect(frame).not.toContain("No chart history.");
|
|
64
|
+
expect(frame).not.toContain("Loading market detail...");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("renders book levels in the shared data table", async () => {
|
|
68
|
+
testSetup = await testRender(<GroupedDetailHarness detailTab="book" />, {
|
|
69
|
+
width: 64,
|
|
70
|
+
height: 24,
|
|
71
|
+
});
|
|
72
|
+
await flushFrames(testSetup);
|
|
73
|
+
|
|
74
|
+
const frame = testSetup.captureCharFrame();
|
|
75
|
+
expect(frame).toContain("OUT");
|
|
76
|
+
expect(frame).toContain("SIDE");
|
|
77
|
+
expect(frame).toContain("PRICE");
|
|
78
|
+
expect(frame).toContain("SIZE");
|
|
79
|
+
expect(frame).toContain("YES");
|
|
80
|
+
expect(frame).toContain("BID");
|
|
81
|
+
expect(frame).toContain("47c");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("renders recent trades in the shared data table", async () => {
|
|
85
|
+
testSetup = await testRender(<GroupedDetailHarness detailTab="trades" />, {
|
|
86
|
+
width: 64,
|
|
87
|
+
height: 24,
|
|
88
|
+
});
|
|
89
|
+
await flushFrames(testSetup);
|
|
90
|
+
|
|
91
|
+
const frame = testSetup.captureCharFrame();
|
|
92
|
+
expect(frame).toContain("TIME");
|
|
93
|
+
expect(frame).toContain("SIDE");
|
|
94
|
+
expect(frame).toContain("OUT");
|
|
95
|
+
expect(frame).toContain("PRICE");
|
|
96
|
+
expect(frame).toContain("SIZE");
|
|
97
|
+
expect(frame).toContain("BUY");
|
|
98
|
+
expect(frame).toContain("YES");
|
|
99
|
+
expect(frame).toContain("48c");
|
|
100
|
+
});
|
|
51
101
|
});
|
|
@@ -130,6 +130,32 @@ describe("prediction markets pane interactions", () => {
|
|
|
130
130
|
expect(frame).not.toContain("Was there a typo in the url or port?");
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
test("shows loading instead of an empty state while empty catalogs are pending", async () => {
|
|
134
|
+
attachPredictionMarketsPersistence(new MemoryPersistence());
|
|
135
|
+
|
|
136
|
+
const releaseFetches: Array<() => void> = [];
|
|
137
|
+
globalThis.fetch = (async (input: Request | string | URL) => {
|
|
138
|
+
await new Promise<void>((resolve) => {
|
|
139
|
+
releaseFetches.push(resolve);
|
|
140
|
+
});
|
|
141
|
+
const url = String(input);
|
|
142
|
+
if (url.includes("/trade-api/v2/events?")) {
|
|
143
|
+
return new Response(JSON.stringify({ events: [] }), { status: 200 });
|
|
144
|
+
}
|
|
145
|
+
return new Response(JSON.stringify([]), { status: 200 });
|
|
146
|
+
}) as unknown as typeof fetch;
|
|
147
|
+
|
|
148
|
+
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
149
|
+
await flushFrames(testSetup);
|
|
150
|
+
|
|
151
|
+
const frame = testSetup.captureCharFrame();
|
|
152
|
+
expect(frame).toContain("Loading markets...");
|
|
153
|
+
expect(frame).not.toContain("No markets matched.");
|
|
154
|
+
|
|
155
|
+
for (const release of releaseFetches) release();
|
|
156
|
+
await flushFrames(testSetup);
|
|
157
|
+
});
|
|
158
|
+
|
|
133
159
|
test("selects a market on single click and opens detail on double click", async () => {
|
|
134
160
|
installPredictionMarketMocks();
|
|
135
161
|
|
|
@@ -172,6 +198,23 @@ describe("prediction markets pane interactions", () => {
|
|
|
172
198
|
|
|
173
199
|
frame = testSetup.captureCharFrame();
|
|
174
200
|
expect(frame).toContain("Kalshi primary rule");
|
|
201
|
+
expect(frame).toContain("\u2190 Back Will the Fed cut rates?");
|
|
202
|
+
expect(frame.match(/Will the Fed cut rates\?/g) ?? []).toHaveLength(1);
|
|
203
|
+
expect(frame).not.toContain("[/]search");
|
|
204
|
+
expect(frame).not.toContain("[w]atch");
|
|
205
|
+
expect(frame).not.toContain("[1-4]browse");
|
|
206
|
+
|
|
207
|
+
const metricsHeader = frame
|
|
208
|
+
.split("\n")
|
|
209
|
+
.find((line) =>
|
|
210
|
+
line.includes("YES") &&
|
|
211
|
+
line.includes("NO") &&
|
|
212
|
+
line.includes("24H VOL"),
|
|
213
|
+
);
|
|
214
|
+
expect(metricsHeader).toContain("TOTAL VOL");
|
|
215
|
+
expect(metricsHeader).toContain("OI");
|
|
216
|
+
expect(metricsHeader).toContain("SPREAD");
|
|
217
|
+
expect(metricsHeader).toContain("LAST");
|
|
175
218
|
});
|
|
176
219
|
|
|
177
220
|
test("focuses the pane when a market row is clicked", async () => {
|
|
@@ -265,6 +308,38 @@ describe("prediction markets pane interactions", () => {
|
|
|
265
308
|
expect(frame).not.toContain("\u2190 Back");
|
|
266
309
|
});
|
|
267
310
|
|
|
311
|
+
test("moves focus between search and the market table with arrows", async () => {
|
|
312
|
+
installPredictionMarketMocks();
|
|
313
|
+
|
|
314
|
+
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
315
|
+
await flushFrames(testSetup);
|
|
316
|
+
|
|
317
|
+
await emitKeypress(testSetup, { name: "j", sequence: "j" });
|
|
318
|
+
await flushFrames(testSetup, 1);
|
|
319
|
+
|
|
320
|
+
await emitKeypress(testSetup, { name: "up", sequence: "\u001b[A" });
|
|
321
|
+
await flushFrames(testSetup);
|
|
322
|
+
|
|
323
|
+
let frame = testSetup.captureCharFrame();
|
|
324
|
+
expect(frame).toContain("/ search markets");
|
|
325
|
+
|
|
326
|
+
await emitKeypress(testSetup, { name: "up", sequence: "\u001b[A" });
|
|
327
|
+
await flushFrames(testSetup);
|
|
328
|
+
|
|
329
|
+
frame = testSetup.captureCharFrame();
|
|
330
|
+
expect(frame).toContain("? search markets");
|
|
331
|
+
|
|
332
|
+
await emitKeypress(testSetup, {
|
|
333
|
+
name: "down",
|
|
334
|
+
sequence: "\u001b[B",
|
|
335
|
+
targetEditable: true,
|
|
336
|
+
});
|
|
337
|
+
await flushFrames(testSetup);
|
|
338
|
+
|
|
339
|
+
frame = testSetup.captureCharFrame();
|
|
340
|
+
expect(frame).toContain("/ search markets");
|
|
341
|
+
});
|
|
342
|
+
|
|
268
343
|
test("supports detail outcome navigation and escape return from the keyboard", async () => {
|
|
269
344
|
attachPredictionMarketsPersistence(new MemoryPersistence());
|
|
270
345
|
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { Box, Input, Text } from "../../ui";
|
|
2
2
|
import { useCallback, useMemo, useRef } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
DataTableStackView,
|
|
5
|
+
Spinner,
|
|
6
|
+
Tabs,
|
|
7
|
+
usePaneFooter,
|
|
8
|
+
type DataTableKeyEvent,
|
|
9
|
+
type DataTableRootKeyContext,
|
|
10
|
+
} from "../../components";
|
|
4
11
|
import { createRowValueCache } from "../../components/ui/row-value-cache";
|
|
5
12
|
import type { PaneProps } from "../../types/plugin";
|
|
6
13
|
import { colors } from "../../theme/colors";
|
|
7
14
|
import { PREDICTION_CATEGORY_OPTIONS } from "./categories";
|
|
8
15
|
import { usePredictionMarketsController } from "./controller";
|
|
9
16
|
import { PredictionMarketDetailPane } from "./detail/pane";
|
|
17
|
+
import { resolvePredictionDetailTitle } from "./detail/shared";
|
|
10
18
|
import { getPredictionColumnValue } from "./metrics";
|
|
11
19
|
import { BROWSE_TABS, VENUE_TABS } from "./navigation";
|
|
20
|
+
import { isPlainArrowUp, stopSearchFocusNavigation } from "../../utils/search-focus-navigation";
|
|
12
21
|
import type {
|
|
13
22
|
PredictionBrowseTab,
|
|
14
23
|
PredictionCategoryId,
|
|
@@ -66,33 +75,45 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
66
75
|
controller.catalogStatus?.tone === "danger"
|
|
67
76
|
? colors.negative
|
|
68
77
|
: colors.borderFocused;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
id: "
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
const rowsLoading =
|
|
79
|
+
controller.visibleRows.length === 0 &&
|
|
80
|
+
(controller.catalogLoadCount > 0 || controller.searchLoading);
|
|
81
|
+
const detailTitle = resolvePredictionDetailTitle({
|
|
82
|
+
detail: controller.detail,
|
|
83
|
+
selectedRow: controller.selectedRow,
|
|
84
|
+
selectedSummary: controller.selectedSummary,
|
|
85
|
+
});
|
|
86
|
+
usePaneFooter("prediction-markets", () => {
|
|
87
|
+
if (controller.detailOpen) return null;
|
|
88
|
+
return {
|
|
89
|
+
info: [
|
|
90
|
+
...(controller.searchQuery.trim() ? [{ id: "search", parts: [{ text: `search: ${controller.searchQuery.trim()}`, tone: "value" as const }] }] : []),
|
|
91
|
+
...(controller.searchLoading ? [{ id: "search-loading", parts: [{ text: "searching", tone: "muted" as const }] }] : []),
|
|
92
|
+
...(controller.catalogStatus ? [{
|
|
93
|
+
id: "catalog",
|
|
94
|
+
parts: [{ text: controller.catalogStatus.message, tone: controller.catalogStatus.tone === "danger" ? "warning" as const : "muted" as const, color: catalogStatusColor }],
|
|
95
|
+
}] : []),
|
|
96
|
+
],
|
|
97
|
+
hints: [
|
|
98
|
+
{ id: "search", key: "/", label: "search", onPress: controller.actions.focusSearch },
|
|
99
|
+
{ id: "watch", key: "w", label: "atch", onPress: controller.selectedRow ? () => controller.actions.toggleWatchlist(controller.selectedRow!) : undefined, disabled: !controller.selectedRow },
|
|
100
|
+
{
|
|
101
|
+
id: "browse",
|
|
102
|
+
key: "1-4",
|
|
103
|
+
label: "browse",
|
|
104
|
+
onPress: () => {
|
|
105
|
+
const index = BROWSE_TABS.findIndex((tab) => tab.value === controller.browseTab);
|
|
106
|
+
controller.actions.selectBrowseTab(BROWSE_TABS[(index + 1) % BROWSE_TABS.length]!.value as PredictionBrowseTab);
|
|
107
|
+
},
|
|
88
108
|
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
}, [
|
|
92
112
|
catalogStatusColor,
|
|
93
113
|
controller.browseTab,
|
|
94
114
|
controller.catalogStatus?.message,
|
|
95
115
|
controller.catalogStatus?.tone,
|
|
116
|
+
controller.detailOpen,
|
|
96
117
|
controller.searchLoading,
|
|
97
118
|
controller.searchQuery,
|
|
98
119
|
controller.selectedRow,
|
|
@@ -217,6 +238,18 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
217
238
|
watchlistedRowKeys,
|
|
218
239
|
]);
|
|
219
240
|
|
|
241
|
+
const handleRootKeyDown = useCallback((
|
|
242
|
+
event: DataTableKeyEvent,
|
|
243
|
+
context: DataTableRootKeyContext,
|
|
244
|
+
) => {
|
|
245
|
+
if (context.selectedIndex <= 0 && isPlainArrowUp(event)) {
|
|
246
|
+
stopSearchFocusNavigation(event);
|
|
247
|
+
controller.actions.focusSearch();
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}, [controller.actions.focusSearch]);
|
|
252
|
+
|
|
220
253
|
const detailContent =
|
|
221
254
|
controller.selectedSummary && controller.selectedRow ? (
|
|
222
255
|
<Box
|
|
@@ -256,6 +289,7 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
256
289
|
detailOpen={controller.detailOpen && !!controller.selectedSummary}
|
|
257
290
|
onBack={controller.actions.closeDetail}
|
|
258
291
|
detailContent={detailContent}
|
|
292
|
+
detailTitle={detailTitle}
|
|
259
293
|
rootBefore={browseControls}
|
|
260
294
|
rootWidth={width}
|
|
261
295
|
rootHeight={height}
|
|
@@ -271,6 +305,7 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
271
305
|
}}
|
|
272
306
|
onActivate={(row) =>
|
|
273
307
|
controller.actions.openSelectedRow(row.key)}
|
|
308
|
+
onRootKeyDown={handleRootKeyDown}
|
|
274
309
|
columns={controller.visibleColumns}
|
|
275
310
|
items={controller.visibleRows}
|
|
276
311
|
sortColumnId={controller.sortPreference.columnId}
|
|
@@ -281,6 +316,19 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
281
316
|
getItemKey={(row) => row.key}
|
|
282
317
|
virtualize
|
|
283
318
|
renderCell={renderCell}
|
|
319
|
+
emptyContent={
|
|
320
|
+
rowsLoading ? (
|
|
321
|
+
<Box width="100%" paddingX={1} paddingY={1}>
|
|
322
|
+
<Spinner
|
|
323
|
+
label={
|
|
324
|
+
controller.searchQuery.trim().length > 0
|
|
325
|
+
? "Searching markets..."
|
|
326
|
+
: "Loading markets..."
|
|
327
|
+
}
|
|
328
|
+
/>
|
|
329
|
+
</Box>
|
|
330
|
+
) : undefined
|
|
331
|
+
}
|
|
284
332
|
emptyStateTitle="No markets matched."
|
|
285
333
|
emptyStateHint="Change the venue, browse tab, or search query."
|
|
286
334
|
/>
|