gloomberb 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -2
- package/bin/gloomberb +1 -1
- package/package.json +1 -1
- package/src/app.tsx +10 -7
- 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/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/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/comparison-stock-chart/helpers.ts +0 -32
- package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
- package/src/components/chart/comparison-stock-chart/layout-metrics.ts +6 -6
- package/src/components/chart/comparison-stock-chart/legend.tsx +45 -44
- 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 +56 -6
- 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 +2 -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/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 +5 -2
- package/src/components/layout/header.tsx +13 -5
- package/src/components/layout/shell/active-drag.ts +5 -1
- package/src/components/layout/shell/drag/runtime.ts +21 -3
- package/src/components/layout/shell/fullscreen.ts +10 -0
- package/src/components/layout/shell/index.test.tsx +395 -2
- package/src/components/layout/shell/index.tsx +128 -2
- package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
- package/src/components/layout/shell/pane/layers.tsx +26 -14
- 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/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 +105 -0
- package/src/components/layout/status-bar.tsx +32 -6
- package/src/components/layout/transient-layout.tsx +57 -0
- package/src/components/layout/window-controls.tsx +19 -10
- 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/price-performance.tsx +183 -0
- package/src/core/app-services.ts +7 -4
- package/src/data/config/store/index.ts +1 -2
- package/src/market-data/performance.test.ts +54 -0
- package/src/market-data/performance.ts +109 -0
- 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 +218 -15
- package/src/plugins/builtin/congress-trades/detail.tsx +204 -68
- package/src/plugins/builtin/congress-trades/footer.ts +14 -8
- package/src/plugins/builtin/congress-trades/keyboard.ts +4 -23
- package/src/plugins/builtin/congress-trades/model.ts +29 -0
- package/src/plugins/builtin/congress-trades/pane.tsx +8 -13
- package/src/plugins/builtin/congress-trades/table.tsx +10 -1
- package/src/plugins/builtin/news/wire/read-state.ts +9 -32
- 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/pane/index.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
- 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/overview/model.ts +0 -14
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -0
- package/src/plugins/pane-manager/docking.ts +9 -2
- package/src/plugins/pane-manager/queries.ts +8 -2
- 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/pane.test.tsx +32 -0
- package/src/plugins/prediction-markets/pane.tsx +21 -1
- package/src/plugins/prediction-markets/test-helpers.tsx +1 -0
- 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/desktop/detached-windows.ts +9 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/window-controls.ts +11 -2
- package/src/renderers/electrobun/bun/desktop/window-style.ts +27 -1
- package/src/renderers/electrobun/bun/desktop/windows-custom-chrome.ts +58 -0
- package/src/renderers/electrobun/bun/desktop/windows-icons.ts +11 -64
- package/src/renderers/electrobun/bun/desktop/windows-native.ts +75 -0
- package/src/renderers/electrobun/bun/index.ts +22 -2
- 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/shared/protocol.ts +1 -0
- package/src/renderers/electrobun/view/app-services.ts +6 -4
- package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
- 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/main.tsx +2 -1
- package/src/renderers/electrobun/view/styles.css +9 -8
- package/src/renderers/electrobun/view/ui-host.tsx +102 -77
- package/src/renderers/opentui/start.tsx +13 -5
- package/src/state/app/input-capture.ts +12 -0
- package/src/types/plugin.ts +28 -0
- package/src/utils/cli-output.ts +6 -0
- package/src/utils/search-focus-navigation.ts +24 -0
- package/src/version.ts +1 -1
|
@@ -1,25 +1,35 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Box, ScrollBox, Text, TextAttributes,
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Box, ScrollBox, Text, TextAttributes, useRendererHost } from "../../../ui";
|
|
3
|
+
import {
|
|
4
|
+
DataTableView,
|
|
5
|
+
usePaneFooter,
|
|
6
|
+
type DataTableKeyEvent,
|
|
7
|
+
} from "../../../components";
|
|
8
|
+
import { useInlineTickerOpener } from "../../../state/hooks/inline-tickers";
|
|
3
9
|
import { colors } from "../../../theme/colors";
|
|
4
10
|
import { padTo } from "../../../utils/format";
|
|
11
|
+
import { isPlainKey } from "../../../utils/keyboard";
|
|
5
12
|
import type {
|
|
13
|
+
CloudCongressHousePayload,
|
|
6
14
|
CloudCongressMemberPayload,
|
|
7
15
|
CloudCongressTradePayload,
|
|
8
16
|
} from "../../../api-client";
|
|
17
|
+
import { apiClient } from "../../../api-client";
|
|
9
18
|
import {
|
|
19
|
+
CONGRESS_MEMBER_FILING_LIMIT,
|
|
20
|
+
CONGRESS_MEMBER_TRADE_LIMIT,
|
|
21
|
+
CONGRESS_TRADES_PANE_ID,
|
|
22
|
+
buildMemberTradeColumns,
|
|
10
23
|
formatAmountRange,
|
|
11
24
|
formatLag,
|
|
12
|
-
|
|
25
|
+
nextSort,
|
|
26
|
+
sortedTrades,
|
|
13
27
|
truncate,
|
|
28
|
+
type LoadStatus,
|
|
29
|
+
type TradeColumn,
|
|
30
|
+
type TradeColumnId,
|
|
14
31
|
} from "./model";
|
|
15
|
-
|
|
16
|
-
export function sideColor(side: CloudCongressTradePayload["side"], selected: boolean): string {
|
|
17
|
-
if (selected) return colors.selectedText;
|
|
18
|
-
if (side === "BUY") return colors.positive;
|
|
19
|
-
if (side === "SELL") return colors.negative;
|
|
20
|
-
if (side === "EXCHANGE") return colors.warning;
|
|
21
|
-
return colors.textDim;
|
|
22
|
-
}
|
|
32
|
+
import { renderCongressTradeCell } from "./table";
|
|
23
33
|
|
|
24
34
|
function DetailLine({
|
|
25
35
|
label,
|
|
@@ -86,69 +96,195 @@ export function TradeDetail({
|
|
|
86
96
|
);
|
|
87
97
|
}
|
|
88
98
|
|
|
89
|
-
export function
|
|
99
|
+
export function MemberTradesDetail({
|
|
90
100
|
member,
|
|
91
|
-
|
|
101
|
+
initialTrades,
|
|
92
102
|
width,
|
|
93
|
-
|
|
103
|
+
focused,
|
|
104
|
+
filingLimit,
|
|
94
105
|
}: {
|
|
95
106
|
member: CloudCongressMemberPayload;
|
|
96
|
-
|
|
107
|
+
initialTrades: CloudCongressTradePayload[];
|
|
97
108
|
width: number;
|
|
98
|
-
|
|
109
|
+
focused: boolean;
|
|
110
|
+
filingLimit: number;
|
|
99
111
|
}) {
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
112
|
+
const rendererHost = useRendererHost();
|
|
113
|
+
const openTicker = useInlineTickerOpener();
|
|
114
|
+
const [trades, setTrades] = useState<CloudCongressTradePayload[]>(initialTrades);
|
|
115
|
+
const [detailPayload, setDetailPayload] = useState<CloudCongressHousePayload | null>(null);
|
|
116
|
+
const [status, setStatus] = useState<LoadStatus>("loading");
|
|
117
|
+
const [error, setError] = useState<string | null>(null);
|
|
118
|
+
const [selectedTradeId, setSelectedTradeId] = useState<string | null>(initialTrades[0]?.id ?? null);
|
|
119
|
+
const [sortPreference, setSortPreference] = useState<{ columnId: TradeColumnId; direction: "asc" | "desc" }>({
|
|
120
|
+
columnId: "filed",
|
|
121
|
+
direction: "desc",
|
|
122
|
+
});
|
|
123
|
+
const fetchGenRef = useRef(0);
|
|
124
|
+
|
|
125
|
+
const load = useCallback((refresh = false) => {
|
|
126
|
+
fetchGenRef.current += 1;
|
|
127
|
+
const gen = fetchGenRef.current;
|
|
128
|
+
setStatus("loading");
|
|
129
|
+
setError(null);
|
|
130
|
+
apiClient.getCloudCongressHouse({
|
|
131
|
+
member: member.memberName,
|
|
132
|
+
limit: CONGRESS_MEMBER_TRADE_LIMIT,
|
|
133
|
+
filingLimit: Math.max(CONGRESS_MEMBER_FILING_LIMIT, filingLimit),
|
|
134
|
+
refresh,
|
|
135
|
+
})
|
|
136
|
+
.then((payload) => {
|
|
137
|
+
if (fetchGenRef.current !== gen) return;
|
|
138
|
+
const exactMemberTrades = payload.trades.filter((trade) => (
|
|
139
|
+
trade.memberName === member.memberName
|
|
140
|
+
&& trade.stateDistrict === member.stateDistrict
|
|
141
|
+
));
|
|
142
|
+
setDetailPayload(payload);
|
|
143
|
+
setTrades(exactMemberTrades.length > 0 ? exactMemberTrades : payload.trades);
|
|
144
|
+
setStatus("loaded");
|
|
145
|
+
})
|
|
146
|
+
.catch((loadError) => {
|
|
147
|
+
if (fetchGenRef.current !== gen) return;
|
|
148
|
+
setError(loadError instanceof Error ? loadError.message : String(loadError));
|
|
149
|
+
setStatus("error");
|
|
150
|
+
});
|
|
151
|
+
}, [filingLimit, member.memberName, member.stateDistrict]);
|
|
152
|
+
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
fetchGenRef.current += 1;
|
|
155
|
+
setTrades(initialTrades);
|
|
156
|
+
setDetailPayload(null);
|
|
157
|
+
setStatus("loading");
|
|
158
|
+
setError(null);
|
|
159
|
+
setSelectedTradeId(initialTrades[0]?.id ?? null);
|
|
160
|
+
}, [member.id]);
|
|
161
|
+
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
load(false);
|
|
164
|
+
}, [load]);
|
|
165
|
+
|
|
166
|
+
const sortedRows = useMemo(() => sortedTrades(trades, sortPreference), [sortPreference, trades]);
|
|
167
|
+
const columns = useMemo(() => buildMemberTradeColumns(width), [width]);
|
|
168
|
+
const selectedTrade = useMemo(() => (
|
|
169
|
+
sortedRows.find((trade) => trade.id === selectedTradeId) ?? sortedRows[0] ?? null
|
|
170
|
+
), [selectedTradeId, sortedRows]);
|
|
171
|
+
const summaryMember = useMemo(() => (
|
|
172
|
+
detailPayload?.members.find((entry) => entry.id === member.id)
|
|
173
|
+
?? detailPayload?.members.find((entry) => (
|
|
174
|
+
entry.memberName === member.memberName
|
|
175
|
+
&& entry.stateDistrict === member.stateDistrict
|
|
176
|
+
))
|
|
177
|
+
?? member
|
|
178
|
+
), [detailPayload?.members, member]);
|
|
179
|
+
const maybeTruncated = status === "loaded" && trades.length >= CONGRESS_MEMBER_TRADE_LIMIT;
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (selectedTradeId && sortedRows.some((trade) => trade.id === selectedTradeId)) return;
|
|
183
|
+
setSelectedTradeId(sortedRows[0]?.id ?? null);
|
|
184
|
+
}, [selectedTradeId, sortedRows]);
|
|
185
|
+
|
|
186
|
+
const refresh = useCallback(() => {
|
|
187
|
+
load(true);
|
|
188
|
+
}, [load]);
|
|
189
|
+
|
|
190
|
+
const openSelectedTicker = useCallback(() => {
|
|
191
|
+
if (selectedTrade?.ticker) openTicker(selectedTrade.ticker);
|
|
192
|
+
}, [openTicker, selectedTrade?.ticker]);
|
|
193
|
+
|
|
194
|
+
const openSelectedSource = useCallback(() => {
|
|
195
|
+
if (selectedTrade?.sourceUrl) void rendererHost.openExternal(selectedTrade.sourceUrl);
|
|
196
|
+
}, [rendererHost, selectedTrade?.sourceUrl]);
|
|
197
|
+
|
|
198
|
+
const handleKeyDown = useCallback((event: DataTableKeyEvent) => {
|
|
199
|
+
if (isPlainKey(event, "r")) {
|
|
200
|
+
event.preventDefault?.();
|
|
201
|
+
event.stopPropagation?.();
|
|
202
|
+
refresh();
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
if (isPlainKey(event, "t") && selectedTrade?.ticker) {
|
|
206
|
+
event.preventDefault?.();
|
|
207
|
+
event.stopPropagation?.();
|
|
208
|
+
openSelectedTicker();
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
if (isPlainKey(event, "o") && selectedTrade?.sourceUrl) {
|
|
212
|
+
event.preventDefault?.();
|
|
213
|
+
event.stopPropagation?.();
|
|
214
|
+
openSelectedSource();
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}, [openSelectedSource, openSelectedTicker, refresh, selectedTrade?.sourceUrl, selectedTrade?.ticker]);
|
|
219
|
+
|
|
220
|
+
usePaneFooter(`${CONGRESS_TRADES_PANE_ID}:member-detail`, () => ({
|
|
221
|
+
info: [
|
|
222
|
+
...(status === "loading" ? [{ id: "member-loading", parts: [{ text: "loading member trades", tone: "muted" as const }] }] : []),
|
|
223
|
+
...(error ? [{ id: "member-error", parts: [{ text: error, tone: "warning" as const }] }] : []),
|
|
224
|
+
...(maybeTruncated ? [{ id: "member-truncated", parts: [{ text: `limited to ${CONGRESS_MEMBER_TRADE_LIMIT} trades`, tone: "warning" as const }] }] : []),
|
|
225
|
+
],
|
|
226
|
+
hints: [
|
|
227
|
+
{ id: "member-refresh", key: "r", label: "efresh", onPress: refresh },
|
|
228
|
+
{ id: "member-ticker", key: "t", label: "icker", onPress: openSelectedTicker, disabled: !selectedTrade?.ticker },
|
|
229
|
+
{ id: "member-open", key: "o", label: "pen", onPress: openSelectedSource, disabled: !selectedTrade?.sourceUrl },
|
|
230
|
+
],
|
|
231
|
+
}), [
|
|
232
|
+
error,
|
|
233
|
+
maybeTruncated,
|
|
234
|
+
openSelectedSource,
|
|
235
|
+
openSelectedTicker,
|
|
236
|
+
refresh,
|
|
237
|
+
selectedTrade?.sourceUrl,
|
|
238
|
+
selectedTrade?.ticker,
|
|
239
|
+
status,
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
const summary = (
|
|
243
|
+
<Box flexDirection="column" paddingX={1} paddingTop={1} paddingBottom={1}>
|
|
244
|
+
<Box height={1} flexDirection="row">
|
|
245
|
+
<Text fg={colors.textDim}>
|
|
246
|
+
{`${summaryMember.stateDistrict || "--"} ${summaryMember.tradeCount} trades ${summaryMember.buyCount} buys ${summaryMember.sellCount} sells ${formatAmountRange(summaryMember.estimatedLow, summaryMember.estimatedHigh)}`}
|
|
247
|
+
</Text>
|
|
151
248
|
</Box>
|
|
152
|
-
</
|
|
249
|
+
</Box>
|
|
250
|
+
);
|
|
251
|
+
const emptyTitle = status === "loading"
|
|
252
|
+
? "Loading member trades..."
|
|
253
|
+
: error ?? "No trades for this member.";
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<Box flexDirection="column" flexGrow={1} overflow="hidden">
|
|
257
|
+
<DataTableView<CloudCongressTradePayload, TradeColumn>
|
|
258
|
+
focused={focused}
|
|
259
|
+
selection={{
|
|
260
|
+
kind: "id",
|
|
261
|
+
selectedId: selectedTradeId,
|
|
262
|
+
getId: (trade) => trade.id,
|
|
263
|
+
onChange: (id) => setSelectedTradeId(id),
|
|
264
|
+
}}
|
|
265
|
+
onActivate={(trade) => {
|
|
266
|
+
if (trade.ticker) openTicker(trade.ticker);
|
|
267
|
+
}}
|
|
268
|
+
onRootKeyDown={handleKeyDown}
|
|
269
|
+
rootWidth={width}
|
|
270
|
+
rootBefore={summary}
|
|
271
|
+
resetScrollKey={member.id}
|
|
272
|
+
columns={columns}
|
|
273
|
+
items={sortedRows}
|
|
274
|
+
sortColumnId={sortPreference.columnId}
|
|
275
|
+
sortDirection={sortPreference.direction}
|
|
276
|
+
onHeaderClick={(columnId) => {
|
|
277
|
+
setSortPreference((current) => nextSort(
|
|
278
|
+
current,
|
|
279
|
+
columnId as TradeColumnId,
|
|
280
|
+
columnId === "ticker" || columnId === "asset" || columnId === "side" || columnId === "owner" ? "asc" : "desc",
|
|
281
|
+
));
|
|
282
|
+
}}
|
|
283
|
+
getItemKey={(trade) => trade.id}
|
|
284
|
+
renderCell={renderCongressTradeCell}
|
|
285
|
+
emptyStateTitle={emptyTitle}
|
|
286
|
+
showHorizontalScrollbar={false}
|
|
287
|
+
/>
|
|
288
|
+
</Box>
|
|
153
289
|
);
|
|
154
290
|
}
|
|
@@ -7,11 +7,13 @@ import type {
|
|
|
7
7
|
import {
|
|
8
8
|
CONGRESS_TRADES_PANE_ID,
|
|
9
9
|
type CongressTab,
|
|
10
|
+
type DetailMode,
|
|
10
11
|
type LoadStatus,
|
|
11
12
|
} from "./model";
|
|
12
13
|
|
|
13
14
|
export function useCongressTradesFooter({
|
|
14
15
|
activeTab,
|
|
16
|
+
detailMode,
|
|
15
17
|
detailTrade,
|
|
16
18
|
error,
|
|
17
19
|
load,
|
|
@@ -23,6 +25,7 @@ export function useCongressTradesFooter({
|
|
|
23
25
|
status,
|
|
24
26
|
}: {
|
|
25
27
|
activeTab: CongressTab;
|
|
28
|
+
detailMode: DetailMode;
|
|
26
29
|
detailTrade: CloudCongressTradePayload | null;
|
|
27
30
|
error: string | null;
|
|
28
31
|
load: (refresh?: boolean) => void;
|
|
@@ -44,16 +47,19 @@ export function useCongressTradesFooter({
|
|
|
44
47
|
...(status === "loading" ? [{ id: "loading", parts: [{ text: "loading", tone: "muted" as const }] }] : []),
|
|
45
48
|
...(error ? [{ id: "error", parts: [{ text: error, tone: "warning" as const }] }] : []),
|
|
46
49
|
],
|
|
47
|
-
hints:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
hints: detailMode?.kind === "member"
|
|
51
|
+
? []
|
|
52
|
+
: [
|
|
53
|
+
{ id: "refresh", key: "r", label: "efresh", onPress: () => load(true) },
|
|
54
|
+
...(activeTab === "trades" ? [
|
|
55
|
+
{ id: "member", key: "m", label: "ember", onPress: openSelectedTradeMember, disabled: !selectedTrade },
|
|
56
|
+
{ id: "ticker", key: "t", label: "icker", onPress: openSelectedTicker, disabled: !(detailTrade?.ticker ?? selectedTrade?.ticker) },
|
|
57
|
+
{ id: "open", key: "o", label: "pen", onPress: openSelectedTradeSource, disabled: !(detailTrade ?? selectedTrade)?.sourceUrl },
|
|
58
|
+
] : []),
|
|
59
|
+
],
|
|
55
60
|
}), [
|
|
56
61
|
activeTab,
|
|
62
|
+
detailMode,
|
|
57
63
|
detailTrade,
|
|
58
64
|
error,
|
|
59
65
|
load,
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { useCallback
|
|
1
|
+
import { useCallback } from "react";
|
|
2
2
|
import { type DataTableKeyEvent } from "../../../components";
|
|
3
3
|
import { useShortcut } from "../../../react/input";
|
|
4
|
-
import { type ScrollBoxRenderable } from "../../../ui";
|
|
5
4
|
import { isPlainKey } from "../../../utils/keyboard";
|
|
6
5
|
import type { CongressTab, DetailMode } from "./model";
|
|
7
6
|
|
|
8
7
|
export function useCongressTradesKeyboard({
|
|
9
8
|
activeTab,
|
|
10
9
|
detailMode,
|
|
11
|
-
detailScrollRef,
|
|
12
10
|
focused,
|
|
13
11
|
load,
|
|
14
12
|
openSelectedTicker,
|
|
@@ -18,7 +16,6 @@ export function useCongressTradesKeyboard({
|
|
|
18
16
|
}: {
|
|
19
17
|
activeTab: CongressTab;
|
|
20
18
|
detailMode: DetailMode;
|
|
21
|
-
detailScrollRef: RefObject<ScrollBoxRenderable | null>;
|
|
22
19
|
focused: boolean;
|
|
23
20
|
load: (refresh?: boolean) => void;
|
|
24
21
|
openSelectedTicker: () => void;
|
|
@@ -26,25 +23,9 @@ export function useCongressTradesKeyboard({
|
|
|
26
23
|
openSelectedTradeSource: () => void;
|
|
27
24
|
selectTab: (tab: string) => void;
|
|
28
25
|
}) {
|
|
29
|
-
const scrollDetailBy = useCallback((delta: number) => {
|
|
30
|
-
const scrollBox = detailScrollRef.current;
|
|
31
|
-
if (!scrollBox?.viewport) return;
|
|
32
|
-
const maxScrollTop = Math.max(0, scrollBox.scrollHeight - scrollBox.viewport.height);
|
|
33
|
-
scrollBox.scrollTop = Math.max(0, Math.min(maxScrollTop, scrollBox.scrollTop + delta));
|
|
34
|
-
}, [detailScrollRef]);
|
|
35
|
-
|
|
36
26
|
const handleDetailKeyDown = useCallback((event: DataTableKeyEvent) => {
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
event.stopPropagation?.();
|
|
40
|
-
scrollDetailBy(1);
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
if (isPlainKey(event, "k", "up")) {
|
|
44
|
-
event.preventDefault?.();
|
|
45
|
-
event.stopPropagation?.();
|
|
46
|
-
scrollDetailBy(-1);
|
|
47
|
-
return true;
|
|
27
|
+
if (detailMode?.kind === "member") {
|
|
28
|
+
return false;
|
|
48
29
|
}
|
|
49
30
|
if (isPlainKey(event, "o")) {
|
|
50
31
|
event.preventDefault?.();
|
|
@@ -65,7 +46,7 @@ export function useCongressTradesKeyboard({
|
|
|
65
46
|
return true;
|
|
66
47
|
}
|
|
67
48
|
return false;
|
|
68
|
-
}, [openSelectedTicker, openSelectedTradeMember, openSelectedTradeSource
|
|
49
|
+
}, [detailMode?.kind, openSelectedTicker, openSelectedTradeMember, openSelectedTradeSource]);
|
|
69
50
|
|
|
70
51
|
const handleRootKeyDown = useCallback((event: DataTableKeyEvent) => {
|
|
71
52
|
if (isPlainKey(event, "r")) {
|
|
@@ -8,6 +8,8 @@ import type {
|
|
|
8
8
|
export const CONGRESS_TRADES_PANE_ID = "congress-trades";
|
|
9
9
|
export const CONGRESS_TRADE_LIMIT = 200;
|
|
10
10
|
export const CONGRESS_FILING_LIMIT = 20;
|
|
11
|
+
export const CONGRESS_MEMBER_TRADE_LIMIT = 2000;
|
|
12
|
+
export const CONGRESS_MEMBER_FILING_LIMIT = 500;
|
|
11
13
|
|
|
12
14
|
export type CongressTab = "trades" | "members";
|
|
13
15
|
export type LoadStatus = "idle" | "loading" | "loaded" | "error";
|
|
@@ -25,6 +27,7 @@ export type TradeColumnId =
|
|
|
25
27
|
| "side"
|
|
26
28
|
| "ticker"
|
|
27
29
|
| "amount"
|
|
30
|
+
| "asset"
|
|
28
31
|
| "owner";
|
|
29
32
|
export type TradeColumn = DataTableColumn & { id: TradeColumnId };
|
|
30
33
|
export type MemberColumnId =
|
|
@@ -104,6 +107,8 @@ function compareTrade(
|
|
|
104
107
|
return compareText(left.ticker ?? "", right.ticker ?? "");
|
|
105
108
|
case "amount":
|
|
106
109
|
return (left.amountHigh ?? left.amountLow ?? 0) - (right.amountHigh ?? right.amountLow ?? 0);
|
|
110
|
+
case "asset":
|
|
111
|
+
return compareText(left.assetName, right.assetName);
|
|
107
112
|
case "owner":
|
|
108
113
|
return compareText(left.owner, right.owner);
|
|
109
114
|
}
|
|
@@ -172,6 +177,30 @@ export function buildTradeColumns(width: number): TradeColumn[] {
|
|
|
172
177
|
];
|
|
173
178
|
}
|
|
174
179
|
|
|
180
|
+
export function buildMemberTradeColumns(width: number): TradeColumn[] {
|
|
181
|
+
const filedWidth = 7;
|
|
182
|
+
const txWidth = 7;
|
|
183
|
+
const sideWidth = 8;
|
|
184
|
+
const tickerWidth = 12;
|
|
185
|
+
const amountWidth = 14;
|
|
186
|
+
const ownerWidth = 8;
|
|
187
|
+
const lagWidth = 5;
|
|
188
|
+
const assetWidth = Math.max(
|
|
189
|
+
18,
|
|
190
|
+
width - filedWidth - txWidth - sideWidth - tickerWidth - amountWidth - ownerWidth - lagWidth - 9,
|
|
191
|
+
);
|
|
192
|
+
return [
|
|
193
|
+
{ id: "filed", label: "FILED", width: filedWidth, align: "left" },
|
|
194
|
+
{ id: "tx", label: "TX", width: txWidth, align: "left" },
|
|
195
|
+
{ id: "side", label: "SIDE", width: sideWidth, align: "left" },
|
|
196
|
+
{ id: "ticker", label: "TICKER", width: tickerWidth, align: "left" },
|
|
197
|
+
{ id: "amount", label: "AMOUNT", width: amountWidth, align: "right" },
|
|
198
|
+
{ id: "asset", label: "ASSET", width: assetWidth, align: "left" },
|
|
199
|
+
{ id: "owner", label: "OWNER", width: ownerWidth, align: "left" },
|
|
200
|
+
{ id: "lag", label: "LAG", width: lagWidth, align: "right" },
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
|
|
175
204
|
export function buildMemberColumns(width: number): MemberColumn[] {
|
|
176
205
|
const districtWidth = 6;
|
|
177
206
|
const tradesWidth = 7;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { Box, useRendererHost
|
|
2
|
+
import { Box, useRendererHost } from "../../../ui";
|
|
3
3
|
import {
|
|
4
4
|
DataTableStackView,
|
|
5
5
|
EmptyState,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import type { PaneProps } from "../../../types/plugin";
|
|
18
18
|
import {
|
|
19
19
|
CONGRESS_FILING_LIMIT,
|
|
20
|
+
CONGRESS_MEMBER_FILING_LIMIT,
|
|
20
21
|
CONGRESS_TRADE_LIMIT,
|
|
21
22
|
CONGRESS_TRADES_PANE_ID,
|
|
22
23
|
buildMemberColumns,
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
type TradeColumn,
|
|
35
36
|
type TradeColumnId,
|
|
36
37
|
} from "./model";
|
|
37
|
-
import {
|
|
38
|
+
import { MemberTradesDetail, TradeDetail } from "./detail";
|
|
38
39
|
import { useCongressTradesFooter } from "./footer";
|
|
39
40
|
import { useCongressTradesKeyboard } from "./keyboard";
|
|
40
41
|
import {
|
|
@@ -61,7 +62,6 @@ export function CongressTradesPane({ focused, width, height }: PaneProps) {
|
|
|
61
62
|
columnId: "trades",
|
|
62
63
|
direction: "desc",
|
|
63
64
|
});
|
|
64
|
-
const detailScrollRef = useRef<ScrollBoxRenderable | null>(null);
|
|
65
65
|
const fetchGenRef = useRef(0);
|
|
66
66
|
|
|
67
67
|
const load = useCallback((refresh = false) => {
|
|
@@ -142,12 +142,6 @@ export function CongressTradesPane({ focused, width, height }: PaneProps) {
|
|
|
142
142
|
if (detailMode?.kind === "member" && !detailMember) setDetailMode(null);
|
|
143
143
|
}, [detailMember, detailMode, detailTrade]);
|
|
144
144
|
|
|
145
|
-
useEffect(() => {
|
|
146
|
-
if (!detailMode) return;
|
|
147
|
-
const scrollBox = detailScrollRef.current;
|
|
148
|
-
if (scrollBox) scrollBox.scrollTop = 0;
|
|
149
|
-
}, [detailMode]);
|
|
150
|
-
|
|
151
145
|
const selectTab = useCallback((tab: string) => {
|
|
152
146
|
setActiveTab(tab === "members" ? "members" : "trades");
|
|
153
147
|
setDetailMode(null);
|
|
@@ -176,7 +170,6 @@ export function CongressTradesPane({ focused, width, height }: PaneProps) {
|
|
|
176
170
|
const { handleDetailKeyDown, handleRootKeyDown } = useCongressTradesKeyboard({
|
|
177
171
|
activeTab,
|
|
178
172
|
detailMode,
|
|
179
|
-
detailScrollRef,
|
|
180
173
|
focused,
|
|
181
174
|
load,
|
|
182
175
|
openSelectedTicker,
|
|
@@ -187,6 +180,7 @@ export function CongressTradesPane({ focused, width, height }: PaneProps) {
|
|
|
187
180
|
|
|
188
181
|
useCongressTradesFooter({
|
|
189
182
|
activeTab,
|
|
183
|
+
detailMode,
|
|
190
184
|
detailTrade,
|
|
191
185
|
error,
|
|
192
186
|
load,
|
|
@@ -201,11 +195,12 @@ export function CongressTradesPane({ focused, width, height }: PaneProps) {
|
|
|
201
195
|
const detailContent = detailTrade ? (
|
|
202
196
|
<TradeDetail trade={detailTrade} width={width} />
|
|
203
197
|
) : detailMember ? (
|
|
204
|
-
<
|
|
198
|
+
<MemberTradesDetail
|
|
199
|
+
focused={focused}
|
|
205
200
|
member={detailMember}
|
|
206
|
-
|
|
201
|
+
initialTrades={detailMemberTrades}
|
|
207
202
|
width={width}
|
|
208
|
-
|
|
203
|
+
filingLimit={payload?.filingCount ?? CONGRESS_MEMBER_FILING_LIMIT}
|
|
209
204
|
/>
|
|
210
205
|
) : null;
|
|
211
206
|
const detailTitle = detailTrade
|
|
@@ -12,7 +12,14 @@ import {
|
|
|
12
12
|
type MemberColumn,
|
|
13
13
|
type TradeColumn,
|
|
14
14
|
} from "./model";
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
export function sideColor(side: CloudCongressTradePayload["side"], selected: boolean): string {
|
|
17
|
+
if (selected) return colors.selectedText;
|
|
18
|
+
if (side === "BUY") return colors.positive;
|
|
19
|
+
if (side === "SELL") return colors.negative;
|
|
20
|
+
if (side === "EXCHANGE") return colors.warning;
|
|
21
|
+
return colors.textDim;
|
|
22
|
+
}
|
|
16
23
|
|
|
17
24
|
export function renderCongressTradeCell(
|
|
18
25
|
trade: CloudCongressTradePayload,
|
|
@@ -51,6 +58,8 @@ export function renderCongressTradeCell(
|
|
|
51
58
|
text: formatAmountRange(trade.amountLow, trade.amountHigh, trade.amount),
|
|
52
59
|
color: selectedColor ?? colors.textBright,
|
|
53
60
|
};
|
|
61
|
+
case "asset":
|
|
62
|
+
return { text: trade.assetName, color: selectedColor ?? colors.text };
|
|
54
63
|
case "owner":
|
|
55
64
|
return { text: trade.owner, color: selectedColor ?? colors.textDim };
|
|
56
65
|
}
|
|
@@ -1,55 +1,32 @@
|
|
|
1
1
|
import { useCallback, useMemo } from "react";
|
|
2
2
|
import { usePluginState } from "../../../runtime";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_MAX_READ_IDS,
|
|
5
|
+
markReadId,
|
|
6
|
+
normalizeReadIds,
|
|
7
|
+
sameStringArray,
|
|
8
|
+
} from "../../shared/read-state";
|
|
3
9
|
|
|
4
10
|
export interface NewsReadState {
|
|
5
11
|
articleIds: string[];
|
|
6
12
|
}
|
|
7
13
|
|
|
8
14
|
const NEWS_READ_STATE_SCHEMA_VERSION = 1;
|
|
9
|
-
export const MAX_READ_ARTICLE_IDS =
|
|
15
|
+
export const MAX_READ_ARTICLE_IDS = DEFAULT_MAX_READ_IDS;
|
|
10
16
|
|
|
11
17
|
const READ_STATE_KEY = "read-articles";
|
|
12
18
|
const EMPTY_READ_STATE: NewsReadState = { articleIds: [] };
|
|
13
19
|
|
|
14
|
-
function normalizeArticleId(articleId: string): string {
|
|
15
|
-
return articleId.trim();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function sameStringArray(left: string[], right: string[]): boolean {
|
|
19
|
-
if (left.length !== right.length) return false;
|
|
20
|
-
return left.every((value, index) => value === right[index]);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
20
|
export function normalizeNewsReadState(state: NewsReadState): NewsReadState {
|
|
24
|
-
|
|
25
|
-
const articleIds: string[] = [];
|
|
26
|
-
|
|
27
|
-
for (const rawId of state.articleIds) {
|
|
28
|
-
if (typeof rawId !== "string") continue;
|
|
29
|
-
const articleId = normalizeArticleId(rawId);
|
|
30
|
-
if (!articleId || seen.has(articleId)) continue;
|
|
31
|
-
seen.add(articleId);
|
|
32
|
-
articleIds.push(articleId);
|
|
33
|
-
if (articleIds.length >= MAX_READ_ARTICLE_IDS) break;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return { articleIds };
|
|
21
|
+
return { articleIds: normalizeReadIds(state.articleIds, MAX_READ_ARTICLE_IDS) };
|
|
37
22
|
}
|
|
38
23
|
|
|
39
24
|
export function markNewsArticleRead(
|
|
40
25
|
state: NewsReadState,
|
|
41
26
|
articleId: string,
|
|
42
27
|
): NewsReadState {
|
|
43
|
-
const normalizedId = normalizeArticleId(articleId);
|
|
44
28
|
const current = normalizeNewsReadState(state).articleIds;
|
|
45
|
-
|
|
46
|
-
return sameStringArray(current, state.articleIds) ? state : { articleIds: current };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const articleIds = [
|
|
50
|
-
normalizedId,
|
|
51
|
-
...current.filter((currentId) => currentId !== normalizedId),
|
|
52
|
-
].slice(0, MAX_READ_ARTICLE_IDS);
|
|
29
|
+
const articleIds = markReadId(current, articleId, MAX_READ_ARTICLE_IDS);
|
|
53
30
|
|
|
54
31
|
return sameStringArray(articleIds, state.articleIds) ? state : { articleIds };
|
|
55
32
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { saveConfig } from "../../../../data/config/store";
|
|
2
|
+
import { countCollectionTickers } from "../../../../cli/helpers";
|
|
2
3
|
import { resolveTickerForCli } from "../../../../cli/ticker-resolution";
|
|
3
4
|
import { cliStyles, renderStat } from "../../../../utils/cli-output";
|
|
4
5
|
import { formatMarketCostWithCurrency, formatMarketQuantity } from "../../../../market-data/market/format";
|
|
@@ -17,6 +18,28 @@ import { parseFiniteNumber, requireManualPortfolio } from "./shared";
|
|
|
17
18
|
async function listCollections(ctx: CliCommandContext) {
|
|
18
19
|
const { config, store, persistence } = await ctx.initConfigData();
|
|
19
20
|
const tickers = await store.loadAllTickers();
|
|
21
|
+
if (ctx.cliOptions.format !== "text") {
|
|
22
|
+
ctx.printResult({
|
|
23
|
+
data: {
|
|
24
|
+
portfolios: config.portfolios.map((portfolio) => ({
|
|
25
|
+
id: portfolio.id,
|
|
26
|
+
name: portfolio.name,
|
|
27
|
+
currency: portfolio.currency,
|
|
28
|
+
brokerId: portfolio.brokerId ?? "",
|
|
29
|
+
brokerInstanceId: portfolio.brokerInstanceId ?? "",
|
|
30
|
+
brokerAccountId: portfolio.brokerAccountId ?? "",
|
|
31
|
+
tickerCount: countCollectionTickers(tickers, "portfolios", portfolio.id),
|
|
32
|
+
})),
|
|
33
|
+
watchlists: config.watchlists.map((watchlist) => ({
|
|
34
|
+
id: watchlist.id,
|
|
35
|
+
name: watchlist.name,
|
|
36
|
+
tickerCount: countCollectionTickers(tickers, "watchlists", watchlist.id),
|
|
37
|
+
})),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
persistence.close();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
20
43
|
console.log(renderCollectionOverview(config, tickers));
|
|
21
44
|
persistence.close();
|
|
22
45
|
}
|