gloomberb 0.3.7 → 0.4.0
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 +19 -8
- package/package.json +1 -1
- package/src/app.tsx +29 -65
- package/src/cli/base-converter.ts +31 -0
- package/src/cli/commands/search.ts +11 -3
- package/src/cli/commands/ticker.ts +22 -11
- package/src/cli/context.ts +27 -32
- package/src/cli/helpers.ts +1 -4
- package/src/cli/index.ts +140 -123
- package/src/cli/registry.test.ts +226 -0
- package/src/cli/registry.ts +201 -0
- package/src/cli.test.ts +219 -0
- package/src/components/chart/chart-controller.ts +309 -0
- package/src/components/chart/chart-data.ts +5 -6
- package/src/components/chart/chart-pane-settings.ts +27 -0
- package/src/components/chart/chart-resolution.test.ts +56 -0
- package/src/components/chart/chart-resolution.ts +362 -0
- package/src/components/chart/chart-types.ts +12 -4
- package/src/components/chart/chart-viewport.test.ts +25 -0
- package/src/components/chart/chart-viewport.ts +28 -0
- package/src/components/chart/comparison-chart-data.ts +20 -27
- package/src/components/chart/comparison-stock-chart.tsx +374 -99
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +124 -0
- package/src/components/chart/stock-chart.tsx +455 -106
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
- package/src/components/command-bar/command-bar.test.tsx +650 -5
- package/src/components/command-bar/command-bar.tsx +359 -63
- package/src/components/command-bar/command-registry.ts +8 -0
- package/src/components/command-bar/helpers.test.ts +23 -1
- package/src/components/command-bar/helpers.ts +14 -5
- package/src/components/command-bar/pane-template-display.ts +12 -0
- package/src/components/command-bar/root-shortcuts.ts +2 -1
- package/src/components/command-bar/workflow-ops.ts +4 -2
- package/src/components/detail-feed-view.test.tsx +124 -49
- package/src/components/detail-feed-view.tsx +396 -160
- package/src/components/layout/floating-pane.tsx +1 -1
- package/src/components/layout/pane-header.tsx +26 -2
- package/src/components/layout/shell.tsx +62 -50
- package/src/components/layout/status-bar.test.tsx +66 -0
- package/src/components/layout/status-bar.tsx +26 -5
- package/src/components/onboarding/onboarding-wizard.test.tsx +65 -0
- package/src/components/onboarding/onboarding-wizard.tsx +5 -6
- package/src/components/ticker-list-table.test.tsx +83 -0
- package/src/components/ticker-list-table.tsx +174 -97
- package/src/components/ui/data-table.tsx +27 -2
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/page-stack-view.tsx +60 -0
- package/src/components/use-double-click-activation.ts +31 -0
- package/src/index.tsx +10 -10
- package/src/market-data/coordinator.test.ts +92 -4
- package/src/market-data/coordinator.ts +32 -5
- package/src/market-data/hooks.test.tsx +120 -0
- package/src/market-data/hooks.tsx +53 -16
- package/src/market-data/request-types.ts +4 -2
- package/src/market-data/selectors.ts +24 -58
- package/src/plugins/builtin/chat-controller.test.ts +370 -3
- package/src/plugins/builtin/chat-controller.ts +310 -35
- package/src/plugins/builtin/chat.test.tsx +118 -6
- package/src/plugins/builtin/chat.tsx +175 -116
- package/src/plugins/builtin/comparison-chart.test.tsx +66 -5
- package/src/plugins/builtin/comparison-chart.tsx +21 -1
- package/src/plugins/builtin/news.tsx +1 -11
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +656 -0
- package/src/plugins/builtin/portfolio-list/command-bar.ts +122 -0
- package/src/plugins/builtin/portfolio-list/header.tsx +16 -34
- package/src/plugins/builtin/portfolio-list/index.tsx +5 -0
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +61 -0
- package/src/plugins/builtin/portfolio-list/metrics.ts +2 -2
- package/src/plugins/builtin/portfolio-list/mutations.test.ts +187 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +226 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +39 -17
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -2
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +163 -0
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +81 -0
- package/src/plugins/builtin/sec.tsx +1 -11
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +13 -2
- package/src/plugins/builtin/ticker-detail/financials-tab.tsx +21 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +45 -10
- package/src/plugins/builtin/ticker-detail/pane.tsx +35 -17
- package/src/plugins/builtin/ticker-detail/settings.ts +19 -1
- package/src/plugins/builtin/ticker-detail.test.tsx +47 -0
- package/src/plugins/catalog.ts +65 -0
- package/src/plugins/ibkr/broker-adapter.ts +28 -0
- package/src/plugins/ibkr/gateway-service.test.ts +45 -1
- package/src/plugins/ibkr/gateway-service.ts +141 -7
- package/src/plugins/pane-manager.test.ts +1 -1
- package/src/plugins/plugin-runtime.tsx +27 -7
- package/src/plugins/prediction-markets/cli.ts +28 -0
- package/src/plugins/prediction-markets/controller-data.ts +11 -3
- package/src/plugins/prediction-markets/controller.ts +141 -121
- package/src/plugins/prediction-markets/index.tsx +2 -0
- package/src/plugins/prediction-markets/launch.ts +51 -16
- package/src/plugins/prediction-markets/metrics.ts +9 -5
- package/src/plugins/prediction-markets/pane.test.tsx +119 -57
- package/src/plugins/prediction-markets/pane.tsx +74 -153
- package/src/plugins/prediction-markets/table.tsx +6 -3
- package/src/plugins/prediction-markets/types.ts +0 -1
- package/src/sources/gloomberb-cloud.test.ts +29 -0
- package/src/sources/gloomberb-cloud.ts +89 -1
- package/src/sources/provider-router.test.ts +265 -3
- package/src/sources/provider-router.ts +239 -11
- package/src/sources/yahoo-finance.test.ts +26 -0
- package/src/sources/yahoo-finance.ts +54 -3
- package/src/state/app-context-selector.test.tsx +99 -0
- package/src/state/app-context.test.ts +18 -0
- package/src/state/app-context.tsx +170 -65
- package/src/state/selectors.ts +25 -0
- package/src/theme/colors.ts +8 -8
- package/src/theme/themes.test.ts +2 -0
- package/src/theme/themes.ts +22 -10
- package/src/types/broker.ts +21 -0
- package/src/types/config.ts +17 -1
- package/src/types/data-provider.ts +18 -0
- package/src/types/financials.ts +31 -1
- package/src/types/plugin.ts +70 -0
- package/src/utils/api-client.test.ts +57 -0
- package/src/utils/api-client.ts +35 -12
- package/src/utils/back-navigation.ts +27 -0
- package/src/utils/format.test.ts +14 -0
- package/src/utils/format.ts +3 -3
- package/src/utils/market-status.ts +46 -1
- package/src/utils/quote-freshness.ts +127 -0
- package/src/utils/quote-resolution.test.ts +277 -0
- package/src/utils/quote-resolution.ts +641 -0
- package/src/utils/slugify.ts +3 -0
- package/src/utils/timestamp.ts +25 -0
- package/src/version.ts +1 -1
- package/src/cli/commands/portfolio.ts +0 -210
- package/src/cli/commands/watchlist.ts +0 -210
- package/src/plugins/builtin/data-providers.ts +0 -16
- package/src/sources/exchange-rates.ts +0 -18
- package/src/utils/ascii-chart.ts +0 -172
- package/src/utils/ticker-tokenizer.test.ts +0 -48
|
@@ -25,6 +25,11 @@ import { tmpdir } from "os";
|
|
|
25
25
|
import { join } from "path";
|
|
26
26
|
import { firstValueFrom, filter, take, timeout } from "rxjs";
|
|
27
27
|
import type { TimeRange } from "../../components/chart/chart-types";
|
|
28
|
+
import {
|
|
29
|
+
normalizeChartResolutionSupport,
|
|
30
|
+
type ChartResolutionSupport,
|
|
31
|
+
type ManualChartResolution,
|
|
32
|
+
} from "../../components/chart/chart-resolution";
|
|
28
33
|
import type { BrokerConnectionStatus, BrokerPosition } from "../../types/broker";
|
|
29
34
|
import type { QuoteSubscriptionTarget } from "../../types/data-provider";
|
|
30
35
|
import type { Fundamentals, FinancialStatement, Quote, PricePoint, TickerFinancials } from "../../types/financials";
|
|
@@ -290,6 +295,7 @@ const IBKR_DATA_TIMEOUT = 8_000;
|
|
|
290
295
|
const IBKR_QUOTE_STREAM_TICKS = "165,221,233";
|
|
291
296
|
|
|
292
297
|
const HISTORY_PARAMS: Record<TimeRange, { duration: string; size: BarSizeSetting }> = {
|
|
298
|
+
"1D": { duration: "1 D", size: BarSizeSetting.MINUTES_FIVE },
|
|
293
299
|
"1W": { duration: "7 D", size: BarSizeSetting.HOURS_ONE },
|
|
294
300
|
"1M": { duration: "1 M", size: BarSizeSetting.HOURS_ONE },
|
|
295
301
|
"3M": { duration: "3 M", size: BarSizeSetting.DAYS_ONE },
|
|
@@ -299,6 +305,17 @@ const HISTORY_PARAMS: Record<TimeRange, { duration: string; size: BarSizeSetting
|
|
|
299
305
|
"ALL": { duration: "10 Y", size: BarSizeSetting.MONTHS_ONE },
|
|
300
306
|
};
|
|
301
307
|
|
|
308
|
+
const IBKR_RESOLUTION_SUPPORT = normalizeChartResolutionSupport([
|
|
309
|
+
{ resolution: "1m", maxRange: "1W" },
|
|
310
|
+
{ resolution: "5m", maxRange: "1M" },
|
|
311
|
+
{ resolution: "15m", maxRange: "3M" },
|
|
312
|
+
{ resolution: "30m", maxRange: "6M" },
|
|
313
|
+
{ resolution: "1h", maxRange: "1Y" },
|
|
314
|
+
{ resolution: "1d", maxRange: "ALL" },
|
|
315
|
+
{ resolution: "1wk", maxRange: "ALL" },
|
|
316
|
+
{ resolution: "1mo", maxRange: "ALL" },
|
|
317
|
+
]);
|
|
318
|
+
|
|
302
319
|
/** Map generic bar size labels to IBKR BarSizeSetting values. */
|
|
303
320
|
const GENERIC_BAR_SIZE_MAP: Record<string, BarSizeSetting> = {
|
|
304
321
|
"1m": BarSizeSetting.MINUTES_ONE,
|
|
@@ -307,7 +324,8 @@ const GENERIC_BAR_SIZE_MAP: Record<string, BarSizeSetting> = {
|
|
|
307
324
|
"30m": BarSizeSetting.MINUTES_THIRTY,
|
|
308
325
|
"1h": BarSizeSetting.HOURS_ONE,
|
|
309
326
|
"1d": BarSizeSetting.DAYS_ONE,
|
|
310
|
-
"
|
|
327
|
+
"1wk": BarSizeSetting.WEEKS_ONE,
|
|
328
|
+
"1mo": BarSizeSetting.MONTHS_ONE,
|
|
311
329
|
};
|
|
312
330
|
|
|
313
331
|
export function parseIbkrHistoricalBarTime(value: string | number): Date {
|
|
@@ -383,6 +401,65 @@ function hasDelayedMarketData(
|
|
|
383
401
|
].some((tickType) => marketData.has(tickType));
|
|
384
402
|
}
|
|
385
403
|
|
|
404
|
+
function firstNonSmartExchange(value?: string): string | undefined {
|
|
405
|
+
return value
|
|
406
|
+
?.split(",")
|
|
407
|
+
.map((entry) => entry.trim())
|
|
408
|
+
.find((entry) => entry.length > 0 && entry !== "SMART");
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function resolveIbkrListingExchange(
|
|
412
|
+
current: Quote | undefined,
|
|
413
|
+
contract: Contract,
|
|
414
|
+
details: ContractDetails,
|
|
415
|
+
): string | undefined {
|
|
416
|
+
return current?.listingExchangeName
|
|
417
|
+
?? contract.primaryExch
|
|
418
|
+
?? firstNonSmartExchange(details.validExchanges)
|
|
419
|
+
?? (current?.exchangeName !== "SMART" ? current?.exchangeName : undefined)
|
|
420
|
+
?? (contract.exchange !== "SMART" ? contract.exchange : undefined);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function resolveIbkrRoutingExchange(current: Quote | undefined, contract: Contract): string | undefined {
|
|
424
|
+
return current?.routingExchangeName
|
|
425
|
+
?? contract.exchange
|
|
426
|
+
?? "SMART";
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function ibkrVenueFields(
|
|
430
|
+
current: Quote | undefined,
|
|
431
|
+
contract: Contract,
|
|
432
|
+
details: ContractDetails,
|
|
433
|
+
): Pick<
|
|
434
|
+
Quote,
|
|
435
|
+
| "exchangeName"
|
|
436
|
+
| "fullExchangeName"
|
|
437
|
+
| "listingExchangeName"
|
|
438
|
+
| "listingExchangeFullName"
|
|
439
|
+
| "routingExchangeName"
|
|
440
|
+
| "routingExchangeFullName"
|
|
441
|
+
| "marketState"
|
|
442
|
+
| "sessionConfidence"
|
|
443
|
+
> {
|
|
444
|
+
const listingExchangeName = resolveIbkrListingExchange(current, contract, details);
|
|
445
|
+
const listingExchangeFullName = current?.listingExchangeFullName
|
|
446
|
+
?? current?.fullExchangeName
|
|
447
|
+
?? listingExchangeName;
|
|
448
|
+
const routingExchangeName = resolveIbkrRoutingExchange(current, contract);
|
|
449
|
+
const routingExchangeFullName = current?.routingExchangeFullName ?? routingExchangeName;
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
exchangeName: listingExchangeName,
|
|
453
|
+
fullExchangeName: listingExchangeFullName,
|
|
454
|
+
listingExchangeName,
|
|
455
|
+
listingExchangeFullName,
|
|
456
|
+
routingExchangeName,
|
|
457
|
+
routingExchangeFullName,
|
|
458
|
+
marketState: current?.marketState,
|
|
459
|
+
sessionConfidence: current?.sessionConfidence ?? "unknown",
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
386
463
|
interface TickByTickBidAskUpdate {
|
|
387
464
|
time: number;
|
|
388
465
|
bidPrice: number;
|
|
@@ -417,9 +494,7 @@ export function applyTickByTickAllLastToQuote(
|
|
|
417
494
|
volume: current?.volume,
|
|
418
495
|
name: current?.name ?? details.longName ?? details.marketName ?? contract.symbol,
|
|
419
496
|
lastUpdated: tick.time ? tick.time * 1000 : Date.now(),
|
|
420
|
-
|
|
421
|
-
fullExchangeName: current?.fullExchangeName ?? details.validExchanges?.split(",")[0],
|
|
422
|
-
marketState: current?.marketState ?? "REGULAR",
|
|
497
|
+
...ibkrVenueFields(current, contract, details),
|
|
423
498
|
preMarketPrice: current?.preMarketPrice,
|
|
424
499
|
preMarketChange: current?.preMarketChange,
|
|
425
500
|
preMarketChangePercent: current?.preMarketChangePercent,
|
|
@@ -1052,6 +1127,67 @@ export class IbkrGatewayService {
|
|
|
1052
1127
|
});
|
|
1053
1128
|
}
|
|
1054
1129
|
|
|
1130
|
+
getChartResolutionSupport(
|
|
1131
|
+
_ticker: string,
|
|
1132
|
+
_config: IbkrGatewayConfig,
|
|
1133
|
+
_exchange?: string,
|
|
1134
|
+
_instrument?: BrokerContractRef | null,
|
|
1135
|
+
): ChartResolutionSupport[] {
|
|
1136
|
+
return IBKR_RESOLUTION_SUPPORT;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
getChartResolutionCapabilities(
|
|
1140
|
+
_ticker: string,
|
|
1141
|
+
_config: IbkrGatewayConfig,
|
|
1142
|
+
_exchange?: string,
|
|
1143
|
+
_instrument?: BrokerContractRef | null,
|
|
1144
|
+
): ManualChartResolution[] {
|
|
1145
|
+
return IBKR_RESOLUTION_SUPPORT.map((entry) => entry.resolution);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
async getPriceHistoryForResolution(
|
|
1149
|
+
ticker: string,
|
|
1150
|
+
config: IbkrGatewayConfig,
|
|
1151
|
+
exchange: string,
|
|
1152
|
+
bufferRange: TimeRange,
|
|
1153
|
+
resolution: ManualChartResolution,
|
|
1154
|
+
instrument?: BrokerContractRef | null,
|
|
1155
|
+
): Promise<PricePoint[]> {
|
|
1156
|
+
const ibkrBarSize = GENERIC_BAR_SIZE_MAP[resolution];
|
|
1157
|
+
if (!ibkrBarSize) return [];
|
|
1158
|
+
|
|
1159
|
+
return this.dedup(`history:${ticker}:${exchange}:${bufferRange}:${resolution}`, async () => {
|
|
1160
|
+
await this.connect(config);
|
|
1161
|
+
const contract = await withTimeout(this.resolveContract(ticker, exchange, instrument ?? null), IBKR_DATA_TIMEOUT, "resolveContract");
|
|
1162
|
+
const params = HISTORY_PARAMS[bufferRange];
|
|
1163
|
+
const detailsPromise = withTimeout(this.getPrimaryContractDetails(contract), IBKR_DATA_TIMEOUT, "getContractDetails")
|
|
1164
|
+
.catch(() => undefined);
|
|
1165
|
+
const bars = await this.withMarketDataFallback(
|
|
1166
|
+
config,
|
|
1167
|
+
() => withTimeout(this.api!.getHistoricalData(
|
|
1168
|
+
contract,
|
|
1169
|
+
"",
|
|
1170
|
+
params.duration,
|
|
1171
|
+
ibkrBarSize,
|
|
1172
|
+
WhatToShow.TRADES,
|
|
1173
|
+
1,
|
|
1174
|
+
1,
|
|
1175
|
+
), IBKR_DATA_TIMEOUT, "getHistoricalData"),
|
|
1176
|
+
);
|
|
1177
|
+
const details = await detailsPromise;
|
|
1178
|
+
const priceDivisor = getIbkrPriceDivisor(contract, details);
|
|
1179
|
+
|
|
1180
|
+
return bars.map((bar) => ({
|
|
1181
|
+
date: parseIbkrHistoricalBarTime(bar.time),
|
|
1182
|
+
open: normalizeIbkrPriceValue(bar.open, priceDivisor),
|
|
1183
|
+
high: normalizeIbkrPriceValue(bar.high, priceDivisor),
|
|
1184
|
+
low: normalizeIbkrPriceValue(bar.low, priceDivisor),
|
|
1185
|
+
close: normalizeIbkrPriceValue(bar.close ?? bar.open ?? bar.high ?? bar.low ?? 0, priceDivisor) ?? 0,
|
|
1186
|
+
volume: bar.volume,
|
|
1187
|
+
}));
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1055
1191
|
async getDetailedPriceHistory(
|
|
1056
1192
|
ticker: string,
|
|
1057
1193
|
config: IbkrGatewayConfig,
|
|
@@ -2150,9 +2286,7 @@ export class IbkrGatewayService {
|
|
|
2150
2286
|
volume: marketData.get(IBApiTickType.VOLUME)?.value ?? marketData.get(IBApiTickType.DELAYED_VOLUME)?.value,
|
|
2151
2287
|
name: details.longName || details.marketName || contract.symbol,
|
|
2152
2288
|
lastUpdated: ingressTm,
|
|
2153
|
-
|
|
2154
|
-
fullExchangeName: details.validExchanges?.split(",")[0],
|
|
2155
|
-
marketState: "REGULAR",
|
|
2289
|
+
...ibkrVenueFields(undefined, contract, details),
|
|
2156
2290
|
bid: normalizeIbkrPriceValue(
|
|
2157
2291
|
marketData.get(IBApiTickType.BID)?.value ?? marketData.get(IBApiTickType.DELAYED_BID)?.value,
|
|
2158
2292
|
priceDivisor,
|
|
@@ -71,7 +71,7 @@ describe("pane-manager split-tree drops", () => {
|
|
|
71
71
|
const next = gridlockAllPanes(layout);
|
|
72
72
|
|
|
73
73
|
expect(next.floating).toHaveLength(0);
|
|
74
|
-
expect(getDockedPaneIds(next)).toHaveLength(
|
|
74
|
+
expect(getDockedPaneIds(next)).toHaveLength(5);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
test("gridlock infers a matching tiled layout from arranged windows", () => {
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
createContext,
|
|
3
3
|
useCallback,
|
|
4
4
|
useContext,
|
|
5
|
+
useRef,
|
|
5
6
|
useSyncExternalStore,
|
|
6
7
|
type ReactNode,
|
|
7
8
|
type SetStateAction,
|
|
@@ -101,12 +102,21 @@ export function usePluginPaneState<T>(key: string, fallback: T, paneId?: string)
|
|
|
101
102
|
const { pluginId } = usePluginRenderContext();
|
|
102
103
|
const scopedPaneId = paneId ?? usePaneInstanceId();
|
|
103
104
|
const { state, dispatch } = useAppState();
|
|
105
|
+
const stateRef = useRef(state);
|
|
106
|
+
const fallbackRef = useRef(fallback);
|
|
107
|
+
stateRef.current = state;
|
|
108
|
+
fallbackRef.current = fallback;
|
|
104
109
|
const paneState = state.paneState[scopedPaneId];
|
|
105
110
|
const value = getPluginPaneStateValue(paneState, pluginId, key, fallback);
|
|
106
111
|
|
|
107
112
|
const setValue = useCallback((nextValue: SetStateAction<T>) => {
|
|
108
|
-
const currentPaneState =
|
|
109
|
-
const currentValue = getPluginPaneStateValue(
|
|
113
|
+
const currentPaneState = stateRef.current.paneState[scopedPaneId];
|
|
114
|
+
const currentValue = getPluginPaneStateValue(
|
|
115
|
+
currentPaneState,
|
|
116
|
+
pluginId,
|
|
117
|
+
key,
|
|
118
|
+
fallbackRef.current,
|
|
119
|
+
);
|
|
110
120
|
const resolved = typeof nextValue === "function"
|
|
111
121
|
? (nextValue as (previousValue: T) => T)(currentValue)
|
|
112
122
|
: nextValue;
|
|
@@ -122,7 +132,7 @@ export function usePluginPaneState<T>(key: string, fallback: T, paneId?: string)
|
|
|
122
132
|
key,
|
|
123
133
|
value: resolved,
|
|
124
134
|
});
|
|
125
|
-
}, [dispatch,
|
|
135
|
+
}, [dispatch, key, pluginId, scopedPaneId]);
|
|
126
136
|
|
|
127
137
|
return [value, setValue];
|
|
128
138
|
}
|
|
@@ -130,6 +140,8 @@ export function usePluginPaneState<T>(key: string, fallback: T, paneId?: string)
|
|
|
130
140
|
export function usePluginState<T>(key: string, fallback: T, options?: { schemaVersion?: number }): [T, (value: SetStateAction<T>) => void] {
|
|
131
141
|
const { pluginId, runtime } = usePluginRenderContext();
|
|
132
142
|
const schemaVersion = options?.schemaVersion;
|
|
143
|
+
const fallbackRef = useRef(fallback);
|
|
144
|
+
fallbackRef.current = fallback;
|
|
133
145
|
|
|
134
146
|
const subscribe = useCallback((listener: () => void) => (
|
|
135
147
|
runtime.subscribeResumeState(pluginId, key, listener)
|
|
@@ -142,7 +154,9 @@ export function usePluginState<T>(key: string, fallback: T, options?: { schemaVe
|
|
|
142
154
|
const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
143
155
|
|
|
144
156
|
const setValue = useCallback((nextValue: SetStateAction<T>) => {
|
|
145
|
-
const currentValue =
|
|
157
|
+
const currentValue =
|
|
158
|
+
runtime.getResumeState<T>(pluginId, key, schemaVersion) ??
|
|
159
|
+
fallbackRef.current;
|
|
146
160
|
const resolved = typeof nextValue === "function"
|
|
147
161
|
? (nextValue as (previousValue: T) => T)(currentValue)
|
|
148
162
|
: nextValue;
|
|
@@ -150,7 +164,7 @@ export function usePluginState<T>(key: string, fallback: T, options?: { schemaVe
|
|
|
150
164
|
return;
|
|
151
165
|
}
|
|
152
166
|
runtime.setResumeState(pluginId, key, resolved, schemaVersion);
|
|
153
|
-
}, [
|
|
167
|
+
}, [key, pluginId, runtime, schemaVersion]);
|
|
154
168
|
|
|
155
169
|
return [value, setValue];
|
|
156
170
|
}
|
|
@@ -158,10 +172,16 @@ export function usePluginState<T>(key: string, fallback: T, options?: { schemaVe
|
|
|
158
172
|
export function usePluginConfigState<T>(key: string, fallback: T): [T, (value: SetStateAction<T>) => void] {
|
|
159
173
|
const { pluginId, runtime } = usePluginRenderContext();
|
|
160
174
|
const { state } = useAppState();
|
|
175
|
+
const stateRef = useRef(state);
|
|
176
|
+
const fallbackRef = useRef(fallback);
|
|
177
|
+
stateRef.current = state;
|
|
178
|
+
fallbackRef.current = fallback;
|
|
161
179
|
const value = (state.config.pluginConfig[pluginId]?.[key] as T | undefined) ?? fallback;
|
|
162
180
|
|
|
163
181
|
const setValue = useCallback((nextValue: SetStateAction<T>) => {
|
|
164
|
-
const currentValue =
|
|
182
|
+
const currentValue =
|
|
183
|
+
(stateRef.current.config.pluginConfig[pluginId]?.[key] as T | undefined) ??
|
|
184
|
+
fallbackRef.current;
|
|
165
185
|
const resolved = typeof nextValue === "function"
|
|
166
186
|
? (nextValue as (previousValue: T) => T)(currentValue)
|
|
167
187
|
: nextValue;
|
|
@@ -169,7 +189,7 @@ export function usePluginConfigState<T>(key: string, fallback: T): [T, (value: S
|
|
|
169
189
|
return;
|
|
170
190
|
}
|
|
171
191
|
void runtime.setConfigState(pluginId, key, resolved);
|
|
172
|
-
}, [
|
|
192
|
+
}, [key, pluginId, runtime]);
|
|
173
193
|
|
|
174
194
|
return [value, setValue];
|
|
175
195
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CliCommandDef } from "../../types/plugin";
|
|
2
|
+
import { createPredictionLaunchRequest, parsePredictionCommandArgs } from "./launch";
|
|
3
|
+
|
|
4
|
+
export const predictionMarketsCliCommand: CliCommandDef = {
|
|
5
|
+
name: "predictions",
|
|
6
|
+
aliases: ["prediction-markets", "pm"],
|
|
7
|
+
description: "Launch the UI with Prediction Markets focused",
|
|
8
|
+
help: {
|
|
9
|
+
usage: ["predictions [...]"],
|
|
10
|
+
sections: [{
|
|
11
|
+
title: "Prediction Launch",
|
|
12
|
+
columns: [
|
|
13
|
+
{ header: "Input" },
|
|
14
|
+
{ header: "Example" },
|
|
15
|
+
],
|
|
16
|
+
rows: [
|
|
17
|
+
["gloomberb predictions [venue] [category] [browse-tab] [search...]", "gloomberb predictions world"],
|
|
18
|
+
["venue", "all | polymarket | kalshi"],
|
|
19
|
+
["category", "all | politics | world | macro | crypto | science | sports | entertainment | climate | social"],
|
|
20
|
+
["browse-tab", "top | ending | new | watchlist"],
|
|
21
|
+
],
|
|
22
|
+
}],
|
|
23
|
+
},
|
|
24
|
+
execute: async (args) => ({
|
|
25
|
+
kind: "launch-ui",
|
|
26
|
+
request: createPredictionLaunchRequest(parsePredictionCommandArgs(args)),
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
@@ -39,6 +39,7 @@ const KEYBOARD_DETAIL_LOAD_DELAY_MS = 140;
|
|
|
39
39
|
export function usePredictionMarketsDataState({
|
|
40
40
|
browseTab,
|
|
41
41
|
categoryId,
|
|
42
|
+
detailOpen,
|
|
42
43
|
effectiveVenueScope,
|
|
43
44
|
focused,
|
|
44
45
|
historyRange,
|
|
@@ -52,6 +53,7 @@ export function usePredictionMarketsDataState({
|
|
|
52
53
|
}: {
|
|
53
54
|
browseTab: PredictionBrowseTab;
|
|
54
55
|
categoryId: PredictionCategoryId;
|
|
56
|
+
detailOpen: boolean;
|
|
55
57
|
effectiveVenueScope: PredictionVenueScope;
|
|
56
58
|
focused: boolean;
|
|
57
59
|
historyRange: "1D" | "1W" | "1M" | "ALL";
|
|
@@ -201,7 +203,11 @@ export function usePredictionMarketsDataState({
|
|
|
201
203
|
[selectedRowKey, visibleRows],
|
|
202
204
|
);
|
|
203
205
|
const selectedSummary = useMemo(
|
|
204
|
-
() =>
|
|
206
|
+
() => {
|
|
207
|
+
if (!detailOpen || !selectedRow) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
return (
|
|
205
211
|
selectedRow?.markets.find(
|
|
206
212
|
(market) => market.key === selectedDetailMarketKey,
|
|
207
213
|
) ??
|
|
@@ -209,8 +215,10 @@ export function usePredictionMarketsDataState({
|
|
|
209
215
|
(market) => market.key === selectedRow.focusMarketKey,
|
|
210
216
|
) ??
|
|
211
217
|
selectedRow?.representative ??
|
|
212
|
-
null
|
|
213
|
-
|
|
218
|
+
null
|
|
219
|
+
);
|
|
220
|
+
},
|
|
221
|
+
[detailOpen, selectedDetailMarketKey, selectedRow],
|
|
214
222
|
);
|
|
215
223
|
const selectedSummaryKey = selectedSummary?.key ?? null;
|
|
216
224
|
const selectedSummaryVenue = selectedSummary?.venue ?? null;
|