gloomberb 0.16.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-client/data.ts +18 -3
- package/src/api-client/index.ts +3 -0
- package/src/api-client/members.ts +26 -0
- package/src/api-client/perps.ts +4 -0
- package/src/api-client/types.ts +7 -1
- package/src/app/global-shortcuts.ts +14 -6
- package/src/app/keybindings/actions.ts +1 -1
- package/src/app/runtime/update.ts +38 -2
- package/src/app.tsx +10 -6
- package/src/cli/pane-functions/function-help.ts +9 -2
- package/src/components/command-bar/commands/collection.ts +5 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -2
- package/src/components/command-bar/routes/root/plugin-install.ts +2 -2
- package/src/components/command-bar/routes/root/provider-search.ts +19 -5
- package/src/components/command-bar/routes/root/runtime.ts +14 -5
- package/src/components/command-bar/routes/ticker-search/results.ts +31 -5
- package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
- package/src/components/command-bar/routing/actions.ts +2 -0
- package/src/components/command-bar/selection-runtime.ts +38 -0
- package/src/components/command-bar/surface/index.tsx +7 -2
- package/src/components/index.ts +2 -2
- package/src/components/layout/floating-pane.tsx +6 -0
- package/src/components/layout/header.tsx +24 -7
- package/src/components/layout/missing-pane.tsx +2 -2
- package/src/components/layout/pane/header.tsx +63 -30
- package/src/components/layout/pane/index.tsx +6 -0
- package/src/components/layout/pane/title.ts +31 -7
- package/src/components/layout/shell/drag/index.ts +6 -5
- package/src/components/layout/shell/drag/runtime.ts +3 -0
- package/src/components/layout/shell/index.tsx +6 -0
- package/src/components/layout/shell/pane/layers.tsx +15 -7
- package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -3
- package/src/components/layout/shell/ticker-link-menu.ts +23 -9
- package/src/components/metric-treemap/heat-surface.tsx +718 -0
- package/src/components/metric-treemap/heat-tiles.ts +210 -0
- package/src/components/metric-treemap/index.tsx +2 -0
- package/src/components/metric-treemap/layout.ts +6 -0
- package/src/components/metric-treemap/scene.ts +432 -0
- package/src/components/pane-settings-dialog/field-dialogs.tsx +1 -0
- package/src/components/toggle-list.tsx +38 -12
- package/src/components/ui/country-flag-art.tsx +449 -0
- package/src/components/ui/country-flag.tsx +41 -0
- package/src/components/ui/data-table/types.ts +13 -0
- package/src/components/ui/icon.tsx +2 -0
- package/src/components/ui/index.ts +4 -0
- package/src/components/ui/multi-select/dialog.tsx +108 -22
- package/src/components/ui/range-track.tsx +46 -0
- package/src/components/ui/tabs.tsx +4 -0
- package/src/core/state/app/types.ts +10 -1
- package/src/i18n/es.ts +5 -3
- package/src/i18n/ja.ts +5 -3
- package/src/i18n/ko.ts +5 -3
- package/src/i18n/zh-cn.ts +5 -3
- package/src/i18n/zh-tw.ts +5 -3
- package/src/layout/ticker-navigation.ts +28 -2
- package/src/market-data/quotes/contributions.ts +6 -0
- package/src/market-data/quotes/freshness.ts +36 -0
- package/src/market-data/quotes/resolution.ts +22 -8
- package/src/market-data/quotes/time.ts +16 -0
- package/src/plugins/builtin/analytics/risk-client.ts +185 -59
- package/src/plugins/builtin/analytics/risk-headless.ts +55 -49
- package/src/plugins/builtin/analytics/risk-math.ts +33 -13
- package/src/plugins/builtin/analytics/risk-model.ts +250 -60
- package/src/plugins/builtin/analytics/risk-pane.tsx +61 -30
- package/src/plugins/builtin/analytics/risk-test-data.ts +182 -0
- package/src/plugins/builtin/buildout/cells.tsx +15 -12
- package/src/plugins/builtin/buildout/detail/company.tsx +3 -3
- package/src/plugins/builtin/buildout/detail/index.tsx +0 -1
- package/src/plugins/builtin/buildout/detail/intel.tsx +6 -6
- package/src/plugins/builtin/buildout/detail/site.tsx +4 -7
- package/src/plugins/builtin/buildout/detail/ui.tsx +0 -19
- package/src/plugins/builtin/catalysts/index.tsx +2 -2
- package/src/plugins/builtin/catalysts/pane.tsx +3 -1
- package/src/plugins/builtin/cds/index.tsx +1 -0
- package/src/plugins/builtin/debt-maturities/wall-bar.tsx +14 -41
- package/src/plugins/builtin/dividend-yield/client.ts +4 -2
- package/src/plugins/builtin/earnings/client.ts +2 -2
- package/src/plugins/builtin/earnings-calls/index.tsx +1 -0
- package/src/plugins/builtin/fx-matrix/direction.ts +81 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +71 -33
- package/src/plugins/builtin/fx-matrix/live-legs.ts +17 -0
- package/src/plugins/builtin/fx-matrix/pairs.ts +68 -14
- package/src/plugins/builtin/fx-matrix/settings.ts +62 -0
- package/src/plugins/builtin/jobs/index.tsx +1 -0
- package/src/plugins/builtin/jobs/share-bars.tsx +5 -28
- package/src/plugins/builtin/market-heatmap/data.ts +28 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +250 -79
- package/src/plugins/builtin/market-heatmap/live.ts +88 -0
- package/src/plugins/builtin/market-heatmap/portfolio.ts +27 -7
- package/src/plugins/builtin/market-movers/client.ts +3 -2
- package/src/plugins/builtin/market-movers/headless.ts +8 -1
- package/src/plugins/builtin/market-movers/screener.ts +18 -5
- package/src/plugins/builtin/members/client.ts +36 -0
- package/src/plugins/builtin/members/headless.ts +33 -0
- package/src/plugins/builtin/members/index.tsx +20 -0
- package/src/plugins/builtin/members/model.ts +60 -0
- package/src/plugins/builtin/members/pane.tsx +168 -0
- package/src/plugins/builtin/mna/index.tsx +2 -1
- package/src/plugins/builtin/mna/pane.tsx +5 -2
- package/src/plugins/builtin/options/index.tsx +2 -1
- package/src/plugins/builtin/pe-band/client.ts +29 -2
- package/src/plugins/builtin/pe-band/headless.ts +2 -2
- package/src/plugins/builtin/pe-band/model.ts +11 -7
- package/src/plugins/builtin/pe-band/pane.tsx +2 -1
- package/src/plugins/builtin/pe-band/report-dates.ts +78 -0
- package/src/plugins/builtin/perps/client.ts +17 -3
- package/src/plugins/builtin/perps/equity-identity.ts +16 -0
- package/src/plugins/builtin/perps/equity-row.tsx +6 -4
- package/src/plugins/builtin/plugin-marketplace/activation.ts +19 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -2
- package/src/plugins/builtin/plugin-marketplace/store.ts +2 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +43 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +48 -5
- package/src/plugins/builtin/scanner/hilo-bars.tsx +12 -70
- package/src/plugins/builtin/scanner/hilo-model.ts +0 -18
- package/src/plugins/builtin/sectors/move-bar.tsx +10 -32
- package/src/plugins/builtin/social-mentions/index.tsx +1 -1
- package/src/plugins/builtin/thirteenf/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +5 -60
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +7 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +20 -4
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/catalog-browser.ts +2 -0
- package/src/plugins/catalog-manifest.ts +1 -0
- package/src/plugins/catalog-ui.ts +3 -1
- package/src/plugins/catalog.ts +1 -1
- package/src/plugins/host-link.ts +33 -1
- package/src/plugins/installer.ts +48 -12
- package/src/plugins/loader.ts +18 -3
- package/src/plugins/manager-node.ts +15 -5
- package/src/plugins/registry/index.ts +1 -1
- package/src/plugins/registry/pane-settings.ts +25 -7
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/renderers/dom/data-table/row.tsx +50 -16
- package/src/renderers/dom/desktop/icons.tsx +1 -0
- package/src/renderers/dom/host/box.tsx +69 -26
- package/src/renderers/dom/host/tabs.tsx +37 -1
- package/src/renderers/dom/styles.css +40 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +11 -6
- package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +181 -87
- package/src/renderers/electrobun/bun/index.ts +73 -6
- package/src/renderers/electrobun/bun/window/remembered-frame.ts +122 -0
- package/src/renderers/electrobun/shared/protocol.ts +3 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +2 -1
- package/src/renderers/electrobun/view/main.tsx +7 -1
- package/src/renderers/electrobun/view/renderer-window-error.ts +18 -0
- package/src/renderers/electrobun/view/update-host.ts +45 -21
- package/src/sources/gloomberb-cloud/normalizers.ts +9 -2
- package/src/sources/provider-router/financials.ts +3 -1
- package/src/telemetry/benign-window-error.ts +7 -0
- package/src/telemetry/crash-reports-dom.ts +2 -0
- package/src/test-support/daily-nav.ts +25 -0
- package/src/theme/heat-colors.ts +174 -1
- package/src/tickers/instrument-kind.ts +34 -7
- package/src/tickers/search/asset-classes.ts +24 -9
- package/src/tickers/search/index.ts +44 -11
- package/src/tickers/search/ranking.ts +23 -3
- package/src/types/financials.ts +2 -0
- package/src/types/plugin.ts +18 -5
- package/src/ui/context-menu.tsx +1 -0
- package/src/ui/host.tsx +2 -0
- package/src/updater/index.ts +29 -2
|
@@ -122,12 +122,14 @@ export function useCommandBarRouteActions({
|
|
|
122
122
|
rawInput?: string,
|
|
123
123
|
explicitTargetId?: string | null,
|
|
124
124
|
selectedTicker?: TickerRecord,
|
|
125
|
+
directMembership?: boolean,
|
|
125
126
|
) => executeCollectionCommandAction({
|
|
126
127
|
activeCollectionId,
|
|
127
128
|
activeTickerSymbol,
|
|
128
129
|
buildWorkflowDeps: buildSharedWorkflowDeps,
|
|
129
130
|
closeAll,
|
|
130
131
|
commandId,
|
|
132
|
+
directMembership,
|
|
131
133
|
explicitTargetId,
|
|
132
134
|
getState: () => stateRef.current,
|
|
133
135
|
notify,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { useCallback, type MutableRefObject } from "react";
|
|
2
2
|
import type { AppState } from "../../state/app/context";
|
|
3
|
+
import type { TickerRecord } from "../../types/ticker";
|
|
4
|
+
import { t } from "../../i18n";
|
|
5
|
+
import { listingChoiceQuery } from "../../tickers/search";
|
|
3
6
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
4
7
|
import type { Command } from "./commands/registry";
|
|
5
8
|
import type {
|
|
@@ -30,6 +33,8 @@ type ExecuteCollectionCommandFn = (
|
|
|
30
33
|
commandId: CollectionCommandId,
|
|
31
34
|
rawInput?: string,
|
|
32
35
|
explicitTargetId?: string | null,
|
|
36
|
+
selectedTicker?: TickerRecord,
|
|
37
|
+
directMembership?: boolean,
|
|
33
38
|
) => Promise<void>;
|
|
34
39
|
|
|
35
40
|
interface UseCommandBarSelectionRuntimeOptions {
|
|
@@ -270,6 +275,38 @@ export function useCommandBarSelectionRuntime({
|
|
|
270
275
|
const selected = options?.item ?? typed ?? listState.results[listState.selectedIdx];
|
|
271
276
|
if (!selected || selected.disabled) return;
|
|
272
277
|
|
|
278
|
+
// Opened from an add row: a venue picked from the colon list joins that
|
|
279
|
+
// collection. Any other text or screen runs as usual.
|
|
280
|
+
const addListing = stateRef.current.commandBarLaunchRequest;
|
|
281
|
+
if (
|
|
282
|
+
!options?.secondary
|
|
283
|
+
&& addListing?.kind === "add-listing"
|
|
284
|
+
&& !currentRoute
|
|
285
|
+
&& listingChoiceQuery(rootQueryRef.current)
|
|
286
|
+
&& (selected.kind === "ticker" || selected.kind === "search")
|
|
287
|
+
&& selected.resolveTicker
|
|
288
|
+
) {
|
|
289
|
+
const launch = addListing;
|
|
290
|
+
void (async () => {
|
|
291
|
+
try {
|
|
292
|
+
const ticker = await selected.resolveTicker!();
|
|
293
|
+
await executeCollectionCommand(
|
|
294
|
+
launch.collectionKind === "watchlist" ? "add-watchlist" : "add-portfolio",
|
|
295
|
+
ticker.metadata.ticker,
|
|
296
|
+
launch.collectionId,
|
|
297
|
+
ticker,
|
|
298
|
+
true,
|
|
299
|
+
);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
pluginRegistry.notify({
|
|
302
|
+
type: "error",
|
|
303
|
+
body: error instanceof Error ? error.message : t("Could not add the selected listing."),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
})();
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
273
310
|
const run = () => {
|
|
274
311
|
if (options?.secondary && selected.secondaryAction) {
|
|
275
312
|
void selected.secondaryAction();
|
|
@@ -320,6 +357,7 @@ export function useCommandBarSelectionRuntime({
|
|
|
320
357
|
rootQueryRef,
|
|
321
358
|
runRootRow,
|
|
322
359
|
stateConfigLayout,
|
|
360
|
+
stateRef,
|
|
323
361
|
visibleListStateRef,
|
|
324
362
|
]);
|
|
325
363
|
|
|
@@ -20,6 +20,7 @@ import { useCommandBarRootRuntime } from "../routes/root/runtime";
|
|
|
20
20
|
import { createQuickLookTickerCandidates } from "../routes/ticker-search/results";
|
|
21
21
|
import { useCommandSearchReport } from "../routes/root/search-report";
|
|
22
22
|
import { useAppStateRef } from "../../../state/app/context";
|
|
23
|
+
import { listingChoiceQuery } from "../../../tickers/search";
|
|
23
24
|
import { parseRootShortcutIntent } from "../routes/root/shortcuts";
|
|
24
25
|
import { useRootPluginInstallItem } from "../routes/root/plugin-install";
|
|
25
26
|
import { useCommandBarThemePreview } from "../theme-preview";
|
|
@@ -230,6 +231,9 @@ export function CommandBar({
|
|
|
230
231
|
paneTemplates: getAvailablePaneShortcutTemplates(rootQuery),
|
|
231
232
|
activeTicker: activeTickerSymbol,
|
|
232
233
|
}), [activeTickerSymbol, availableCommands, getAvailablePaneShortcutTemplates, getAvailablePluginCommands, rootQuery]);
|
|
234
|
+
const listingChoice = !currentRoute && rootShortcutIntent.kind === "none"
|
|
235
|
+
? listingChoiceQuery(rootQuery)
|
|
236
|
+
: null;
|
|
233
237
|
|
|
234
238
|
// Runs the typed text again once a plugin installed from the bar is in, the
|
|
235
239
|
// way a key bound to it would. Not over a dialog opened since: the bar would
|
|
@@ -258,6 +262,7 @@ export function CommandBar({
|
|
|
258
262
|
// Only the root list asks on its own, and only for text the prefix parser
|
|
259
263
|
// could not claim — otherwise the user is mid-command, not mid-question.
|
|
260
264
|
const assistAutoAsk = !currentRoute
|
|
265
|
+
&& !listingChoice
|
|
261
266
|
&& planAccess.emailVerified
|
|
262
267
|
&& shouldAutoAskAssist({ query: rootQuery, hasShortcutIntent: rootShortcutIntent.kind !== "none" });
|
|
263
268
|
// The server keeps a question unless told not to, so the answer always
|
|
@@ -373,8 +378,8 @@ export function CommandBar({
|
|
|
373
378
|
providers: searchProviders,
|
|
374
379
|
query: rootQuery,
|
|
375
380
|
// A resolved prefix means the user is running a command, so free-text
|
|
376
|
-
// providers neither ask the network nor add rows.
|
|
377
|
-
enabled: !currentRoute && rootShortcutIntent.kind === "none",
|
|
381
|
+
// providers neither ask the network nor add rows. A colon is the venue list.
|
|
382
|
+
enabled: !currentRoute && rootShortcutIntent.kind === "none" && !listingChoice,
|
|
378
383
|
context: searchProviderContext,
|
|
379
384
|
onExecuted: closeAfterProviderResult,
|
|
380
385
|
});
|
package/src/components/index.ts
CHANGED
|
@@ -131,7 +131,7 @@ export {
|
|
|
131
131
|
IconButton, InlineQuickAddRow, KeyValueRow, ListView, loadingText, Menu, MenuPopover,
|
|
132
132
|
MessageComposer, MultiSelectDialogButton, MultiSelectDialogContent, Notice, NumberField,
|
|
133
133
|
NumberPromptDialog, openUrl, PageStackView, PaneLinkMenu, PaneStatusBody, Popover, Prose,
|
|
134
|
-
QueryBar, READING_WIDTH, RemoteImage, Section, SectionHeading, SegmentedControl, SelectButton, ShortcutHint,
|
|
134
|
+
QueryBar, RangeTrack, RatioBar, READING_WIDTH, RemoteImage, Section, SectionHeading, SegmentedControl, SelectButton, ShortcutHint,
|
|
135
135
|
Spinner, StatGrid, statGridColumns, statGridRows, Tabs, TextField, TextPromptDialog, unavailableText,
|
|
136
136
|
useFieldRing, usePaneLinkMenuEntry, useQueryBarSearch,
|
|
137
137
|
} from "./ui";
|
|
@@ -145,7 +145,7 @@ export type {
|
|
|
145
145
|
MenuProps, MultiSelectDialogButtonHandle, MultiSelectPopoverAnchorPoint, MultiSelectRowAction,
|
|
146
146
|
NoticeProps, NumberFieldProps, PaneStatusBodyProps, PopoverProps, ProseProps, QueryBarFilter,
|
|
147
147
|
QueryBarMultiFilter, QueryBarProps, QueryBarSearch, QueryBarSearchFocus, QueryBarSelectFilter, QueryBarTextFilter,
|
|
148
|
-
QueryBarToggleFilter, QueryBarView, SectionHeadingProps, SectionProps, SegmentedControlProps,
|
|
148
|
+
QueryBarToggleFilter, QueryBarView, RangeTrackProps, RatioBarProps, SectionHeadingProps, SectionProps, SegmentedControlProps,
|
|
149
149
|
SelectButtonOption, SelectButtonProps, SelectControl, StatGridProps, StatItem, TabsProps,
|
|
150
150
|
TextFieldProps, TextPromptDialogProps,
|
|
151
151
|
} from "./ui";
|
|
@@ -31,6 +31,8 @@ interface FloatingPaneWrapperProps {
|
|
|
31
31
|
onHeaderContextMenu?: (event: any) => void;
|
|
32
32
|
onActionMouseDown?: (event: any) => void;
|
|
33
33
|
onCloseMouseDown?: (event: any) => void;
|
|
34
|
+
onRestoreMouseDown?: (event: any) => void;
|
|
35
|
+
fullscreen?: boolean;
|
|
34
36
|
onResizeMouseDown?: (event: any) => void;
|
|
35
37
|
onResizeMouseDrag?: (event: any) => void;
|
|
36
38
|
onResizeMouseDragEnd?: (event: any) => void;
|
|
@@ -80,6 +82,8 @@ export function FloatingPaneWrapper({
|
|
|
80
82
|
onHeaderContextMenu,
|
|
81
83
|
onActionMouseDown,
|
|
82
84
|
onCloseMouseDown,
|
|
85
|
+
onRestoreMouseDown,
|
|
86
|
+
fullscreen = false,
|
|
83
87
|
onResizeMouseDown,
|
|
84
88
|
onResizeMouseDrag,
|
|
85
89
|
onResizeMouseDragEnd,
|
|
@@ -137,6 +141,8 @@ export function FloatingPaneWrapper({
|
|
|
137
141
|
onHeaderContextMenu={onHeaderContextMenu}
|
|
138
142
|
onActionMouseDown={onActionMouseDown}
|
|
139
143
|
onCloseMouseDown={onCloseMouseDown}
|
|
144
|
+
onRestoreMouseDown={onRestoreMouseDown}
|
|
145
|
+
fullscreen={fullscreen}
|
|
140
146
|
/>
|
|
141
147
|
|
|
142
148
|
<PaneBodyFrame layoutProps={bodyFrame.layoutProps} backgroundColor={bg}>
|
|
@@ -239,14 +239,15 @@ function HeaderCommandPrompt({
|
|
|
239
239
|
);
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
function UpdateStatus() {
|
|
242
|
+
function UpdateStatus({ onRestart }: { onRestart?: () => void }) {
|
|
243
243
|
const colors = useThemeColors();
|
|
244
244
|
const dispatch = useAppDispatch();
|
|
245
245
|
const updateAvailable = useAppSelector((state) => state.updateAvailable);
|
|
246
246
|
const updateProgress = useAppSelector((state) => state.updateProgress);
|
|
247
247
|
const updateCheckInProgress = useAppSelector((state) => state.updateCheckInProgress);
|
|
248
248
|
const updateNotice = useAppSelector((state) => state.updateNotice);
|
|
249
|
-
|
|
249
|
+
// One key: Restart for a downloaded update, retry for a failed one.
|
|
250
|
+
const updateKey = useActionShortcut("install-update");
|
|
250
251
|
|
|
251
252
|
useEffect(() => {
|
|
252
253
|
if (!updateNotice || updateAvailable || updateProgress || updateCheckInProgress) return;
|
|
@@ -278,15 +279,28 @@ function UpdateStatus() {
|
|
|
278
279
|
</Box>
|
|
279
280
|
);
|
|
280
281
|
}
|
|
282
|
+
if (updateProgress.phase === "ready") {
|
|
283
|
+
// A desktop update waits for the user: the app never relaunches by itself.
|
|
284
|
+
// The terminal binary is already swapped in and only needs a new launch.
|
|
285
|
+
if (updateProgress.canRestart && onRestart) {
|
|
286
|
+
return (
|
|
287
|
+
<Box flexDirection="row" gap={1} alignItems="center">
|
|
288
|
+
<Text fg={colors.headerText}>{t("Update ready")}</Text>
|
|
289
|
+
<Button compact label="Restart" shortcut={updateKey || undefined} onPress={onRestart} stopPropagation />
|
|
290
|
+
</Box>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
return <Text fg={colors.headerText}>{t("Update ready, restart to apply")}</Text>;
|
|
294
|
+
}
|
|
281
295
|
if (updateProgress.phase === "done") {
|
|
282
|
-
return <Text fg={colors.headerText}>{t(updateProgress.message ?? "Update
|
|
296
|
+
return <Text fg={colors.headerText}>{t(updateProgress.message ?? "Update ready, restart to apply")}</Text>;
|
|
283
297
|
}
|
|
284
298
|
if (updateProgress.phase === "error") {
|
|
285
|
-
const retry =
|
|
299
|
+
const retry = updateKey && canRetryUpdate({ updateAvailable, updateProgress, updateCheckInProgress });
|
|
286
300
|
return (
|
|
287
301
|
<Box flexDirection="row">
|
|
288
302
|
<Text fg={colors.headerText}>{tf("Update failed: {error}", { error: updateProgress.error ?? "Unknown error" })}</Text>
|
|
289
|
-
{retry ? <Text fg={blendHex(colors.headerText, colors.header, 0.62)}>{` ${
|
|
303
|
+
{retry ? <Text fg={blendHex(colors.headerText, colors.header, 0.62)}>{` ${updateKey} ${t("retry")}`}</Text> : null}
|
|
290
304
|
</Box>
|
|
291
305
|
);
|
|
292
306
|
}
|
|
@@ -378,8 +392,11 @@ function HeaderMarketSummary({ nativePaneChrome, width }: { nativePaneChrome: bo
|
|
|
378
392
|
|
|
379
393
|
export function Header({
|
|
380
394
|
onOpenHelp,
|
|
395
|
+
onRestartForUpdate,
|
|
381
396
|
}: {
|
|
382
397
|
onOpenHelp?: () => void;
|
|
398
|
+
/** Applies a downloaded desktop update and relaunches; the header offers Restart only when it is given. */
|
|
399
|
+
onRestartForUpdate?: () => void;
|
|
383
400
|
}) {
|
|
384
401
|
const colors = useThemeColors();
|
|
385
402
|
const rendererHost = useRendererHost();
|
|
@@ -444,7 +461,7 @@ export function Header({
|
|
|
444
461
|
<Box width={prompt.left} />
|
|
445
462
|
{commandPrompt}
|
|
446
463
|
<Box flexGrow={1} paddingLeft={2} paddingRight={2} minWidth={0}>
|
|
447
|
-
<UpdateStatus />
|
|
464
|
+
<UpdateStatus onRestart={onRestartForUpdate} />
|
|
448
465
|
</Box>
|
|
449
466
|
{onOpenHelp ? (
|
|
450
467
|
<Box flexShrink={0} data-gloom-role="header-help-action" style={{ marginRight: 8 }}>
|
|
@@ -470,7 +487,7 @@ export function Header({
|
|
|
470
487
|
<Box width={prompt.left} />
|
|
471
488
|
{commandPrompt}
|
|
472
489
|
<Box flexGrow={1} minWidth={0} paddingLeft={2}>
|
|
473
|
-
<UpdateStatus />
|
|
490
|
+
<UpdateStatus onRestart={onRestartForUpdate} />
|
|
474
491
|
</Box>
|
|
475
492
|
{marketSummary}
|
|
476
493
|
{showWindowControls ? <WindowControls /> : null}
|
|
@@ -3,7 +3,7 @@ import { Button } from "../ui/button";
|
|
|
3
3
|
import { confirmDialog } from "../ui/confirm-dialog";
|
|
4
4
|
import { EmptyState } from "../ui/status";
|
|
5
5
|
import { runsExternalPlugins } from "../../plugins/current-target";
|
|
6
|
-
import {
|
|
6
|
+
import { activateInstall } from "../../plugins/builtin/plugin-marketplace/activation";
|
|
7
7
|
import { installConsent } from "../../plugins/builtin/plugin-marketplace/model";
|
|
8
8
|
import { getMarketplaceHost, getPluginManager } from "../../plugins/builtin/plugin-marketplace/store";
|
|
9
9
|
import type { PaneDef, PaneProps } from "../../types/plugin";
|
|
@@ -115,7 +115,7 @@ function MissingPanePlaceholder({ paneType, width }: PaneProps) {
|
|
|
115
115
|
// Bring it into this session so the pane replaces the placeholder rather
|
|
116
116
|
// than waiting for a restart; a plugin that cannot load live says so.
|
|
117
117
|
const host = getMarketplaceHost();
|
|
118
|
-
const activated = host ? await
|
|
118
|
+
const activated = host ? await activateInstall(result, host, installer) : null;
|
|
119
119
|
if (activated && !activated.ok) setNotice({ text: `Installed but did not load: ${activated.error}`, error: true });
|
|
120
120
|
else if (!activated || activated.restart) setNotice({ text: "Installed. Restart to finish.", error: false });
|
|
121
121
|
} finally {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Box, Text, useActionShortcut, useNativeRenderer, useUiCapabilities } from "../../../ui";
|
|
1
|
+
import { Box, Text, useActionShortcut, useNativeRenderer, useRendererHost, useUiCapabilities } from "../../../ui";
|
|
2
2
|
import { useCallback, useRef, type ReactNode } from "react";
|
|
3
3
|
import { blendHex, colors, floatingPaneTitleBg, paneTitleBg, paneTitleText } from "../../../theme/colors";
|
|
4
4
|
import { WEB_CELL_WIDTH } from "../../../theme/font-scale";
|
|
5
5
|
import { displayWidth, truncateToDisplayWidth } from "../../../utils/format";
|
|
6
6
|
import { capturePointerDrag } from "../../../ui/pointer-drag";
|
|
7
7
|
import { Tabs } from "../../ui/tabs";
|
|
8
|
-
import { Icon, IconButton } from "../../ui/icon";
|
|
8
|
+
import { ICON_GLYPHS, Icon, IconButton } from "../../ui/icon";
|
|
9
9
|
import type { PaneHeaderTabsRegistration } from "./header-tabs";
|
|
10
10
|
import { nativePaneHeaderRows } from "./sizing";
|
|
11
11
|
|
|
@@ -15,6 +15,8 @@ const HEADER_TAB_TOP_GAP = 2;
|
|
|
15
15
|
const PANE_HEADER_GRIP = ":: ";
|
|
16
16
|
export const PANE_HEADER_ACTION = " ... ";
|
|
17
17
|
export const PANE_HEADER_CLOSE = " x ";
|
|
18
|
+
/** Same width as the close mark, so the header hit areas stay put. */
|
|
19
|
+
export const PANE_HEADER_RESTORE = " - ";
|
|
18
20
|
const PANE_HEADER_LOCK = " 🔒 ";
|
|
19
21
|
|
|
20
22
|
interface PaneHeaderProps {
|
|
@@ -45,6 +47,9 @@ interface PaneHeaderProps {
|
|
|
45
47
|
onHeaderContextMenu?: (event: any) => void;
|
|
46
48
|
onActionMouseDown?: (event: any) => void;
|
|
47
49
|
onCloseMouseDown?: (event: any) => void;
|
|
50
|
+
/** Pane fullscreen. The corner mark leaves fullscreen instead of closing. */
|
|
51
|
+
fullscreen?: boolean;
|
|
52
|
+
onRestoreMouseDown?: (event: any) => void;
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
/**
|
|
@@ -63,9 +68,14 @@ interface PaneHeaderTitleBar {
|
|
|
63
68
|
trailing?: ReactNode;
|
|
64
69
|
}
|
|
65
70
|
|
|
71
|
+
/** A quick setting's terminal button: its glyph with a space either side. */
|
|
72
|
+
function quickSettingText(setting: { icon: PaneHeaderQuickSetting["icon"] }): string {
|
|
73
|
+
return ` ${ICON_GLYPHS[setting.icon]} `;
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
export interface PaneHeaderQuickSetting {
|
|
67
77
|
key: string;
|
|
68
|
-
icon: "zap";
|
|
78
|
+
icon: "zap" | "sqrt";
|
|
69
79
|
label: string;
|
|
70
80
|
description?: string;
|
|
71
81
|
active: boolean;
|
|
@@ -118,18 +128,23 @@ export function PaneHeader({
|
|
|
118
128
|
onHeaderContextMenu,
|
|
119
129
|
onActionMouseDown,
|
|
120
130
|
onCloseMouseDown,
|
|
131
|
+
fullscreen = false,
|
|
132
|
+
onRestoreMouseDown,
|
|
121
133
|
}: PaneHeaderProps) {
|
|
122
|
-
const { nativePaneChrome } = useUiCapabilities();
|
|
134
|
+
const { nativePaneChrome, titleBarOverlay, nativeWindowChrome = titleBarOverlay } = useUiCapabilities();
|
|
135
|
+
const rendererHost = useRendererHost();
|
|
123
136
|
const nativeRenderer = useNativeRenderer();
|
|
124
137
|
const menuShortcut = useActionShortcut("pane-menu");
|
|
125
138
|
const closeShortcut = useActionShortcut("pane-close");
|
|
139
|
+
const fullscreenShortcut = useActionShortcut("pane-fullscreen");
|
|
126
140
|
const terminalHeaderRef = useRef<unknown>(null);
|
|
127
141
|
const visuallyFocused = focused || windowModeSelected;
|
|
128
142
|
const backgroundColor = floating ? floatingPaneTitleBg(visuallyFocused) : paneTitleBg(visuallyFocused);
|
|
129
143
|
const actionText = showActions ? PANE_HEADER_ACTION : " ";
|
|
130
|
-
const closeText = floating ? PANE_HEADER_CLOSE : "";
|
|
144
|
+
const closeText = fullscreen ? PANE_HEADER_RESTORE : floating ? PANE_HEADER_CLOSE : "";
|
|
145
|
+
const onCornerMouseDown = fullscreen ? onRestoreMouseDown : onCloseMouseDown;
|
|
131
146
|
const lockText = locked ? PANE_HEADER_LOCK : "";
|
|
132
|
-
const terminalQuickSettingsWidth = quickSettings.reduce((total) => total + displayWidth(
|
|
147
|
+
const terminalQuickSettingsWidth = quickSettings.reduce((total, setting) => total + displayWidth(quickSettingText(setting)), 0)
|
|
133
148
|
+ displayWidth(lockText);
|
|
134
149
|
const textColor = paneTitleText(visuallyFocused, floating);
|
|
135
150
|
const topInset = topRule ? 1 : 0;
|
|
@@ -137,6 +152,27 @@ export function PaneHeader({
|
|
|
137
152
|
capturePointerDrag(nativeRenderer, terminalHeaderRef.current);
|
|
138
153
|
onHeaderMouseDown?.(event);
|
|
139
154
|
}, [nativeRenderer, onHeaderMouseDown]);
|
|
155
|
+
// The grip, the title and the empty bar move the pane. A fullscreen pane
|
|
156
|
+
// cannot move, so there they move the desktop window instead.
|
|
157
|
+
const windowDrag = Boolean(titleBar) || (fullscreen && titleBarOverlay === true && nativeWindowChrome === true);
|
|
158
|
+
const beginPaneDrag = (event: { stopPropagation?: () => void }) => {
|
|
159
|
+
event.stopPropagation?.();
|
|
160
|
+
onHeaderMouseDown?.(event);
|
|
161
|
+
};
|
|
162
|
+
const beginWindowDrag = (event?: { stopPropagation?: () => void }) => {
|
|
163
|
+
event?.stopPropagation?.();
|
|
164
|
+
if (titleBar) {
|
|
165
|
+
onHeaderMouseDown?.(event);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
void rendererHost.startWindowDrag?.();
|
|
169
|
+
};
|
|
170
|
+
const paneDragHandlers = {
|
|
171
|
+
onMouseDown: beginPaneDrag,
|
|
172
|
+
onMouseDrag: onHeaderMouseDrag,
|
|
173
|
+
onMouseDragEnd: onHeaderMouseDragEnd,
|
|
174
|
+
};
|
|
175
|
+
const chromeHandlers = windowDrag ? { onMouseDown: beginWindowDrag } : paneDragHandlers;
|
|
140
176
|
|
|
141
177
|
if (nativePaneChrome) {
|
|
142
178
|
const ruleColor = visuallyFocused ? colors.borderFocused : colors.border;
|
|
@@ -151,10 +187,7 @@ export function PaneHeader({
|
|
|
151
187
|
data-floating={floating ? "true" : "false"}
|
|
152
188
|
data-focused={focused ? "true" : "false"}
|
|
153
189
|
data-window-mode-selected={windowModeSelected ? "true" : "false"}
|
|
154
|
-
onMouseDown={onHeaderMouseDown}
|
|
155
190
|
onMouseMove={onHeaderMouseMove}
|
|
156
|
-
onMouseDrag={onHeaderMouseDrag}
|
|
157
|
-
onMouseDragEnd={onHeaderMouseDragEnd}
|
|
158
191
|
onContextMenu={onHeaderContextMenu}
|
|
159
192
|
style={{
|
|
160
193
|
// One 1px rule at the bottom, painted inside the header, mirrored by
|
|
@@ -173,15 +206,14 @@ export function PaneHeader({
|
|
|
173
206
|
: `inset 0 -1px 0 ${ruleColor}`,
|
|
174
207
|
}}
|
|
175
208
|
>
|
|
176
|
-
{/* Part of the header, so it clicks and drags like the rest of it. */}
|
|
177
209
|
{!titleBar && (
|
|
178
|
-
<Box data-gloom-role="pane-grip" flexShrink={0} flexDirection="row" alignItems="center" style={{ alignSelf: "stretch", marginRight: 6 }}>
|
|
210
|
+
<Box data-gloom-role="pane-grip" flexShrink={0} flexDirection="row" alignItems="center" style={{ alignSelf: "stretch", marginRight: 6 }} {...paneDragHandlers}>
|
|
179
211
|
<Icon name="grip" size={12} color={visuallyFocused ? colors.borderFocused : colors.textMuted} />
|
|
180
212
|
</Box>
|
|
181
213
|
)}
|
|
182
214
|
{/* Full header height, so trimming the title to its capitals never lets
|
|
183
215
|
this clip cut descenders. */}
|
|
184
|
-
<Box minWidth={0} flexShrink={tabs ? 0 : 1} overflow="hidden" flexDirection="row" alignItems="center" style={{ alignSelf: "stretch", ...(tabs ? { maxWidth: "40%" } : {}) }}>
|
|
216
|
+
<Box data-gloom-role="pane-header-chrome" minWidth={0} flexShrink={tabs ? 0 : 1} overflow="hidden" flexDirection="row" alignItems="center" style={{ alignSelf: "stretch", ...(tabs ? { maxWidth: "40%" } : {}) }} {...chromeHandlers}>
|
|
185
217
|
<Text
|
|
186
218
|
fg={textColor}
|
|
187
219
|
selectable={false}
|
|
@@ -237,6 +269,7 @@ export function PaneHeader({
|
|
|
237
269
|
onAdd={tabs.onAdd}
|
|
238
270
|
addLabel={tabs.addLabel}
|
|
239
271
|
onReorder={tabs.onReorder}
|
|
272
|
+
onDrag={windowDrag && !tabs.onReorder ? beginWindowDrag : undefined}
|
|
240
273
|
closeMode={tabs.closeMode}
|
|
241
274
|
paneMenu={tabs.paneMenu}
|
|
242
275
|
/>
|
|
@@ -245,14 +278,14 @@ export function PaneHeader({
|
|
|
245
278
|
{quickSettings.map((setting) => (
|
|
246
279
|
<Box key={setting.key} data-gloom-role="pane-quick-setting" data-setting-key={setting.key}>
|
|
247
280
|
<IconButton
|
|
248
|
-
icon=
|
|
281
|
+
icon={setting.icon}
|
|
249
282
|
label={`${setting.label}: ${setting.active ? "on" : "off"}`}
|
|
250
283
|
pressed={setting.active}
|
|
251
284
|
onPress={setting.onMouseDown ? (event) => setting.onMouseDown?.(event) : undefined}
|
|
252
285
|
/>
|
|
253
286
|
</Box>
|
|
254
287
|
))}
|
|
255
|
-
<Box flexGrow={1} minWidth={0} />
|
|
288
|
+
<Box data-gloom-role="pane-header-chrome" flexGrow={1} minWidth={0} {...chromeHandlers} />
|
|
256
289
|
{locked && (
|
|
257
290
|
<Box data-gloom-role="pane-lock">
|
|
258
291
|
<IconButton icon="lock" label="Locked: the close shortcut leaves this pane open" color={colors.textMuted} />
|
|
@@ -269,13 +302,13 @@ export function PaneHeader({
|
|
|
269
302
|
/>
|
|
270
303
|
) : <Box width={2} />}
|
|
271
304
|
</Box>
|
|
272
|
-
{floating && !titleBar && (
|
|
273
|
-
<Box data-gloom-role="pane-close" marginLeft={1}>
|
|
305
|
+
{(fullscreen || (floating && !titleBar)) && (
|
|
306
|
+
<Box data-gloom-role={fullscreen ? "pane-restore" : "pane-close"} marginLeft={1}>
|
|
274
307
|
<IconButton
|
|
275
|
-
icon="close"
|
|
276
|
-
label="Close pane"
|
|
277
|
-
shortcut={closeShortcut || undefined}
|
|
278
|
-
onPress={
|
|
308
|
+
icon={fullscreen ? "minimize" : "close"}
|
|
309
|
+
label={fullscreen ? "Exit fullscreen" : "Close pane"}
|
|
310
|
+
shortcut={(fullscreen ? fullscreenShortcut : closeShortcut) || undefined}
|
|
311
|
+
onPress={onCornerMouseDown ? (event) => onCornerMouseDown(event) : undefined}
|
|
279
312
|
/>
|
|
280
313
|
</Box>
|
|
281
314
|
)}
|
|
@@ -284,9 +317,9 @@ export function PaneHeader({
|
|
|
284
317
|
);
|
|
285
318
|
}
|
|
286
319
|
|
|
287
|
-
if (visuallyFocused || floating) {
|
|
320
|
+
if (visuallyFocused || floating || fullscreen) {
|
|
288
321
|
// Build: ┌─:: Title ─────────── ... x─┐
|
|
289
|
-
//
|
|
322
|
+
// Fullscreen draws "-" in that corner. Reserve 2 for the frame.
|
|
290
323
|
const borderColor = visuallyFocused ? colors.borderFocused : colors.border;
|
|
291
324
|
const innerWidth = Math.max(0, width - 4);
|
|
292
325
|
const contentWidth = PANE_HEADER_GRIP.length + terminalQuickSettingsWidth + closeText.length + actionText.length;
|
|
@@ -312,7 +345,7 @@ export function PaneHeader({
|
|
|
312
345
|
{quickSettings.map((setting) => (
|
|
313
346
|
<TerminalPaneButton
|
|
314
347
|
key={setting.key}
|
|
315
|
-
text=
|
|
348
|
+
text={quickSettingText(setting)}
|
|
316
349
|
fg={setting.active ? colors.warning : colors.textDim}
|
|
317
350
|
role="pane-quick-setting"
|
|
318
351
|
onMouseDown={setting.onMouseDown}
|
|
@@ -328,12 +361,12 @@ export function PaneHeader({
|
|
|
328
361
|
role="pane-action"
|
|
329
362
|
onMouseDown={onActionMouseDown}
|
|
330
363
|
/>
|
|
331
|
-
{floating && (
|
|
364
|
+
{(fullscreen || floating) && (
|
|
332
365
|
<TerminalPaneButton
|
|
333
366
|
text={closeText}
|
|
334
367
|
fg={textColor}
|
|
335
|
-
role="pane-close"
|
|
336
|
-
onMouseDown={
|
|
368
|
+
role={fullscreen ? "pane-restore" : "pane-close"}
|
|
369
|
+
onMouseDown={onCornerMouseDown}
|
|
337
370
|
/>
|
|
338
371
|
)}
|
|
339
372
|
<Text fg={borderColor} selectable={false}>{"─┐"}</Text>
|
|
@@ -363,7 +396,7 @@ export function PaneHeader({
|
|
|
363
396
|
{quickSettings.map((setting) => (
|
|
364
397
|
<TerminalPaneButton
|
|
365
398
|
key={setting.key}
|
|
366
|
-
text=
|
|
399
|
+
text={quickSettingText(setting)}
|
|
367
400
|
fg={setting.active ? colors.warning : colors.textDim}
|
|
368
401
|
role="pane-quick-setting"
|
|
369
402
|
onMouseDown={setting.onMouseDown}
|
|
@@ -378,12 +411,12 @@ export function PaneHeader({
|
|
|
378
411
|
role="pane-action"
|
|
379
412
|
onMouseDown={onActionMouseDown}
|
|
380
413
|
/>
|
|
381
|
-
{floating && (
|
|
414
|
+
{(fullscreen || floating) && (
|
|
382
415
|
<TerminalPaneButton
|
|
383
416
|
text={closeText}
|
|
384
417
|
fg={textColor}
|
|
385
|
-
role="pane-close"
|
|
386
|
-
onMouseDown={
|
|
418
|
+
role={fullscreen ? "pane-restore" : "pane-close"}
|
|
419
|
+
onMouseDown={onCornerMouseDown}
|
|
387
420
|
/>
|
|
388
421
|
)}
|
|
389
422
|
</Box>
|
|
@@ -28,6 +28,8 @@ interface PaneWrapperProps {
|
|
|
28
28
|
onHeaderMouseDragEnd?: (event: any) => void;
|
|
29
29
|
onHeaderContextMenu?: (event: any) => void;
|
|
30
30
|
onActionMouseDown?: (event: any) => void;
|
|
31
|
+
onRestoreMouseDown?: (event: any) => void;
|
|
32
|
+
fullscreen?: boolean;
|
|
31
33
|
footer?: CombinedPaneFooter | null;
|
|
32
34
|
children: ReactNode;
|
|
33
35
|
}
|
|
@@ -52,6 +54,8 @@ export function PaneWrapper({
|
|
|
52
54
|
onHeaderMouseDragEnd,
|
|
53
55
|
onHeaderContextMenu,
|
|
54
56
|
onActionMouseDown,
|
|
57
|
+
onRestoreMouseDown,
|
|
58
|
+
fullscreen = false,
|
|
55
59
|
footer,
|
|
56
60
|
children,
|
|
57
61
|
}: PaneWrapperProps) {
|
|
@@ -107,6 +111,8 @@ export function PaneWrapper({
|
|
|
107
111
|
onHeaderMouseDragEnd={onHeaderMouseDragEnd}
|
|
108
112
|
onHeaderContextMenu={onHeaderContextMenu}
|
|
109
113
|
onActionMouseDown={onActionMouseDown}
|
|
114
|
+
fullscreen={fullscreen}
|
|
115
|
+
onRestoreMouseDown={onRestoreMouseDown}
|
|
110
116
|
/>
|
|
111
117
|
)}
|
|
112
118
|
<PaneBodyFrame layoutProps={bodyFrame.layoutProps} backgroundColor={bg}>
|
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
} from "../../../types/config";
|
|
8
8
|
import type { PaneDef } from "../../../types/plugin";
|
|
9
9
|
import { paneTitleMnemonic } from "../../../layout/pane-follow";
|
|
10
|
-
import { canFollowTickerSource } from "../../../layout/ticker-navigation";
|
|
10
|
+
import { canFollowTickerSource, isTickerLinkPeer } from "../../../layout/ticker-navigation";
|
|
11
11
|
|
|
12
12
|
/** Single-cell link glyph: emoji link icons render double-width in terminals. */
|
|
13
13
|
const LINK_GLYPH = "\u29c9";
|
|
14
14
|
|
|
15
|
-
/** The title without the link suffix
|
|
16
|
-
|
|
15
|
+
/** The title without the link suffix. */
|
|
16
|
+
function getBasePaneDisplayTitle(
|
|
17
17
|
state: Pick<AppState, "config" | "paneState">,
|
|
18
18
|
instance: PaneInstanceConfig,
|
|
19
19
|
paneDef: PaneDef,
|
|
@@ -79,9 +79,33 @@ export function getPaneDisplayTitle(
|
|
|
79
79
|
const source = findPaneInstance(state.config.layout, instance.binding.sourceInstanceId);
|
|
80
80
|
const sourceDef = source ? panes.get(source.paneId) : null;
|
|
81
81
|
if (!source || !sourceDef) return title;
|
|
82
|
-
//
|
|
83
|
-
// OMON following
|
|
84
|
-
if (!
|
|
85
|
-
const sourceTitle =
|
|
82
|
+
// Name the list, scanner, or single-ticker pane this one follows. A follow pointed at anything
|
|
83
|
+
// else (a desk's OMON following a comparison chart) reads as it always has.
|
|
84
|
+
if (!sourceDef.tickerSource && !isTickerLinkPeer(source, panes)) return title;
|
|
85
|
+
const sourceTitle = getTickerLinkSourceTitle(state, source, sourceDef, panes, "title");
|
|
86
86
|
return `${title} ${LINK_GLYPH} ${tf("Linked to {source}", { source: sourceTitle })}`;
|
|
87
87
|
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* How a follower names the pane it follows. A list or scanner goes by its title. A single-ticker
|
|
91
|
+
* pane shows the follower's own symbol, so a title names only its command ("OMON SPY ⧉ Linked to
|
|
92
|
+
* OVDV"), and a menu row keeps the symbol to tell two of them apart ("Link to OVDV SPY"). Ticker
|
|
93
|
+
* Research's title is only its symbol, which would read as a ticker, so it goes by its name
|
|
94
|
+
* ("Link to Ticker Research AAPL").
|
|
95
|
+
*/
|
|
96
|
+
export function getTickerLinkSourceTitle(
|
|
97
|
+
state: Pick<AppState, "config" | "paneState">,
|
|
98
|
+
source: PaneInstanceConfig,
|
|
99
|
+
sourceDef: PaneDef,
|
|
100
|
+
panes: ReadonlyMap<string, PaneDef>,
|
|
101
|
+
place: "menu" | "title",
|
|
102
|
+
): string {
|
|
103
|
+
const title = getBasePaneDisplayTitle(state, source, sourceDef, panes);
|
|
104
|
+
if (sourceDef.tickerSource) return title;
|
|
105
|
+
const research = source.paneId === TICKER_RESEARCH_PANE_ID;
|
|
106
|
+
const kind = research ? t("Ticker Research") : paneTitleMnemonic(title);
|
|
107
|
+
if (!kind) return title;
|
|
108
|
+
if (place === "title") return kind;
|
|
109
|
+
const symbol = research ? resolveTickerForPane(state as AppState, source.instanceId) : null;
|
|
110
|
+
return symbol ? `${kind} ${symbol}` : title;
|
|
111
|
+
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "../../../../layout/pane-manager";
|
|
11
11
|
import type { DesktopDockPreviewState } from "../../../../types/desktop-window";
|
|
12
12
|
import type { LayoutConfig } from "../../../../types/config";
|
|
13
|
-
import { PANE_HEADER_ACTION, PANE_HEADER_CLOSE } from "../../pane/header";
|
|
13
|
+
import { PANE_HEADER_ACTION, PANE_HEADER_CLOSE, PANE_HEADER_RESTORE } from "../../pane/header";
|
|
14
14
|
|
|
15
15
|
export interface HoverOverlay {
|
|
16
16
|
targetId: string;
|
|
@@ -355,18 +355,19 @@ export function resolveSnapGuide(x: number, y: number, guides: SnapGuide[]): Sna
|
|
|
355
355
|
|
|
356
356
|
export function resolveHeaderHitAreas(
|
|
357
357
|
width: number,
|
|
358
|
-
options: { floating: boolean; focused: boolean },
|
|
358
|
+
options: { floating: boolean; focused: boolean; fullscreen?: boolean },
|
|
359
359
|
): {
|
|
360
360
|
actionStart: number | null;
|
|
361
361
|
closeStart: number | null;
|
|
362
362
|
} {
|
|
363
363
|
// Focused panes and all floating panes render corner chrome around the header.
|
|
364
|
-
let rightEdge = options.focused || options.floating ? width - 2 : width;
|
|
364
|
+
let rightEdge = options.focused || options.floating || options.fullscreen ? width - 2 : width;
|
|
365
365
|
let closeStart: number | null = null;
|
|
366
366
|
let actionStart: number | null = null;
|
|
367
367
|
|
|
368
|
-
if (options.floating) {
|
|
369
|
-
|
|
368
|
+
if (options.floating || options.fullscreen) {
|
|
369
|
+
const corner = options.fullscreen ? PANE_HEADER_RESTORE : PANE_HEADER_CLOSE;
|
|
370
|
+
closeStart = Math.max(0, rightEdge - corner.length);
|
|
370
371
|
rightEdge = closeStart;
|
|
371
372
|
}
|
|
372
373
|
|
|
@@ -171,6 +171,7 @@ interface UseShellPointerRuntimeOptions {
|
|
|
171
171
|
focusPane: (paneId: string) => void;
|
|
172
172
|
focusedPaneId: string | null;
|
|
173
173
|
handleFloatingClose: (paneId: string) => void;
|
|
174
|
+
restoreFullscreen: () => void;
|
|
174
175
|
menuState: ActionMenuState | null;
|
|
175
176
|
nativePaneChrome: boolean;
|
|
176
177
|
openPaneMenu: (
|
|
@@ -206,6 +207,7 @@ export function useShellPointerRuntime({
|
|
|
206
207
|
focusPane,
|
|
207
208
|
focusedPaneId,
|
|
208
209
|
handleFloatingClose,
|
|
210
|
+
restoreFullscreen,
|
|
209
211
|
menuState,
|
|
210
212
|
nativePaneChrome,
|
|
211
213
|
openPaneMenu,
|
|
@@ -254,6 +256,7 @@ export function useShellPointerRuntime({
|
|
|
254
256
|
focusedPaneId,
|
|
255
257
|
handleActiveDrag,
|
|
256
258
|
handleFloatingClose,
|
|
259
|
+
restoreFullscreen,
|
|
257
260
|
menuState,
|
|
258
261
|
openPaneMenu,
|
|
259
262
|
paneMap,
|
|
@@ -754,6 +754,7 @@ export function Shell({
|
|
|
754
754
|
focusPane,
|
|
755
755
|
focusedPaneId,
|
|
756
756
|
handleFloatingClose,
|
|
757
|
+
restoreFullscreen: exitTransientFocusLayout,
|
|
757
758
|
menuState,
|
|
758
759
|
nativePaneChrome,
|
|
759
760
|
openPaneMenu,
|
|
@@ -834,6 +835,11 @@ export function Shell({
|
|
|
834
835
|
getPaneQuickSettings={getPaneQuickSettings}
|
|
835
836
|
handleFloatingClose={handleFloatingClose}
|
|
836
837
|
handleFloatingCloseMouseDown={handleFloatingCloseMouseDown}
|
|
838
|
+
handleRestoreFullscreen={(event) => {
|
|
839
|
+
event?.preventDefault?.();
|
|
840
|
+
event?.stopPropagation?.();
|
|
841
|
+
exitTransientFocusLayout();
|
|
842
|
+
}}
|
|
837
843
|
handleNativeDrag={handleNativeDrag}
|
|
838
844
|
handleNativePaneContextMenu={handleNativePaneContextMenu}
|
|
839
845
|
handleNativePaneMouseDown={handleNativePaneMouseDown}
|