gloomberb 0.10.2 → 0.10.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 +3 -3
- package/README.zh-CN.md +2 -2
- package/package.json +2 -1
- package/src/api-client/auth.ts +8 -0
- package/src/api-client/index.ts +70 -0
- package/src/api-client/types.ts +49 -0
- package/src/app/runtime/desktop-deeplink.test.ts +8 -0
- package/src/app/runtime/desktop-deeplink.ts +14 -1
- package/src/components/chart/composite/composite-chart.test.tsx +340 -17
- package/src/components/chart/composite/composite-chart.tsx +859 -68
- package/src/components/chart/composite/interactions.test.ts +78 -22
- package/src/components/chart/composite/interactions.ts +45 -17
- package/src/components/chart/composite/rasterizer.ts +8 -2
- package/src/components/chart/composite/renderers.test.ts +61 -1
- package/src/components/chart/composite/scene.ts +14 -4
- package/src/components/chart/composite/tools.test.ts +371 -0
- package/src/components/chart/composite/tools.ts +508 -0
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart/core/pointer.ts +2 -0
- package/src/components/chart/native/raster/primitives.ts +18 -0
- package/src/components/chart/price-axis-labels.tsx +42 -2
- package/src/components/chart/static/chart/axis-overlays.test.tsx +2 -0
- package/src/components/chart/static/chart/axis-overlays.tsx +46 -0
- package/src/components/command-bar/action-runtime.ts +3 -0
- package/src/components/command-bar/assist/inventory.test.ts +93 -0
- package/src/components/command-bar/assist/inventory.ts +92 -0
- package/src/components/command-bar/assist/model.test.ts +104 -0
- package/src/components/command-bar/assist/model.ts +169 -0
- package/src/components/command-bar/assist/runtime.ts +185 -0
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/keyboard-shortcuts.ts +5 -0
- package/src/components/command-bar/list/model.ts +15 -2
- package/src/components/command-bar/list/navigation.ts +6 -0
- package/src/components/command-bar/list/view.tsx +16 -3
- package/src/components/command-bar/pane-templates/workflow.ts +1 -0
- package/src/components/command-bar/panel/body.tsx +2 -0
- package/src/components/command-bar/panel/index.tsx +3 -0
- package/src/components/command-bar/panel/runtime.ts +6 -0
- package/src/components/command-bar/panel/types.ts +1 -0
- package/src/components/command-bar/routes/root/results.ts +28 -2
- package/src/components/command-bar/routes/root/runtime.ts +66 -36
- package/src/components/command-bar/routes/root/shortcuts.ts +8 -4
- package/src/components/command-bar/routing/navigation-state.ts +17 -0
- package/src/components/command-bar/selection-runtime.ts +26 -0
- package/src/components/command-bar/surface/assist.test.tsx +276 -0
- package/src/components/command-bar/surface/index.tsx +93 -2
- package/src/components/command-bar/surface/portfolio.test.tsx +2 -1
- package/src/components/command-bar/view-model.ts +7 -1
- package/src/components/command-bar/workflow/broker.test.ts +22 -0
- package/src/components/command-bar/workflow/broker.ts +1 -0
- package/src/components/index.ts +1 -1
- package/src/components/layout/detached-pane-shell.tsx +40 -2
- package/src/components/layout/floating-pane.tsx +4 -1
- package/src/components/layout/pane/header.tsx +64 -6
- package/src/components/layout/pane/index.tsx +4 -1
- package/src/components/layout/shell/index.tsx +23 -0
- package/src/components/layout/shell/pane/layers.tsx +5 -0
- package/src/components/onboarding/account-step/chooser-panel.tsx +74 -0
- package/src/components/onboarding/account-step/form-panel.tsx +157 -0
- package/src/components/onboarding/account-step/index.tsx +58 -0
- package/src/components/onboarding/account-step/model.test.ts +67 -0
- package/src/components/onboarding/account-step/model.ts +126 -0
- package/src/components/onboarding/account-step/signed-in-panel.tsx +31 -0
- package/src/components/onboarding/onboarding-steps.tsx +91 -55
- package/src/components/onboarding/onboarding-wizard.test.tsx +73 -6
- package/src/components/onboarding/onboarding-wizard.tsx +93 -9
- package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +3 -3
- package/src/components/onboarding/wizard-account.ts +235 -0
- package/src/components/onboarding/wizard-keyboard.ts +67 -1
- package/src/components/onboarding/wizard-model.ts +2 -2
- package/src/components/ui/data-table/index.tsx +1 -0
- package/src/components/ui/data-table/opentui/index.tsx +44 -2
- package/src/components/ui/data-table/types.ts +10 -0
- package/src/components/ui/data-table/visible-range.test.ts +29 -0
- package/src/components/ui/data-table/visible-range.ts +21 -0
- package/src/components/ui/ui.test.tsx +23 -1
- package/src/i18n/es.ts +1093 -0
- package/src/i18n/index.test.ts +5 -2
- package/src/i18n/index.ts +3 -0
- package/src/i18n/ja.ts +66 -3
- package/src/i18n/ko.ts +66 -3
- package/src/i18n/languages.test.ts +4 -1
- package/src/i18n/languages.ts +12 -1
- package/src/i18n/zh-cn.ts +66 -3
- package/src/i18n/zh-tw.ts +66 -3
- package/src/market-data/coordinator/index.ts +1 -1
- package/src/market-data/coordinator/quotes.ts +64 -1
- package/src/market-data/coordinator/subscriptions.test.ts +72 -0
- package/src/market-data/quotes/cadence.ts +2 -0
- package/src/plugins/builtin/account-management/footer.ts +9 -3
- package/src/plugins/builtin/account-management/model.ts +53 -1
- package/src/plugins/builtin/account-management/pane.tsx +107 -24
- package/src/plugins/builtin/ai/index.tsx +7 -2
- package/src/plugins/builtin/ai/screener/market-runtime.ts +4 -2
- package/src/plugins/builtin/ai/screener/pane.tsx +3 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +0 -1
- package/src/plugins/builtin/chart-composer/index.tsx +10 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +111 -44
- package/src/plugins/builtin/chart-composer/quick-add.tsx +27 -3
- package/src/plugins/builtin/chart-composer/semantic.ts +21 -0
- package/src/plugins/builtin/chat/controller/persistence.ts +5 -0
- package/src/plugins/builtin/cloud/plugin.tsx +9 -1
- package/src/plugins/builtin/cloud/upgrade-command.ts +28 -0
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +54 -0
- package/src/plugins/builtin/help/index.tsx +38 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +18 -38
- package/src/plugins/builtin/options/footer.test.ts +20 -112
- package/src/plugins/builtin/options/footer.ts +35 -78
- package/src/plugins/builtin/options/index.tsx +6 -0
- package/src/plugins/builtin/options/live-quotes.test.ts +117 -14
- package/src/plugins/builtin/options/live-quotes.ts +93 -39
- package/src/plugins/builtin/options/view.test.tsx +5 -2
- package/src/plugins/builtin/options/view.tsx +49 -9
- package/src/plugins/builtin/portfolio-list/index.tsx +12 -4
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +22 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +17 -13
- package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.test.tsx +76 -0
- package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.ts +78 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +131 -0
- package/src/plugins/builtin/shared/live-streaming.ts +60 -0
- package/src/plugins/builtin/shared/plan-access.test.ts +69 -0
- package/src/plugins/builtin/shared/plan-access.ts +109 -0
- package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +11 -2
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -3
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +4 -2
- package/src/plugins/registry/index.test.ts +51 -0
- package/src/plugins/registry/index.ts +12 -0
- package/src/plugins/registry/pane-settings.ts +29 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +44 -3
- package/src/renderers/electrobun/view/host/box.test.tsx +97 -0
- package/src/renderers/electrobun/view/host/box.tsx +3 -0
- package/src/renderers/electrobun/view/host/chart-surface.tsx +85 -1
- package/src/renderers/electrobun/view/host/focus-scope.test.ts +37 -0
- package/src/renderers/electrobun/view/host/focus-scope.ts +56 -0
- package/src/renderers/electrobun/view/host/input.tsx +16 -1
- package/src/renderers/electrobun/view/host/mouse.ts +1 -0
- package/src/renderers/electrobun/view/host/style.ts +1 -1
- package/src/renderers/electrobun/view/host/tabs.tsx +4 -2
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/renderers/opentui/chart-surface.tsx +2 -0
- package/src/renderers/opentui/host.tsx +3 -0
- package/src/sources/gloomberb-cloud/index.test.ts +8 -0
- package/src/sources/gloomberb-cloud/index.ts +4 -4
- package/src/sources/provider-router/history.test.ts +3 -3
- package/src/sources/provider-router/history.ts +15 -4
- package/src/sources/yahoo-finance/history.test.ts +47 -0
- package/src/sources/yahoo-finance/history.ts +5 -1
- package/src/state/hooks/quote-streaming.test.tsx +181 -1
- package/src/state/hooks/quote-streaming.ts +93 -7
- package/src/theme/colors.ts +20 -1
- package/src/theme/themes.test.ts +3 -0
- package/src/{sources/gloomberb-cloud → time-series}/history-quality.ts +27 -3
- package/src/time-series/resolution.test.ts +51 -0
- package/src/time-series/resolution.ts +41 -0
- package/src/time-series/resolve.test.ts +207 -1
- package/src/time-series/resolve.ts +196 -29
- package/src/time-series/spec-studies.test.ts +4 -0
- package/src/time-series/spec.ts +15 -1
- package/src/time-series/types.ts +7 -0
- package/src/time-series/use-chart-resolution.test.tsx +37 -0
- package/src/time-series/use-chart-resolution.ts +17 -1
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +17 -0
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
2
|
import type { DataProvider } from "../../../types/data-provider";
|
|
3
3
|
import type { AppTickerRepositoryPort } from "../../../core/app-service-ports";
|
|
4
4
|
import type { PluginRegistry } from "../../../plugins/registry";
|
|
5
5
|
import type { LayoutBounds } from "../../../plugins/pane-manager";
|
|
6
|
+
import { usePlanAccess } from "../../../plugins/builtin/shared/plan-access";
|
|
7
|
+
import { buildAssistCommandInventory } from "../assist/inventory";
|
|
8
|
+
import { useCommandBarAssist } from "../assist/runtime";
|
|
9
|
+
import { shouldAutoAskAssist, type AssistRowHandlers } from "../assist/model";
|
|
6
10
|
import { useRouteListState } from "../routing/list-state";
|
|
7
11
|
import { useCommandBarRootRuntime } from "../routes/root/runtime";
|
|
12
|
+
import { parseRootShortcutIntent } from "../routes/root/shortcuts";
|
|
8
13
|
import { useCommandBarThemePreview } from "../theme-preview";
|
|
9
14
|
import { CommandBarPanel } from "../panel";
|
|
10
15
|
import { useCommandBarNavigationState } from "../routing/navigation-state";
|
|
@@ -71,6 +76,7 @@ export function CommandBar({
|
|
|
71
76
|
currentRouteRef,
|
|
72
77
|
dismissCommandBar,
|
|
73
78
|
lastMainBrowseRef,
|
|
79
|
+
markRootSelectionNavigated,
|
|
74
80
|
popRoute,
|
|
75
81
|
pushRoute,
|
|
76
82
|
rootHoveredIdx,
|
|
@@ -78,6 +84,7 @@ export function CommandBar({
|
|
|
78
84
|
rootModeKindRef,
|
|
79
85
|
rootQuery,
|
|
80
86
|
rootQueryRef,
|
|
87
|
+
rootSelectionNavigatedRef,
|
|
81
88
|
rootSelectedIdx,
|
|
82
89
|
setRootHoveredIdx,
|
|
83
90
|
setRootQuery,
|
|
@@ -104,6 +111,7 @@ export function CommandBar({
|
|
|
104
111
|
createPluginCommandItem,
|
|
105
112
|
executeCollectionCommand,
|
|
106
113
|
getAvailablePaneShortcutTemplates,
|
|
114
|
+
getAvailablePaneTemplates,
|
|
107
115
|
getAvailablePluginCommands,
|
|
108
116
|
ensureRouteFieldFocus,
|
|
109
117
|
focusWorkflowField,
|
|
@@ -115,6 +123,7 @@ export function CommandBar({
|
|
|
115
123
|
openInlineConfirm,
|
|
116
124
|
openModeRoute,
|
|
117
125
|
openPaneTemplateWorkflow,
|
|
126
|
+
openPluginCommandWorkflow,
|
|
118
127
|
openWorkflowFieldPicker,
|
|
119
128
|
paneShortcutItems,
|
|
120
129
|
persistLayoutChange,
|
|
@@ -162,6 +171,82 @@ export function CommandBar({
|
|
|
162
171
|
const getTickerSearchTickers = useCallback(() => stateRef.current.tickers, []);
|
|
163
172
|
const hasPaneSettings = useCallback((paneId: string) => pluginRegistry.hasPaneSettings(paneId), [pluginRegistry]);
|
|
164
173
|
|
|
174
|
+
const rootShortcutIntent = useMemo(() => parseRootShortcutIntent({
|
|
175
|
+
query: rootQuery,
|
|
176
|
+
commands: availableCommands,
|
|
177
|
+
pluginCommands: getAvailablePluginCommands(),
|
|
178
|
+
paneTemplates: getAvailablePaneShortcutTemplates(rootQuery),
|
|
179
|
+
activeTicker: activeTickerSymbol,
|
|
180
|
+
}), [activeTickerSymbol, availableCommands, getAvailablePaneShortcutTemplates, getAvailablePluginCommands, rootQuery]);
|
|
181
|
+
|
|
182
|
+
const planAccess = usePlanAccess();
|
|
183
|
+
const buildAssistInventory = useCallback(() => buildAssistCommandInventory({
|
|
184
|
+
commands: availableCommands,
|
|
185
|
+
pluginCommands: getAvailablePluginCommands(),
|
|
186
|
+
paneTemplates: getAvailablePaneTemplates(undefined, { includePromptableTickerTemplates: true }),
|
|
187
|
+
}), [availableCommands, getAvailablePaneTemplates, getAvailablePluginCommands]);
|
|
188
|
+
// Only the root list asks on its own, and only for text the prefix parser
|
|
189
|
+
// could not claim — otherwise the user is mid-command, not mid-question.
|
|
190
|
+
const assistAutoAsk = !currentRoute
|
|
191
|
+
&& planAccess.emailVerified
|
|
192
|
+
&& shouldAutoAskAssist({ query: rootQuery, hasShortcutIntent: rootShortcutIntent.kind !== "none" });
|
|
193
|
+
const { assistActive, assistState, askAssist, resetAssist } = useCommandBarAssist({
|
|
194
|
+
autoAsk: assistAutoAsk,
|
|
195
|
+
getInventory: buildAssistInventory,
|
|
196
|
+
rootQuery,
|
|
197
|
+
});
|
|
198
|
+
// Filled in below once the selection runtime exists, so an AI candidate runs
|
|
199
|
+
// through the very same submit path as text the user typed.
|
|
200
|
+
const runRootQueryRef = useRef<
|
|
201
|
+
((query: string, options?: { fallbackPrefix?: string }) => void) | null
|
|
202
|
+
>(null);
|
|
203
|
+
/**
|
|
204
|
+
* Query whose answer the user is already waiting on, set by activating the
|
|
205
|
+
* "Thinking…" row. The row leads the list and holds the default selection, so
|
|
206
|
+
* Enter has to mean something even before the answer is back: it claims the
|
|
207
|
+
* answer, and the best candidate runs the moment it lands.
|
|
208
|
+
*/
|
|
209
|
+
const assistPendingRunRef = useRef<string | null>(null);
|
|
210
|
+
const askAssistNow = useCallback(() => {
|
|
211
|
+
assistPendingRunRef.current = rootQueryRef.current.trim();
|
|
212
|
+
askAssist();
|
|
213
|
+
}, [askAssist, rootQueryRef]);
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
const pendingQuery = assistPendingRunRef.current;
|
|
216
|
+
if (!pendingQuery) return;
|
|
217
|
+
// Still the very ask that was claimed; nothing to do until it answers.
|
|
218
|
+
if (assistState.status === "loading" && assistState.query === pendingQuery) return;
|
|
219
|
+
assistPendingRunRef.current = null;
|
|
220
|
+
if (assistState.status !== "answered" || assistState.query !== pendingQuery) return;
|
|
221
|
+
// Typing moved on, so the answer is no longer what the user is looking at.
|
|
222
|
+
if (rootQueryRef.current.trim() !== pendingQuery) return;
|
|
223
|
+
const candidate = assistState.candidates[0];
|
|
224
|
+
if (!candidate) return;
|
|
225
|
+
runRootQueryRef.current?.(
|
|
226
|
+
candidate.input,
|
|
227
|
+
candidate.prefix ? { fallbackPrefix: candidate.prefix } : undefined,
|
|
228
|
+
);
|
|
229
|
+
}, [assistState, rootQueryRef]);
|
|
230
|
+
const startAssistSignUp = useCallback(() => {
|
|
231
|
+
const signUpCommand = getAvailablePluginCommands().find((command) => command.id === "auth-signup");
|
|
232
|
+
if (signUpCommand?.wizard?.length) {
|
|
233
|
+
openPluginCommandWorkflow(signUpCommand);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
setRootQuery("Sign Up");
|
|
237
|
+
}, [getAvailablePluginCommands, openPluginCommandWorkflow, setRootQuery]);
|
|
238
|
+
const assist = useMemo<AssistRowHandlers>(() => ({
|
|
239
|
+
enabled: planAccess.emailVerified,
|
|
240
|
+
auto: assistAutoAsk && assistActive,
|
|
241
|
+
state: assistState,
|
|
242
|
+
onAsk: askAssistNow,
|
|
243
|
+
onSignUp: startAssistSignUp,
|
|
244
|
+
onRunCandidate: (input: string, prefix?: string) => runRootQueryRef.current?.(
|
|
245
|
+
input,
|
|
246
|
+
prefix ? { fallbackPrefix: prefix } : undefined,
|
|
247
|
+
),
|
|
248
|
+
}), [askAssistNow, assistActive, assistAutoAsk, assistState, planAccess.emailVerified, startAssistSignUp]);
|
|
249
|
+
|
|
165
250
|
const {
|
|
166
251
|
activeMatch,
|
|
167
252
|
orderedRootResults,
|
|
@@ -176,6 +261,7 @@ export function CommandBar({
|
|
|
176
261
|
activePortfolio,
|
|
177
262
|
activeTickerData,
|
|
178
263
|
activeTickerSymbol,
|
|
264
|
+
assist,
|
|
179
265
|
availableCommands,
|
|
180
266
|
buildLayoutItems,
|
|
181
267
|
buildPaneSettingItems,
|
|
@@ -188,7 +274,6 @@ export function CommandBar({
|
|
|
188
274
|
dataProvider,
|
|
189
275
|
executeCollectionCommand,
|
|
190
276
|
getAvailablePaneShortcutTemplates,
|
|
191
|
-
getAvailablePluginCommands,
|
|
192
277
|
getTickers: getTickerSearchTickers,
|
|
193
278
|
hasPaneSettings,
|
|
194
279
|
localTickerSearchResultItems,
|
|
@@ -201,6 +286,8 @@ export function CommandBar({
|
|
|
201
286
|
readTickerSearchCache,
|
|
202
287
|
rootModeKind: rootModeInfo.kind,
|
|
203
288
|
rootQuery,
|
|
289
|
+
rootSelectionNavigatedRef,
|
|
290
|
+
rootShortcutIntent,
|
|
204
291
|
runDirectCommand,
|
|
205
292
|
runSecurityDescriptionShortcut,
|
|
206
293
|
setRootHoveredIdx,
|
|
@@ -217,6 +304,7 @@ export function CommandBar({
|
|
|
217
304
|
acceptRootShortcutTab,
|
|
218
305
|
acceptSelectedShortcutTab,
|
|
219
306
|
activateListSelection,
|
|
307
|
+
runRootQuery,
|
|
220
308
|
setActiveListQuery,
|
|
221
309
|
} = useCommandBarSelectionRuntime({
|
|
222
310
|
activeTickerSymbol,
|
|
@@ -251,6 +339,7 @@ export function CommandBar({
|
|
|
251
339
|
updateWorkflowValue,
|
|
252
340
|
visibleListStateRef,
|
|
253
341
|
});
|
|
342
|
+
runRootQueryRef.current = runRootQuery;
|
|
254
343
|
|
|
255
344
|
const routeListState = useRouteListState({
|
|
256
345
|
activeMatch,
|
|
@@ -301,6 +390,7 @@ export function CommandBar({
|
|
|
301
390
|
focusWorkflowField,
|
|
302
391
|
getWorkflowInputRef,
|
|
303
392
|
getWorkflowFieldStringValue,
|
|
393
|
+
markRootSelectionNavigated,
|
|
304
394
|
moveWorkflowFocus,
|
|
305
395
|
nativeListScrollRef,
|
|
306
396
|
nativePaneChrome,
|
|
@@ -309,6 +399,7 @@ export function CommandBar({
|
|
|
309
399
|
persistConfig,
|
|
310
400
|
pluginRegistry,
|
|
311
401
|
popRoute,
|
|
402
|
+
resetAssist,
|
|
312
403
|
rootModeKind: rootModeInfo.kind,
|
|
313
404
|
rootGhostSuffix,
|
|
314
405
|
rootQueryLength: rootQuery.length,
|
|
@@ -265,7 +265,8 @@ describe("CommandBar portfolio commands", () => {
|
|
|
265
265
|
|
|
266
266
|
await testSetup.renderOnce();
|
|
267
267
|
const frame = testSetup.captureCharFrame();
|
|
268
|
-
|
|
268
|
+
// Nothing local matches, so the AI fallback row is all that is offered.
|
|
269
|
+
expect(frame).toContain("Ask AI");
|
|
269
270
|
expect(frame).not.toContain("Create or update a manual position in a portfolio");
|
|
270
271
|
});
|
|
271
272
|
|
|
@@ -28,6 +28,8 @@ export interface CommandBarItemView {
|
|
|
28
28
|
right?: string;
|
|
29
29
|
checked?: boolean;
|
|
30
30
|
current?: boolean;
|
|
31
|
+
accent?: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
export interface CommandBarRowPresentation {
|
|
@@ -36,6 +38,7 @@ export interface CommandBarRowPresentation {
|
|
|
36
38
|
trailing: string;
|
|
37
39
|
selected: boolean;
|
|
38
40
|
primaryMuted: boolean;
|
|
41
|
+
trailingAccent: boolean;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
export function resolveCommandBarMode(query: string, commandList?: Command[]): CommandBarModeInfo {
|
|
@@ -110,7 +113,7 @@ export function getEmptyState(mode: CommandBarMode, query: string, searchQuery?:
|
|
|
110
113
|
|
|
111
114
|
export function getRowPresentation(item: CommandBarItemView, selected: boolean, showTrailing: boolean): CommandBarRowPresentation {
|
|
112
115
|
const glyph = selected ? "\u203a" : " ";
|
|
113
|
-
const primaryMuted = item.kind === "plugin" && !item.checked;
|
|
116
|
+
const primaryMuted = (item.kind === "plugin" && !item.checked) || item.disabled === true;
|
|
114
117
|
let trailing = "";
|
|
115
118
|
|
|
116
119
|
if (showTrailing) {
|
|
@@ -125,6 +128,7 @@ export function getRowPresentation(item: CommandBarItemView, selected: boolean,
|
|
|
125
128
|
trailing: t(trailing),
|
|
126
129
|
selected,
|
|
127
130
|
primaryMuted,
|
|
131
|
+
trailingAccent: item.accent === true && trailing.length > 0,
|
|
128
132
|
};
|
|
129
133
|
}
|
|
130
134
|
|
|
@@ -134,6 +138,8 @@ export function truncateText(text: string, width: number): string {
|
|
|
134
138
|
|
|
135
139
|
function getCategoryPriority(category: string, sectionOrder: CommandBarSectionOrder = "default"): number {
|
|
136
140
|
const normalized = category.trim().toLowerCase();
|
|
141
|
+
// The AI answers the question the user actually typed, so it leads the list.
|
|
142
|
+
if (normalized === "ask ai") return -100;
|
|
137
143
|
if (normalized === "exact match") return -50;
|
|
138
144
|
if (sectionOrder === "app-first") {
|
|
139
145
|
if (normalized === "saved") return 100;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import type { BrokerAdapter } from "../../../types/broker";
|
|
3
|
+
import { buildBrokerChoices } from "./broker";
|
|
4
|
+
|
|
5
|
+
function createBroker(id: string, name: string): BrokerAdapter {
|
|
6
|
+
return {
|
|
7
|
+
id,
|
|
8
|
+
name,
|
|
9
|
+
configSchema: [{ key: "apiKey", label: "API key", type: "password", required: true }],
|
|
10
|
+
validate: async () => true,
|
|
11
|
+
importPositions: async () => [],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test("shows external brokers as sorted top-level choices", () => {
|
|
16
|
+
const choices = buildBrokerChoices(new Map([
|
|
17
|
+
["ibkr", createBroker("ibkr", "Interactive Brokers")],
|
|
18
|
+
["newbroker", createBroker("newbroker", "newbroker")],
|
|
19
|
+
]));
|
|
20
|
+
|
|
21
|
+
expect(choices.map((choice) => choice.id)).toEqual(["ibkr", "newbroker"]);
|
|
22
|
+
});
|
|
@@ -23,6 +23,7 @@ export interface CommandBarBrokerChoice {
|
|
|
23
23
|
export function buildBrokerChoices(brokers: ReadonlyMap<string, BrokerAdapter>): CommandBarBrokerChoice[] {
|
|
24
24
|
return [...brokers.values()]
|
|
25
25
|
.filter((adapter) => adapter.configSchema.length > 0)
|
|
26
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
26
27
|
.map((adapter) => ({
|
|
27
28
|
id: adapter.id,
|
|
28
29
|
label: adapter.name,
|
package/src/components/index.ts
CHANGED
|
@@ -41,7 +41,7 @@ export { Checkbox } from "./ui/checkbox";
|
|
|
41
41
|
export { ConfirmDialog } from "./ui/confirm-dialog";
|
|
42
42
|
export { ChoiceDialog } from "./ui/choice-dialog";
|
|
43
43
|
export type { ChoiceDialogChoice } from "./ui/choice-dialog";
|
|
44
|
-
export type { DataTableCell, DataTableColumn } from "./ui/data-table";
|
|
44
|
+
export type { DataTableCell, DataTableColumn, DataTableVisibleRange } from "./ui/data-table";
|
|
45
45
|
export { EmptyState } from "./ui/status";
|
|
46
46
|
export { getMessageComposerBlockHeight, MessageComposer } from "./ui/message-composer";
|
|
47
47
|
export { NumberField, TextField } from "./ui/fields";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box, Text, useRendererHost, useUiCapabilities } from "../../ui";
|
|
1
|
+
import { Box, Span, Text, useRendererHost, useUiCapabilities } from "../../ui";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { t } from "../../i18n";
|
|
4
4
|
import { useShortcut, useViewport } from "../../react/input";
|
|
@@ -49,6 +49,7 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
49
49
|
const instance = useAppSelector((state) => findPaneInstance(state.config.layout, desktopWindowBridge.paneId) ?? null);
|
|
50
50
|
const paneDef = instance ? pluginRegistry.panes.get(instance.paneId) ?? null : null;
|
|
51
51
|
const hasPaneSettings = !!instance && pluginRegistry.hasPaneSettings(instance.instanceId);
|
|
52
|
+
const quickSettings = instance ? pluginRegistry.resolvePaneQuickSettings(instance.instanceId) : [];
|
|
52
53
|
const titleState = useMemo(
|
|
53
54
|
() => ({ config, paneState }) as Parameters<typeof getPaneDisplayTitle>[0],
|
|
54
55
|
[config, paneState],
|
|
@@ -118,6 +119,16 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
118
119
|
stopMouse(event);
|
|
119
120
|
pluginRegistry.openPaneSettingsFn(desktopWindowBridge.paneId);
|
|
120
121
|
}, [desktopWindowBridge.paneId, pluginRegistry]);
|
|
122
|
+
const toggleQuickSetting = useCallback((key: string, event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
|
|
123
|
+
stopMouse(event);
|
|
124
|
+
focusPane();
|
|
125
|
+
void pluginRegistry.togglePaneQuickSetting(desktopWindowBridge.paneId, key).catch((error) => {
|
|
126
|
+
pluginRegistry.notify({
|
|
127
|
+
body: error instanceof Error ? error.message : "Could not update pane setting.",
|
|
128
|
+
type: "error",
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}, [desktopWindowBridge.paneId, focusPane, pluginRegistry]);
|
|
121
132
|
|
|
122
133
|
if (!instance || !paneDef) {
|
|
123
134
|
return (
|
|
@@ -183,9 +194,36 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
183
194
|
paddingRight={showWindowControls ? 0 : 1}
|
|
184
195
|
style={{ position: "relative" }}
|
|
185
196
|
>
|
|
186
|
-
<Box
|
|
197
|
+
<Box minWidth={0} flexShrink={1} overflow="hidden">
|
|
187
198
|
<Text fg={paneTitleText(focused, true, colors)} selectable={false} data-gloom-role="pane-title">{title}</Text>
|
|
188
199
|
</Box>
|
|
200
|
+
{quickSettings.map((setting) => (
|
|
201
|
+
<Box
|
|
202
|
+
key={setting.key}
|
|
203
|
+
height={1}
|
|
204
|
+
minWidth={20}
|
|
205
|
+
paddingLeft={1}
|
|
206
|
+
paddingRight={1}
|
|
207
|
+
alignItems="center"
|
|
208
|
+
justifyContent="center"
|
|
209
|
+
className="electrobun-webkit-app-region-no-drag"
|
|
210
|
+
data-gloom-role="pane-quick-setting"
|
|
211
|
+
data-setting-key={setting.key}
|
|
212
|
+
data-gloom-interactive="true"
|
|
213
|
+
aria-label={`${setting.label}: ${setting.value ? "on" : "off"}`}
|
|
214
|
+
aria-pressed={setting.value}
|
|
215
|
+
title={`${setting.label}: ${setting.value ? "on" : "off"}`}
|
|
216
|
+
style={{ cursor: "pointer" }}
|
|
217
|
+
onMouseDown={(event: any) => toggleQuickSetting(setting.key, event)}
|
|
218
|
+
>
|
|
219
|
+
<Span style={{ display: "inline-flex", width: 12, height: 12, color: setting.value ? colors.warning : colors.textDim }}>
|
|
220
|
+
<svg viewBox="0 0 12 12" width="12" height="12" fill="none" aria-hidden="true">
|
|
221
|
+
<path d="M7.1 1.2 2.7 6.5h3.1l-.7 4.3 4.4-5.5H6.4l.7-4.1Z" fill="currentColor" />
|
|
222
|
+
</svg>
|
|
223
|
+
</Span>
|
|
224
|
+
</Box>
|
|
225
|
+
))}
|
|
226
|
+
<Box flexGrow={1} minWidth={0} />
|
|
189
227
|
{hasPaneSettings && (
|
|
190
228
|
<Text
|
|
191
229
|
fg={paneTitleText(focused, true, colors)}
|
|
@@ -2,7 +2,7 @@ import { Box, Text, useUiCapabilities } from "../../ui";
|
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import { colors, floatingPaneBg } from "../../theme/colors";
|
|
4
4
|
import { PaneBodyFrame, getPaneWindowAttributes } from "./pane/frame";
|
|
5
|
-
import { PaneHeader } from "./pane/header";
|
|
5
|
+
import { PaneHeader, type PaneHeaderQuickSetting } from "./pane/header";
|
|
6
6
|
import { hasPaneFooterContent, PaneFooterBar, type CombinedPaneFooter } from "./pane/footer";
|
|
7
7
|
import { resolvePaneBodyFrame, shouldReservePaneFooter } from "./pane/sizing";
|
|
8
8
|
|
|
@@ -17,6 +17,7 @@ interface FloatingPaneWrapperProps {
|
|
|
17
17
|
focused: boolean;
|
|
18
18
|
windowModeSelected?: boolean;
|
|
19
19
|
showActions?: boolean;
|
|
20
|
+
quickSettings?: PaneHeaderQuickSetting[];
|
|
20
21
|
onMouseDown?: (event: any) => void;
|
|
21
22
|
onMouseDownCapture?: (event: any) => void;
|
|
22
23
|
onHeaderMouseMove?: (event: any) => void;
|
|
@@ -63,6 +64,7 @@ export function FloatingPaneWrapper({
|
|
|
63
64
|
focused,
|
|
64
65
|
windowModeSelected = false,
|
|
65
66
|
showActions = false,
|
|
67
|
+
quickSettings,
|
|
66
68
|
onMouseDown,
|
|
67
69
|
onMouseDownCapture,
|
|
68
70
|
onHeaderMouseMove,
|
|
@@ -115,6 +117,7 @@ export function FloatingPaneWrapper({
|
|
|
115
117
|
windowModeSelected={windowModeSelected}
|
|
116
118
|
floating
|
|
117
119
|
showActions={showActions}
|
|
120
|
+
quickSettings={quickSettings}
|
|
118
121
|
onHeaderMouseMove={onHeaderMouseMove}
|
|
119
122
|
onHeaderMouseDown={onHeaderMouseDown}
|
|
120
123
|
onHeaderMouseDrag={onHeaderMouseDrag}
|
|
@@ -15,6 +15,7 @@ interface PaneHeaderProps {
|
|
|
15
15
|
windowModeSelected?: boolean;
|
|
16
16
|
floating?: boolean;
|
|
17
17
|
showActions?: boolean;
|
|
18
|
+
quickSettings?: PaneHeaderQuickSetting[];
|
|
18
19
|
onHeaderMouseMove?: (event: any) => void;
|
|
19
20
|
onHeaderMouseDown?: (event: any) => void;
|
|
20
21
|
onHeaderMouseDrag?: (event: any) => void;
|
|
@@ -24,6 +25,15 @@ interface PaneHeaderProps {
|
|
|
24
25
|
onCloseMouseDown?: (event: any) => void;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
export interface PaneHeaderQuickSetting {
|
|
29
|
+
key: string;
|
|
30
|
+
icon: "zap";
|
|
31
|
+
label: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
active: boolean;
|
|
34
|
+
onMouseDown?: (event: any) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
function truncateTitle(title: string, maxWidth: number): string {
|
|
28
38
|
return truncateToDisplayWidth(title, maxWidth);
|
|
29
39
|
}
|
|
@@ -43,9 +53,15 @@ function captureTerminalPointerDrag(renderer: unknown, renderable: unknown): voi
|
|
|
43
53
|
function DesktopPaneButton({
|
|
44
54
|
icon,
|
|
45
55
|
onMouseDown,
|
|
56
|
+
color = colors.textDim,
|
|
57
|
+
label,
|
|
58
|
+
pressed,
|
|
46
59
|
}: {
|
|
47
60
|
icon: ReactNode;
|
|
48
61
|
onMouseDown?: (event: any) => void;
|
|
62
|
+
color?: string;
|
|
63
|
+
label?: string;
|
|
64
|
+
pressed?: boolean;
|
|
49
65
|
}) {
|
|
50
66
|
return (
|
|
51
67
|
<Box
|
|
@@ -54,6 +70,9 @@ function DesktopPaneButton({
|
|
|
54
70
|
justifyContent="center"
|
|
55
71
|
onMouseDown={onMouseDown}
|
|
56
72
|
data-gloom-interactive={onMouseDown ? "true" : undefined}
|
|
73
|
+
aria-label={label}
|
|
74
|
+
aria-pressed={pressed}
|
|
75
|
+
title={label}
|
|
57
76
|
style={{
|
|
58
77
|
borderRadius: 4,
|
|
59
78
|
minWidth: 20,
|
|
@@ -69,7 +88,7 @@ function DesktopPaneButton({
|
|
|
69
88
|
justifyContent: "center",
|
|
70
89
|
width: 12,
|
|
71
90
|
height: 12,
|
|
72
|
-
color
|
|
91
|
+
color,
|
|
73
92
|
}}
|
|
74
93
|
>
|
|
75
94
|
{icon}
|
|
@@ -92,7 +111,7 @@ function TerminalPaneButton({
|
|
|
92
111
|
return (
|
|
93
112
|
<Box
|
|
94
113
|
height={1}
|
|
95
|
-
width={text
|
|
114
|
+
width={displayWidth(text)}
|
|
96
115
|
flexDirection="row"
|
|
97
116
|
data-gloom-role={role}
|
|
98
117
|
data-gloom-interactive={onMouseDown ? "true" : undefined}
|
|
@@ -110,6 +129,7 @@ export function PaneHeader({
|
|
|
110
129
|
windowModeSelected = false,
|
|
111
130
|
floating = false,
|
|
112
131
|
showActions = false,
|
|
132
|
+
quickSettings = [],
|
|
113
133
|
onHeaderMouseMove,
|
|
114
134
|
onHeaderMouseDown,
|
|
115
135
|
onHeaderMouseDrag,
|
|
@@ -125,6 +145,7 @@ export function PaneHeader({
|
|
|
125
145
|
const backgroundColor = floating ? floatingPaneTitleBg(visuallyFocused) : paneTitleBg(visuallyFocused);
|
|
126
146
|
const actionText = showActions ? PANE_HEADER_ACTION : " ";
|
|
127
147
|
const closeText = floating ? PANE_HEADER_CLOSE : "";
|
|
148
|
+
const terminalQuickSettingsWidth = quickSettings.reduce((total) => total + displayWidth(" ⚡ "), 0);
|
|
128
149
|
const textColor = paneTitleText(visuallyFocused, floating);
|
|
129
150
|
const handleTerminalHeaderMouseDown = useCallback((event: any) => {
|
|
130
151
|
captureTerminalPointerDrag(nativeRenderer, terminalHeaderRef.current);
|
|
@@ -158,7 +179,7 @@ export function PaneHeader({
|
|
|
158
179
|
<Text fg={visuallyFocused ? colors.borderFocused : colors.textMuted} selectable={false} data-gloom-role="pane-grip">
|
|
159
180
|
{PANE_HEADER_GRIP}
|
|
160
181
|
</Text>
|
|
161
|
-
<Box
|
|
182
|
+
<Box minWidth={0} flexShrink={1} overflow="hidden">
|
|
162
183
|
<Text
|
|
163
184
|
fg={textColor}
|
|
164
185
|
selectable={false}
|
|
@@ -173,6 +194,25 @@ export function PaneHeader({
|
|
|
173
194
|
{title}
|
|
174
195
|
</Text>
|
|
175
196
|
</Box>
|
|
197
|
+
{quickSettings.map((setting) => (
|
|
198
|
+
<Box key={setting.key} data-gloom-role="pane-quick-setting" data-setting-key={setting.key}>
|
|
199
|
+
<DesktopPaneButton
|
|
200
|
+
onMouseDown={setting.onMouseDown}
|
|
201
|
+
color={setting.active ? colors.warning : colors.textDim}
|
|
202
|
+
label={`${setting.label}: ${setting.active ? "on" : "off"}`}
|
|
203
|
+
pressed={setting.active}
|
|
204
|
+
icon={(
|
|
205
|
+
<svg viewBox="0 0 12 12" width="12" height="12" fill="none" aria-hidden="true">
|
|
206
|
+
<path
|
|
207
|
+
d="M7.1 1.2 2.7 6.5h3.1l-.7 4.3 4.4-5.5H6.4l.7-4.1Z"
|
|
208
|
+
fill="currentColor"
|
|
209
|
+
/>
|
|
210
|
+
</svg>
|
|
211
|
+
)}
|
|
212
|
+
/>
|
|
213
|
+
</Box>
|
|
214
|
+
))}
|
|
215
|
+
<Box flexGrow={1} minWidth={0} />
|
|
176
216
|
<Box data-gloom-role="pane-action">
|
|
177
217
|
{showActions ? (
|
|
178
218
|
<DesktopPaneButton
|
|
@@ -213,10 +253,10 @@ export function PaneHeader({
|
|
|
213
253
|
// Reserve 2 for corners, 1 for ─ after ┌, 1 for ─ before ┐
|
|
214
254
|
const borderColor = visuallyFocused ? colors.borderFocused : colors.border;
|
|
215
255
|
const innerWidth = Math.max(0, width - 4);
|
|
216
|
-
const contentWidth = PANE_HEADER_GRIP.length + closeText.length + actionText.length;
|
|
256
|
+
const contentWidth = PANE_HEADER_GRIP.length + terminalQuickSettingsWidth + closeText.length + actionText.length;
|
|
217
257
|
const titleWidth = Math.max(0, innerWidth - contentWidth);
|
|
218
258
|
const clippedTitle = truncateTitle(title, titleWidth);
|
|
219
|
-
const fillLen = Math.max(0, innerWidth - PANE_HEADER_GRIP.length - displayWidth(clippedTitle) - actionText.length - closeText.length);
|
|
259
|
+
const fillLen = Math.max(0, innerWidth - PANE_HEADER_GRIP.length - displayWidth(clippedTitle) - terminalQuickSettingsWidth - actionText.length - closeText.length);
|
|
220
260
|
const fill = "─".repeat(fillLen);
|
|
221
261
|
|
|
222
262
|
return (
|
|
@@ -233,6 +273,15 @@ export function PaneHeader({
|
|
|
233
273
|
>
|
|
234
274
|
<Text fg={borderColor} selectable={false}>{"┌─"}</Text>
|
|
235
275
|
<Text fg={textColor} selectable={false}>{`${PANE_HEADER_GRIP}${clippedTitle}`}</Text>
|
|
276
|
+
{quickSettings.map((setting) => (
|
|
277
|
+
<TerminalPaneButton
|
|
278
|
+
key={setting.key}
|
|
279
|
+
text=" ⚡ "
|
|
280
|
+
fg={setting.active ? colors.warning : colors.textDim}
|
|
281
|
+
role="pane-quick-setting"
|
|
282
|
+
onMouseDown={setting.onMouseDown}
|
|
283
|
+
/>
|
|
284
|
+
))}
|
|
236
285
|
<Text fg={borderColor} selectable={false}>{fill}</Text>
|
|
237
286
|
<TerminalPaneButton
|
|
238
287
|
text={actionText}
|
|
@@ -253,7 +302,7 @@ export function PaneHeader({
|
|
|
253
302
|
);
|
|
254
303
|
}
|
|
255
304
|
|
|
256
|
-
const titleWidth = Math.max(0, width - PANE_HEADER_GRIP.length - actionText.length - closeText.length);
|
|
305
|
+
const titleWidth = Math.max(0, width - PANE_HEADER_GRIP.length - terminalQuickSettingsWidth - actionText.length - closeText.length);
|
|
257
306
|
const clippedTitle = truncateTitle(title, titleWidth);
|
|
258
307
|
const padding = " ".repeat(Math.max(0, titleWidth - displayWidth(clippedTitle)));
|
|
259
308
|
|
|
@@ -272,6 +321,15 @@ export function PaneHeader({
|
|
|
272
321
|
<Text fg={textColor} selectable={false}>
|
|
273
322
|
{`${PANE_HEADER_GRIP}${clippedTitle}${padding}`}
|
|
274
323
|
</Text>
|
|
324
|
+
{quickSettings.map((setting) => (
|
|
325
|
+
<TerminalPaneButton
|
|
326
|
+
key={setting.key}
|
|
327
|
+
text=" ⚡ "
|
|
328
|
+
fg={setting.active ? colors.warning : colors.textDim}
|
|
329
|
+
role="pane-quick-setting"
|
|
330
|
+
onMouseDown={setting.onMouseDown}
|
|
331
|
+
/>
|
|
332
|
+
))}
|
|
275
333
|
<TerminalPaneButton
|
|
276
334
|
text={actionText}
|
|
277
335
|
fg={textColor}
|
|
@@ -2,7 +2,7 @@ import { Box, useUiCapabilities } from "../../../ui";
|
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import { paneBg } from "../../../theme/colors";
|
|
4
4
|
import { PaneBodyFrame, getPaneWindowAttributes } from "./frame";
|
|
5
|
-
import { PaneHeader } from "./header";
|
|
5
|
+
import { PaneHeader, type PaneHeaderQuickSetting } from "./header";
|
|
6
6
|
import { hasPaneFooterContent, PaneFooterBar, type CombinedPaneFooter } from "./footer";
|
|
7
7
|
import { resolvePaneBodyFrame, shouldReservePaneFooter } from "./sizing";
|
|
8
8
|
|
|
@@ -15,6 +15,7 @@ interface PaneWrapperProps {
|
|
|
15
15
|
height?: number | `${number}%` | "auto";
|
|
16
16
|
flexGrow?: number;
|
|
17
17
|
showActions?: boolean;
|
|
18
|
+
quickSettings?: PaneHeaderQuickSetting[];
|
|
18
19
|
onMouseDown?: (event: any) => void;
|
|
19
20
|
onMouseDownCapture?: (event: any) => void;
|
|
20
21
|
onHeaderMouseMove?: (event: any) => void;
|
|
@@ -36,6 +37,7 @@ export function PaneWrapper({
|
|
|
36
37
|
height,
|
|
37
38
|
flexGrow,
|
|
38
39
|
showActions = false,
|
|
40
|
+
quickSettings,
|
|
39
41
|
onMouseDown,
|
|
40
42
|
onMouseDownCapture,
|
|
41
43
|
onHeaderMouseMove,
|
|
@@ -87,6 +89,7 @@ export function PaneWrapper({
|
|
|
87
89
|
focused={focused}
|
|
88
90
|
windowModeSelected={windowModeSelected}
|
|
89
91
|
showActions={showActions}
|
|
92
|
+
quickSettings={quickSettings}
|
|
90
93
|
onHeaderMouseMove={onHeaderMouseMove}
|
|
91
94
|
onHeaderMouseDown={onHeaderMouseDown}
|
|
92
95
|
onHeaderMouseDrag={onHeaderMouseDrag}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from "../../../state/selectors-ui";
|
|
31
31
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
32
32
|
import { getPaneDisplayTitle } from "../pane/title";
|
|
33
|
+
import type { PaneHeaderQuickSetting } from "../pane/header";
|
|
33
34
|
import { getShortcutDisplayMode } from "../../../utils/shortcut-labels";
|
|
34
35
|
import {
|
|
35
36
|
actionMenuWidth,
|
|
@@ -423,6 +424,27 @@ export function Shell({
|
|
|
423
424
|
(pane: ResolvedPane): string => getPaneDisplayTitle(titleState, pane.instance, pane.def),
|
|
424
425
|
[titleState],
|
|
425
426
|
);
|
|
427
|
+
const handlePaneQuickSetting = useCallback((paneId: string, key: string, event: any) => {
|
|
428
|
+
event?.preventDefault?.();
|
|
429
|
+
event?.stopPropagation?.();
|
|
430
|
+
focusPane(paneId);
|
|
431
|
+
void pluginRegistry.togglePaneQuickSetting(paneId, key).catch((error) => {
|
|
432
|
+
pluginRegistry.notify({
|
|
433
|
+
body: error instanceof Error ? error.message : "Could not update pane setting.",
|
|
434
|
+
type: "error",
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
}, [focusPane, pluginRegistry]);
|
|
438
|
+
const getPaneQuickSettings = useCallback((paneId: string): PaneHeaderQuickSetting[] => (
|
|
439
|
+
(pluginRegistry.resolvePaneQuickSettings?.(paneId) ?? []).map((setting) => ({
|
|
440
|
+
key: setting.key,
|
|
441
|
+
icon: setting.icon,
|
|
442
|
+
label: setting.label,
|
|
443
|
+
description: setting.description,
|
|
444
|
+
active: setting.value,
|
|
445
|
+
onMouseDown: (event) => handlePaneQuickSetting(paneId, setting.key, event),
|
|
446
|
+
}))
|
|
447
|
+
), [config, handlePaneQuickSetting, pluginRegistry]);
|
|
426
448
|
|
|
427
449
|
const openPaneMenu = useCallback((paneId: string, rect: LayoutBounds, event?: { preventDefault?: () => void; stopPropagation?: () => void }) => {
|
|
428
450
|
const pane = paneMap.get(paneId);
|
|
@@ -561,6 +583,7 @@ export function Shell({
|
|
|
561
583
|
dragFloatingRect={dragFloatingRect}
|
|
562
584
|
focusedPaneId={focusedPaneId}
|
|
563
585
|
getPaneTitle={getPaneTitle}
|
|
586
|
+
getPaneQuickSettings={getPaneQuickSettings}
|
|
564
587
|
handleFloatingClose={handleFloatingClose}
|
|
565
588
|
handleFloatingCloseMouseDown={handleFloatingCloseMouseDown}
|
|
566
589
|
handleNativeDrag={handleNativeDrag}
|
|
@@ -12,6 +12,7 @@ import { pathKey } from "../../window-edit/mode";
|
|
|
12
12
|
import { FloatingPaneWrapper } from "../../floating-pane";
|
|
13
13
|
import { PaneContent } from "../../pane/content";
|
|
14
14
|
import { PaneWrapper } from "../../pane";
|
|
15
|
+
import type { PaneHeaderQuickSetting } from "../../pane/header";
|
|
15
16
|
import { hasPaneFooterContent, PaneFooterProvider } from "../../pane/footer";
|
|
16
17
|
import { resolvePaneBodyFrame, shouldReservePaneFooter } from "../../pane/sizing";
|
|
17
18
|
import type { DividerPreviewState } from "../native/window-state";
|
|
@@ -31,6 +32,7 @@ interface ShellPaneLayersProps {
|
|
|
31
32
|
dragFloatingRect: { paneId: string; rect: FloatingRect } | null;
|
|
32
33
|
focusedPaneId: string | null;
|
|
33
34
|
getPaneTitle: (pane: ResolvedPane) => string;
|
|
35
|
+
getPaneQuickSettings: (paneId: string) => PaneHeaderQuickSetting[];
|
|
34
36
|
handleFloatingClose: (paneId: string) => void;
|
|
35
37
|
handleFloatingCloseMouseDown: (paneId: string, event: any) => void;
|
|
36
38
|
handleNativeDrag: ShellMouseHandler;
|
|
@@ -64,6 +66,7 @@ export function ShellPaneLayers({
|
|
|
64
66
|
dragFloatingRect,
|
|
65
67
|
focusedPaneId,
|
|
66
68
|
getPaneTitle,
|
|
69
|
+
getPaneQuickSettings,
|
|
67
70
|
handleFloatingClose,
|
|
68
71
|
handleFloatingCloseMouseDown,
|
|
69
72
|
handleNativeDrag,
|
|
@@ -129,6 +132,7 @@ export function ShellPaneLayers({
|
|
|
129
132
|
width={rect.width}
|
|
130
133
|
height={rect.height}
|
|
131
134
|
showActions={showActions}
|
|
135
|
+
quickSettings={getPaneQuickSettings(leaf.instanceId)}
|
|
132
136
|
windowModeSelected={windowModeSelected}
|
|
133
137
|
footer={footer}
|
|
134
138
|
onMouseDownCapture={nativePaneChrome ? (event) => handleNativePaneMouseDown(leaf.instanceId, event) : undefined}
|
|
@@ -190,6 +194,7 @@ export function ShellPaneLayers({
|
|
|
190
194
|
focused={focused}
|
|
191
195
|
windowModeSelected={windowModeSelected}
|
|
192
196
|
showActions={showActions}
|
|
197
|
+
quickSettings={getPaneQuickSettings(pane.instance.instanceId)}
|
|
193
198
|
footer={footer}
|
|
194
199
|
onMouseDownCapture={nativePaneChrome ? (event) => handleNativePaneMouseDown(pane.instance.instanceId, event) : undefined}
|
|
195
200
|
onHeaderMouseMove={() => setHoveredPaneIfChanged(pane.instance.instanceId)}
|