gloomberb 0.7.0 → 0.7.2
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/app.tsx +149 -51
- package/src/capabilities/factories.ts +3 -0
- package/src/components/chart/chart-renderer.ts +24 -12
- package/src/components/chart/chart.test.ts +43 -0
- package/src/components/chart/comparison-chart-renderer.test.ts +26 -0
- package/src/components/chart/comparison-chart-renderer.ts +13 -6
- package/src/components/chart/comparison-stock-chart.tsx +15 -12
- package/src/components/chart/price-axis-labels.tsx +114 -0
- package/src/components/chart/static-chart-surface.tsx +2 -0
- package/src/components/chart/stock-chart.tsx +31 -20
- package/src/components/chart/time-axis-label.tsx +78 -1
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +0 -28
- package/src/components/command-bar/command-bar.test.tsx +114 -31
- package/src/components/command-bar/command-bar.tsx +587 -335
- package/src/components/command-bar/command-registry.ts +0 -10
- package/src/components/command-bar/helpers.ts +2 -5
- package/src/components/command-bar/list-model.ts +1 -2
- package/src/components/command-bar/theme-picker.tsx +254 -0
- package/src/components/command-bar/view-model.test.ts +4 -4
- package/src/components/command-bar/view-model.ts +1 -1
- package/src/components/command-bar/workflow-types.ts +1 -2
- package/src/components/layout/detached-pane-shell.tsx +29 -10
- package/src/components/layout/floating-pane.tsx +5 -2
- package/src/components/layout/header.tsx +2 -0
- package/src/components/layout/pane-content.test.tsx +64 -0
- package/src/components/layout/pane-content.tsx +2 -0
- package/src/components/layout/pane-sizing.ts +9 -2
- package/src/components/layout/pane-title.ts +4 -1
- package/src/components/layout/pane.tsx +11 -5
- package/src/components/layout/shell.test.tsx +198 -0
- package/src/components/layout/shell.tsx +33 -2
- package/src/components/layout/status-bar.tsx +2 -0
- package/src/components/speedometer-gauge.tsx +29 -25
- package/src/components/table-view-shared.tsx +85 -3
- package/src/components/ticker-list-table-view.tsx +16 -2
- package/src/components/ticker-list-table.test.tsx +82 -1
- package/src/components/ticker-list-table.tsx +22 -9
- package/src/components/ui/data-table.tsx +12 -9
- package/src/components/ui/frame.tsx +2 -0
- package/src/components/ui/table-layout.test.ts +27 -1
- package/src/components/ui/table-layout.ts +39 -1
- package/src/components/ui/tabs.tsx +34 -1
- package/src/components/ui/ui.test.tsx +68 -1
- package/src/core/state/app-state.ts +4 -14
- package/src/market-data/coordinator.test.ts +75 -0
- package/src/market-data/coordinator.ts +218 -48
- package/src/news/aggregator.test.ts +49 -1
- package/src/news/aggregator.ts +48 -0
- package/src/news/hooks.ts +6 -2
- package/src/plugins/builtin/ai/screener-pane.tsx +3 -2
- package/src/plugins/builtin/analytics/index.tsx +1 -18
- package/src/plugins/builtin/chat-controller.test.ts +83 -1
- package/src/plugins/builtin/chat-controller.ts +491 -243
- package/src/plugins/builtin/chat.test.tsx +425 -7
- package/src/plugins/builtin/chat.tsx +1109 -211
- package/src/plugins/builtin/cloud-tweets.tsx +1 -0
- package/src/plugins/builtin/fear-greed/index.tsx +148 -39
- package/src/plugins/builtin/holders/index.test.ts +13 -1
- package/src/plugins/builtin/holders/index.tsx +54 -14
- package/src/plugins/builtin/market-movers/index.tsx +12 -18
- package/src/plugins/builtin/market-movers/screener.test.ts +39 -1
- package/src/plugins/builtin/market-movers/screener.ts +70 -17
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +3 -2
- package/src/plugins/builtin/news-wire/industry-pane.tsx +4 -18
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +46 -4
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +3 -2
- package/src/plugins/builtin/notes.tsx +1 -0
- package/src/plugins/builtin/options.tsx +2 -16
- package/src/plugins/builtin/portfolio-list/pane.tsx +16 -39
- package/src/plugins/builtin/research/index.test.ts +94 -0
- package/src/plugins/builtin/research/index.tsx +159 -12
- package/src/plugins/builtin/ticker-detail/pane.tsx +28 -22
- package/src/plugins/builtin/ticker-detail.test.tsx +38 -0
- package/src/plugins/plugin-runtime.test.tsx +34 -0
- package/src/plugins/plugin-runtime.tsx +7 -2
- package/src/plugins/prediction-markets/controller.ts +0 -15
- package/src/plugins/prediction-markets/detail/pane.tsx +1 -0
- package/src/plugins/registry.ts +3 -3
- package/src/plugins/ticker-navigation.test.ts +66 -0
- package/src/plugins/ticker-navigation.ts +44 -0
- package/src/react/input.ts +1 -1
- package/src/renderers/electrobun/bun/index.ts +32 -11
- package/src/renderers/electrobun/bun/window-focus.ts +37 -0
- package/src/renderers/electrobun/shared/protocol.ts +7 -1
- package/src/renderers/electrobun/view/app-services.ts +88 -31
- package/src/renderers/electrobun/view/backend-rpc.ts +15 -0
- package/src/renderers/electrobun/view/data-table.tsx +111 -96
- package/src/renderers/electrobun/view/desktop-controls.tsx +8 -0
- package/src/renderers/electrobun/view/desktop-window-bridge.ts +12 -1
- package/src/renderers/electrobun/view/dialog-host.tsx +2 -0
- package/src/renderers/electrobun/view/input-host.tsx +1 -1
- package/src/renderers/electrobun/view/main.tsx +18 -1
- package/src/renderers/electrobun/view/styles.css +6 -8
- package/src/renderers/electrobun/view/toast-host.tsx +29 -3
- package/src/renderers/electrobun/view/ui-host.tsx +101 -5
- package/src/renderers/opentui/input-host.tsx +1 -1
- package/src/sources/gloomberb-cloud.test.ts +86 -1
- package/src/sources/gloomberb-cloud.ts +117 -21
- package/src/sources/provider-router.test.ts +122 -0
- package/src/sources/provider-router.ts +198 -11
- package/src/sources/yahoo-finance.test.ts +44 -0
- package/src/sources/yahoo-finance.ts +6 -0
- package/src/state/app-bootstrap.ts +20 -7
- package/src/state/app-context-selector.test.tsx +189 -4
- package/src/state/app-context.test.ts +20 -0
- package/src/state/app-context.tsx +48 -16
- package/src/state/use-inline-tickers.ts +13 -10
- package/src/theme/colors.test.ts +75 -0
- package/src/theme/colors.ts +64 -3
- package/src/theme/theme-context.tsx +26 -0
- package/src/types/capability-route-source.ts +1 -0
- package/src/types/data-provider.ts +15 -1
- package/src/types/desktop-window.ts +6 -0
- package/src/types/financials.ts +2 -0
- package/src/types/plugin.ts +1 -1
- package/src/ui/host.tsx +14 -2
- package/src/ui/index.tsx +1 -0
- package/src/utils/api-client.test.ts +32 -0
- package/src/utils/api-client.ts +119 -18
- package/src/utils/double-escape-close.test.ts +33 -0
- package/src/utils/double-escape-close.ts +40 -0
- package/src/utils/throttled-fetch.test.ts +22 -0
- package/src/utils/throttled-fetch.ts +3 -1
- package/src/utils/ticker-search.ts +53 -9
- package/src/version.ts +1 -1
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type RefObject,
|
|
15
15
|
} from "react";
|
|
16
16
|
import { TextAttributes, type ScrollBoxRenderable } from "../../../ui/host";
|
|
17
|
-
import {
|
|
17
|
+
import { useThemeColors } from "../../../theme/theme-context";
|
|
18
18
|
import { useAppDispatch, usePaneInstance } from "../../../state/app-context";
|
|
19
19
|
import { useRafCallback } from "../../../react/use-raf-callback";
|
|
20
20
|
import { measurePerf } from "../../../utils/perf-marks";
|
|
@@ -24,7 +24,11 @@ import type {
|
|
|
24
24
|
DataTableProps,
|
|
25
25
|
DataTableSectionHeader,
|
|
26
26
|
} from "../../../components/ui/data-table";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
buildTableGridTemplateColumns,
|
|
29
|
+
getTableWidth,
|
|
30
|
+
hasMeaningfulTableHorizontalOverflow,
|
|
31
|
+
} from "../../../components/ui/table-layout";
|
|
28
32
|
import { WEB_CELL_HEIGHT, WEB_CELL_WIDTH } from "./input-host";
|
|
29
33
|
import { useScrollbarActivity } from "./scrollbar-activity";
|
|
30
34
|
|
|
@@ -36,6 +40,14 @@ interface VirtualRow {
|
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
const TABLE_INLINE_PADDING_PX = 8;
|
|
43
|
+
const CSS_BG = "var(--gloom-bg)";
|
|
44
|
+
const CSS_PANEL = "var(--gloom-panel)";
|
|
45
|
+
const CSS_TEXT = "var(--gloom-text)";
|
|
46
|
+
const CSS_TEXT_DIM = "var(--gloom-text-dim)";
|
|
47
|
+
const CSS_TEXT_BRIGHT = "var(--gloom-text-bright)";
|
|
48
|
+
const CSS_SELECTED = "var(--gloom-selected)";
|
|
49
|
+
const CSS_SELECTED_TEXT = "var(--gloom-selected-text)";
|
|
50
|
+
const CSS_HOVER_BG = "var(--gloom-hover-bg)";
|
|
39
51
|
|
|
40
52
|
function hasAttribute(attributes: unknown, flag: number): boolean {
|
|
41
53
|
return typeof attributes === "number" && (attributes & flag) !== 0;
|
|
@@ -84,32 +96,6 @@ function clippedCellTextStyle(
|
|
|
84
96
|
};
|
|
85
97
|
}
|
|
86
98
|
|
|
87
|
-
function columnMinCh(column: DataTableColumn): number {
|
|
88
|
-
const width = Math.max(1, Math.floor(column.width));
|
|
89
|
-
if ((column.flexGrow ?? 0) > 0) {
|
|
90
|
-
return Math.max(8, Math.min(18, Math.floor(width * 0.35)));
|
|
91
|
-
}
|
|
92
|
-
if (column.align === "right") {
|
|
93
|
-
return Math.max(3, Math.min(width, 8));
|
|
94
|
-
}
|
|
95
|
-
if (width >= 16) {
|
|
96
|
-
return Math.max(8, Math.min(16, Math.floor(width * 0.35)));
|
|
97
|
-
}
|
|
98
|
-
return Math.max(1, Math.min(width, 8));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function columnFlexWeight(column: DataTableColumn): number {
|
|
102
|
-
const flexGrow = column.flexGrow ?? 0;
|
|
103
|
-
const baseWeight = Math.max(1, Math.floor(column.width));
|
|
104
|
-
return flexGrow > 0 ? baseWeight * Math.max(1, flexGrow) : baseWeight;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function buildGridTemplateColumns(columns: readonly DataTableColumn[]): string {
|
|
108
|
-
return columns
|
|
109
|
-
.map((column) => `minmax(${columnMinCh(column)}ch, ${columnFlexWeight(column)}fr)`)
|
|
110
|
-
.join(" ");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
99
|
function toCellY(pixels: number): number {
|
|
114
100
|
return Math.max(0, Math.round(pixels / WEB_CELL_HEIGHT));
|
|
115
101
|
}
|
|
@@ -265,6 +251,82 @@ function renderHeaderLabel<C extends DataTableColumn>(
|
|
|
265
251
|
};
|
|
266
252
|
}
|
|
267
253
|
|
|
254
|
+
function WebDataTableHeader<C extends DataTableColumn>({
|
|
255
|
+
columns,
|
|
256
|
+
focusPane,
|
|
257
|
+
gridTemplateColumns,
|
|
258
|
+
onHeaderClick,
|
|
259
|
+
sortColumnId,
|
|
260
|
+
sortDirection,
|
|
261
|
+
}: {
|
|
262
|
+
columns: C[];
|
|
263
|
+
focusPane: () => void;
|
|
264
|
+
gridTemplateColumns: string;
|
|
265
|
+
onHeaderClick: (columnId: string) => void;
|
|
266
|
+
sortColumnId: string | null;
|
|
267
|
+
sortDirection: "asc" | "desc";
|
|
268
|
+
}) {
|
|
269
|
+
useThemeColors();
|
|
270
|
+
return (
|
|
271
|
+
<div
|
|
272
|
+
data-gloom-role="data-table-header-row"
|
|
273
|
+
style={{
|
|
274
|
+
position: "sticky",
|
|
275
|
+
top: 0,
|
|
276
|
+
zIndex: 2,
|
|
277
|
+
display: "grid",
|
|
278
|
+
gridTemplateColumns,
|
|
279
|
+
columnGap: "1ch",
|
|
280
|
+
alignItems: "center",
|
|
281
|
+
width: "100%",
|
|
282
|
+
minWidth: 0,
|
|
283
|
+
height: WEB_CELL_HEIGHT,
|
|
284
|
+
paddingLeft: TABLE_INLINE_PADDING_PX,
|
|
285
|
+
paddingRight: TABLE_INLINE_PADDING_PX,
|
|
286
|
+
boxSizing: "border-box",
|
|
287
|
+
backgroundColor: CSS_PANEL,
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
{columns.map((column) => {
|
|
291
|
+
const { isSorted, text } = renderHeaderLabel(
|
|
292
|
+
column,
|
|
293
|
+
sortColumnId,
|
|
294
|
+
sortDirection,
|
|
295
|
+
);
|
|
296
|
+
return (
|
|
297
|
+
<div
|
|
298
|
+
key={column.id}
|
|
299
|
+
data-gloom-role="data-table-header-cell"
|
|
300
|
+
data-gloom-interactive="true"
|
|
301
|
+
style={{
|
|
302
|
+
minWidth: 0,
|
|
303
|
+
height: WEB_CELL_HEIGHT,
|
|
304
|
+
overflow: "hidden",
|
|
305
|
+
backgroundColor: column.headerBackgroundColor ?? CSS_PANEL,
|
|
306
|
+
}}
|
|
307
|
+
onMouseDown={(event) => {
|
|
308
|
+
focusPane();
|
|
309
|
+
event.preventDefault();
|
|
310
|
+
onHeaderClick(column.id);
|
|
311
|
+
}}
|
|
312
|
+
>
|
|
313
|
+
<span
|
|
314
|
+
title={text}
|
|
315
|
+
style={clippedCellTextStyle(
|
|
316
|
+
column,
|
|
317
|
+
isSorted ? CSS_TEXT : column.headerColor ?? CSS_TEXT_DIM,
|
|
318
|
+
TextAttributes.BOLD,
|
|
319
|
+
)}
|
|
320
|
+
>
|
|
321
|
+
{text}
|
|
322
|
+
</span>
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
|
+
})}
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
268
330
|
function WebDataTableRowInner<
|
|
269
331
|
T,
|
|
270
332
|
C extends DataTableColumn,
|
|
@@ -303,6 +365,7 @@ function WebDataTableRowInner<
|
|
|
303
365
|
selected: boolean;
|
|
304
366
|
setHoveredIdx: (index: number | null) => void;
|
|
305
367
|
}) {
|
|
368
|
+
useThemeColors();
|
|
306
369
|
const sectionHeader: DataTableSectionHeader | null =
|
|
307
370
|
renderSectionHeader?.(item, index) ?? null;
|
|
308
371
|
const baseRowStyle: CSSProperties = {
|
|
@@ -330,7 +393,7 @@ function WebDataTableRowInner<
|
|
|
330
393
|
data-gloom-role="data-table-section-header"
|
|
331
394
|
style={{
|
|
332
395
|
...baseRowStyle,
|
|
333
|
-
backgroundColor: sectionHeader.backgroundColor ??
|
|
396
|
+
backgroundColor: sectionHeader.backgroundColor ?? CSS_BG,
|
|
334
397
|
}}
|
|
335
398
|
onMouseDown={(event) => {
|
|
336
399
|
focusPane();
|
|
@@ -341,7 +404,7 @@ function WebDataTableRowInner<
|
|
|
341
404
|
title={sectionHeader.text}
|
|
342
405
|
style={{
|
|
343
406
|
...cellTextStyle(
|
|
344
|
-
sectionHeader.color ??
|
|
407
|
+
sectionHeader.color ?? CSS_TEXT_BRIGHT,
|
|
345
408
|
sectionHeader.attributes ?? TextAttributes.BOLD,
|
|
346
409
|
),
|
|
347
410
|
gridColumn: "1 / -1",
|
|
@@ -358,10 +421,10 @@ function WebDataTableRowInner<
|
|
|
358
421
|
const rowState = { selected, hovered };
|
|
359
422
|
const rowBackgroundColor = getRowBackgroundColor?.(item, index, rowState);
|
|
360
423
|
const rowBg = selected
|
|
361
|
-
?
|
|
424
|
+
? CSS_SELECTED
|
|
362
425
|
: hovered
|
|
363
|
-
?
|
|
364
|
-
: rowBackgroundColor ??
|
|
426
|
+
? CSS_HOVER_BG
|
|
427
|
+
: rowBackgroundColor ?? CSS_BG;
|
|
365
428
|
|
|
366
429
|
return (
|
|
367
430
|
<div
|
|
@@ -438,7 +501,7 @@ function WebDataTableRowInner<
|
|
|
438
501
|
title={cell.text}
|
|
439
502
|
style={clippedCellTextStyle(
|
|
440
503
|
column,
|
|
441
|
-
cell.color ?? (selected ?
|
|
504
|
+
cell.color ?? (selected ? CSS_SELECTED_TEXT : CSS_TEXT),
|
|
442
505
|
cell.attributes ?? TextAttributes.NONE,
|
|
443
506
|
)}
|
|
444
507
|
>
|
|
@@ -494,7 +557,7 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
494
557
|
const [scrollbarActive, markScrollbarActive] = useScrollbarActivity();
|
|
495
558
|
const tableWidth = useMemo(() => getTableWidth(columns), [columns]);
|
|
496
559
|
const gridTemplateColumns = useMemo(
|
|
497
|
-
() =>
|
|
560
|
+
() => buildTableGridTemplateColumns(columns),
|
|
498
561
|
[columns],
|
|
499
562
|
);
|
|
500
563
|
const selectRow = useCallback((item: T, index: number) => {
|
|
@@ -624,7 +687,7 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
624
687
|
width: "100%",
|
|
625
688
|
minWidth: 0,
|
|
626
689
|
minHeight: 0,
|
|
627
|
-
backgroundColor:
|
|
690
|
+
backgroundColor: CSS_BG,
|
|
628
691
|
overflow: "hidden",
|
|
629
692
|
};
|
|
630
693
|
const bodyScrollerStyle: CSSProperties = {
|
|
@@ -634,7 +697,7 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
634
697
|
minHeight: 0,
|
|
635
698
|
overflowX: horizontalScrollEnabled ? "auto" : "hidden",
|
|
636
699
|
overflowY: "auto",
|
|
637
|
-
backgroundColor:
|
|
700
|
+
backgroundColor: CSS_BG,
|
|
638
701
|
};
|
|
639
702
|
|
|
640
703
|
return (
|
|
@@ -673,77 +736,29 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
673
736
|
minHeight: WEB_CELL_HEIGHT,
|
|
674
737
|
}}
|
|
675
738
|
>
|
|
676
|
-
<
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
columnGap: "1ch",
|
|
685
|
-
alignItems: "center",
|
|
686
|
-
width: "100%",
|
|
687
|
-
minWidth: 0,
|
|
688
|
-
height: WEB_CELL_HEIGHT,
|
|
689
|
-
paddingLeft: TABLE_INLINE_PADDING_PX,
|
|
690
|
-
paddingRight: TABLE_INLINE_PADDING_PX,
|
|
691
|
-
boxSizing: "border-box",
|
|
692
|
-
backgroundColor: colors.panel,
|
|
693
|
-
}}
|
|
694
|
-
>
|
|
695
|
-
{columns.map((column) => {
|
|
696
|
-
const { isSorted, text } = renderHeaderLabel(
|
|
697
|
-
column,
|
|
698
|
-
sortColumnId,
|
|
699
|
-
sortDirection,
|
|
700
|
-
);
|
|
701
|
-
return (
|
|
702
|
-
<div
|
|
703
|
-
key={column.id}
|
|
704
|
-
data-gloom-role="data-table-header-cell"
|
|
705
|
-
data-gloom-interactive="true"
|
|
706
|
-
style={{
|
|
707
|
-
minWidth: 0,
|
|
708
|
-
height: WEB_CELL_HEIGHT,
|
|
709
|
-
overflow: "hidden",
|
|
710
|
-
backgroundColor: column.headerBackgroundColor ?? colors.panel,
|
|
711
|
-
}}
|
|
712
|
-
onMouseDown={(event) => {
|
|
713
|
-
focusPane();
|
|
714
|
-
event.preventDefault();
|
|
715
|
-
onHeaderClick(column.id);
|
|
716
|
-
}}
|
|
717
|
-
>
|
|
718
|
-
<span
|
|
719
|
-
title={text}
|
|
720
|
-
style={clippedCellTextStyle(
|
|
721
|
-
column,
|
|
722
|
-
isSorted ? colors.text : column.headerColor ?? colors.textDim,
|
|
723
|
-
TextAttributes.BOLD,
|
|
724
|
-
)}
|
|
725
|
-
>
|
|
726
|
-
{text}
|
|
727
|
-
</span>
|
|
728
|
-
</div>
|
|
729
|
-
);
|
|
730
|
-
})}
|
|
731
|
-
</div>
|
|
739
|
+
<WebDataTableHeader
|
|
740
|
+
columns={columns}
|
|
741
|
+
focusPane={focusPane}
|
|
742
|
+
gridTemplateColumns={gridTemplateColumns}
|
|
743
|
+
onHeaderClick={onHeaderClick}
|
|
744
|
+
sortColumnId={sortColumnId}
|
|
745
|
+
sortDirection={sortDirection}
|
|
746
|
+
/>
|
|
732
747
|
{items.length === 0 ? (
|
|
733
748
|
emptyContent ?? (
|
|
734
749
|
<div
|
|
735
750
|
style={{
|
|
736
751
|
width: "100%",
|
|
737
752
|
padding: `${WEB_CELL_HEIGHT}px ${WEB_CELL_WIDTH}px`,
|
|
738
|
-
color:
|
|
753
|
+
color: CSS_TEXT_DIM,
|
|
739
754
|
lineHeight: "var(--cell-h)",
|
|
740
755
|
}}
|
|
741
756
|
>
|
|
742
|
-
<div style={cellTextStyle(
|
|
757
|
+
<div style={cellTextStyle(CSS_TEXT_BRIGHT, TextAttributes.BOLD)}>
|
|
743
758
|
{emptyStateTitle}
|
|
744
759
|
</div>
|
|
745
760
|
{emptyStateHint ? (
|
|
746
|
-
<div style={cellTextStyle(
|
|
761
|
+
<div style={cellTextStyle(CSS_TEXT_DIM, TextAttributes.NONE)}>
|
|
747
762
|
{emptyStateHint}
|
|
748
763
|
</div>
|
|
749
764
|
) : null}
|
|
@@ -4,6 +4,7 @@ import { Box, Input, ScrollBox, Text, Textarea, editableTextContextMenuItems, us
|
|
|
4
4
|
import { TextAttributes, type InputRenderable, type ScrollBoxRenderable } from "../../../ui";
|
|
5
5
|
import { useShortcut } from "../../../react/input";
|
|
6
6
|
import { blendHex, colors, hoverBg } from "../../../theme/colors";
|
|
7
|
+
import { useThemeColors } from "../../../theme/theme-context";
|
|
7
8
|
import { isDetailBackNavigationKey } from "../../../utils/back-navigation";
|
|
8
9
|
import type { ButtonProps } from "../../../components/ui/button";
|
|
9
10
|
import type { TextFieldProps } from "../../../components/ui/fields";
|
|
@@ -97,6 +98,7 @@ export function WebButton({
|
|
|
97
98
|
shortcut,
|
|
98
99
|
width,
|
|
99
100
|
}: ButtonProps) {
|
|
101
|
+
useThemeColors();
|
|
100
102
|
const palette = buttonPalette({ variant, active, disabled });
|
|
101
103
|
|
|
102
104
|
return (
|
|
@@ -156,6 +158,7 @@ export function WebTextField({
|
|
|
156
158
|
placeholderColor = colors.textDim,
|
|
157
159
|
onMouseDown,
|
|
158
160
|
}: TextFieldProps) {
|
|
161
|
+
useThemeColors();
|
|
159
162
|
const localInputRef = useRef<InputRenderable>(null);
|
|
160
163
|
const resolvedInputRef = inputRef ?? localInputRef;
|
|
161
164
|
const renderer = useRendererHost();
|
|
@@ -245,6 +248,7 @@ export function WebMessageComposer({
|
|
|
245
248
|
keyBindings,
|
|
246
249
|
wrapText = false,
|
|
247
250
|
}: MessageComposerProps) {
|
|
251
|
+
useThemeColors();
|
|
248
252
|
const borderColor = focused
|
|
249
253
|
? blendHex(colors.borderFocused, colors.textBright, 0.24)
|
|
250
254
|
: colors.border;
|
|
@@ -361,6 +365,7 @@ export function WebListView({
|
|
|
361
365
|
scrollable = false,
|
|
362
366
|
autoScrollToIndex = true,
|
|
363
367
|
}: ListViewProps) {
|
|
368
|
+
useThemeColors();
|
|
364
369
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
|
365
370
|
const scrollRef = useRef<ScrollBoxRenderable>(null);
|
|
366
371
|
const baseBg = bgColor ?? colors.bg;
|
|
@@ -489,6 +494,7 @@ export function WebSegmentedControl({
|
|
|
489
494
|
value,
|
|
490
495
|
onChange,
|
|
491
496
|
}: SegmentedControlProps) {
|
|
497
|
+
useThemeColors();
|
|
492
498
|
return (
|
|
493
499
|
<Box
|
|
494
500
|
flexDirection="row"
|
|
@@ -538,6 +544,7 @@ export function WebDialogFrame({
|
|
|
538
544
|
footer,
|
|
539
545
|
showTitleDivider = true,
|
|
540
546
|
}: DialogFrameProps) {
|
|
547
|
+
useThemeColors();
|
|
541
548
|
return (
|
|
542
549
|
<Box flexDirection="column" style={{ padding: 14 }}>
|
|
543
550
|
<Box
|
|
@@ -583,6 +590,7 @@ export function WebPageStackView({
|
|
|
583
590
|
backLabel = "Back",
|
|
584
591
|
backHint,
|
|
585
592
|
}: PageStackViewProps) {
|
|
593
|
+
useThemeColors();
|
|
586
594
|
useShortcut((event) => {
|
|
587
595
|
if (!focused || !detailOpen || !isDetailBackNavigationKey(event)) return;
|
|
588
596
|
event.stopPropagation?.();
|
|
@@ -2,9 +2,10 @@ import type { PaneRuntimeState } from "../../../core/state/app-state";
|
|
|
2
2
|
import type {
|
|
3
3
|
DesktopDockPreviewState,
|
|
4
4
|
DesktopSharedStateSnapshot,
|
|
5
|
+
DesktopThemePreviewState,
|
|
5
6
|
DesktopWindowBridge,
|
|
6
7
|
} from "../../../types/desktop-window";
|
|
7
|
-
import { backendRequest, getElectrobunBackendInitSnapshot, onDesktopDockPreview, onDesktopState } from "./backend-rpc";
|
|
8
|
+
import { backendRequest, getElectrobunBackendInitSnapshot, onDesktopDockPreview, onDesktopState, onDesktopThemePreview } from "./backend-rpc";
|
|
8
9
|
import { detachedSnapshotKey, prepareDetachedSnapshot } from "./desktop-window-snapshot";
|
|
9
10
|
|
|
10
11
|
export function createDesktopWindowBridge(kind: "main" | "detached", paneId?: string): DesktopWindowBridge {
|
|
@@ -21,6 +22,11 @@ export function createDesktopWindowBridge(kind: "main" | "detached", paneId?: st
|
|
|
21
22
|
await backendRequest("desktop.syncMainState", { snapshot });
|
|
22
23
|
}
|
|
23
24
|
: undefined,
|
|
25
|
+
syncThemePreview: kind === "main"
|
|
26
|
+
? async (preview: DesktopThemePreviewState) => {
|
|
27
|
+
await backendRequest("desktop.setThemePreview", { preview });
|
|
28
|
+
}
|
|
29
|
+
: undefined,
|
|
24
30
|
replaceDetachedPaneState: kind === "detached"
|
|
25
31
|
? async (targetPaneId: string, paneState: PaneRuntimeState) => {
|
|
26
32
|
await backendRequest("desktop.replaceDetachedPaneState", { paneId: targetPaneId, paneState });
|
|
@@ -55,5 +61,10 @@ export function createDesktopWindowBridge(kind: "main" | "detached", paneId?: st
|
|
|
55
61
|
listener(message.preview);
|
|
56
62
|
});
|
|
57
63
|
},
|
|
64
|
+
subscribeThemePreview(listener: (preview: DesktopThemePreviewState) => void) {
|
|
65
|
+
return onDesktopThemePreview((message) => {
|
|
66
|
+
listener(message.preview);
|
|
67
|
+
});
|
|
68
|
+
},
|
|
58
69
|
};
|
|
59
70
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useCallback, useMemo, useState, type ReactNode } from "react";
|
|
4
4
|
import { DialogHostProvider, type DialogApi } from "../../../ui/dialog";
|
|
5
5
|
import { blendHex, colors } from "../../../theme/colors";
|
|
6
|
+
import { useThemeColors } from "../../../theme/theme-context";
|
|
6
7
|
|
|
7
8
|
interface DialogState {
|
|
8
9
|
id: string;
|
|
@@ -13,6 +14,7 @@ interface DialogState {
|
|
|
13
14
|
let nextDialogId = 1;
|
|
14
15
|
|
|
15
16
|
export function WebDialogHostProvider({ children }: { children: ReactNode }) {
|
|
17
|
+
useThemeColors();
|
|
16
18
|
const [dialogState, setDialogState] = useState<DialogState | null>(null);
|
|
17
19
|
const dialogBorder = blendHex(colors.border, colors.borderFocused, 0.18);
|
|
18
20
|
const dialogBg = blendHex(colors.panel, colors.bg, 0.12);
|
|
@@ -99,7 +99,7 @@ export function WebInputHostProvider({ children }: { children: ReactNode }) {
|
|
|
99
99
|
const shortcutsRef = useRef<ShortcutEntry[]>([]);
|
|
100
100
|
|
|
101
101
|
const dispatchShortcut = (shortcutEvent: KeyEventLike) => {
|
|
102
|
-
for (const phase of ["normal", "after"] as const) {
|
|
102
|
+
for (const phase of ["before", "normal", "after"] as const) {
|
|
103
103
|
if (phase === "after" && (shortcutEvent.defaultPrevented || shortcutEvent.propagationStopped)) break;
|
|
104
104
|
for (const entry of shortcutsRef.current) {
|
|
105
105
|
if (entry.phase !== phase || !entry.enabledRef.current) continue;
|
|
@@ -6,7 +6,7 @@ import { UiHostProvider } from "../../../ui/host";
|
|
|
6
6
|
import { debugLog } from "../../../utils/debug-log";
|
|
7
7
|
import { measurePerfAsync } from "../../../utils/perf-marks";
|
|
8
8
|
import { startMainThreadMonitor } from "../../../utils/main-thread-monitor";
|
|
9
|
-
import { initElectrobunBackend } from "./backend-rpc";
|
|
9
|
+
import { backendRequest, initElectrobunBackend } from "./backend-rpc";
|
|
10
10
|
import { installElectrobunAiHost } from "./ai-host";
|
|
11
11
|
import { installElectrobunBrokerRemoteClient } from "./broker-remote-client";
|
|
12
12
|
import { installElectrobunConfigStoreHost } from "./config-host";
|
|
@@ -45,9 +45,24 @@ debugLog.mirrorToConsole({
|
|
|
45
45
|
});
|
|
46
46
|
const stopMainThreadMonitor = startMainThreadMonitor("electrobun.web", { mirrorToConsole: true });
|
|
47
47
|
|
|
48
|
+
rootElement.tabIndex = -1;
|
|
48
49
|
root.render(<div className="gloom-loading">Starting Gloomberb...</div>);
|
|
49
50
|
bootLog.warn("diagnostic console mirror enabled", { sources: ELECTROBUN_WEB_CONSOLE_LOG_SOURCES });
|
|
50
51
|
|
|
52
|
+
function focusWebSurface(): void {
|
|
53
|
+
window.focus();
|
|
54
|
+
rootElement.focus({ preventScroll: true });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function requestStartupFocus(): void {
|
|
58
|
+
focusWebSurface();
|
|
59
|
+
requestAnimationFrame(() => {
|
|
60
|
+
void backendRequest("host.focusWindow")
|
|
61
|
+
.catch(() => null)
|
|
62
|
+
.then(() => focusWebSurface());
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
51
66
|
async function boot() {
|
|
52
67
|
bootLog.info("boot started");
|
|
53
68
|
const backendInitPromise = initElectrobunBackend();
|
|
@@ -78,6 +93,7 @@ async function boot() {
|
|
|
78
93
|
desktopWindowBridge={desktopWindowBridge}
|
|
79
94
|
desktopApplicationMenuBridge={desktopApplicationMenuBridge}
|
|
80
95
|
desktopSnapshot={desktopSnapshot}
|
|
96
|
+
desktopThemePreview={init.desktopThemePreview}
|
|
81
97
|
/>
|
|
82
98
|
</WebDialogHostProvider>
|
|
83
99
|
</WebToastHostProvider>
|
|
@@ -85,6 +101,7 @@ async function boot() {
|
|
|
85
101
|
</UiHostProvider>,
|
|
86
102
|
);
|
|
87
103
|
});
|
|
104
|
+
requestStartupFocus();
|
|
88
105
|
bootLog.info("root render scheduled", {
|
|
89
106
|
layoutPanes: config.layout.instances.length,
|
|
90
107
|
floatingPanes: config.layout.floating.length,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
@keyframes gloom-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
2
2
|
html, body, #root { width: 100%; height: 100%; min-height: 100%; overflow: hidden; background: #000; }
|
|
3
|
+
#root:focus { outline: none; }
|
|
3
4
|
body {
|
|
4
5
|
--cell-w: 8px;
|
|
5
6
|
--cell-h: 18px;
|
|
@@ -119,6 +120,8 @@ body.gloom-dragging {
|
|
|
119
120
|
pointer-events: auto;
|
|
120
121
|
}
|
|
121
122
|
[data-gloom-role="status-bar"] {
|
|
123
|
+
flex: 0 0 var(--cell-h);
|
|
124
|
+
height: var(--cell-h);
|
|
122
125
|
min-height: var(--cell-h);
|
|
123
126
|
max-height: var(--cell-h);
|
|
124
127
|
align-items: center;
|
|
@@ -129,6 +132,8 @@ body.gloom-dragging {
|
|
|
129
132
|
line-height: var(--cell-h);
|
|
130
133
|
}
|
|
131
134
|
[data-gloom-role="pane-footer"] {
|
|
135
|
+
flex: 0 0 var(--cell-h);
|
|
136
|
+
height: var(--cell-h);
|
|
132
137
|
min-height: var(--cell-h);
|
|
133
138
|
max-height: var(--cell-h);
|
|
134
139
|
align-items: center;
|
|
@@ -294,18 +299,11 @@ body.gloom-dragging {
|
|
|
294
299
|
min-width: 260px;
|
|
295
300
|
max-width: 420px;
|
|
296
301
|
padding: 10px 12px;
|
|
297
|
-
border: 1px solid
|
|
298
|
-
background: #151b20;
|
|
299
|
-
color: #d8dde3;
|
|
302
|
+
border: 1px solid;
|
|
300
303
|
box-shadow: 0 12px 24px rgba(0,0,0,.28);
|
|
301
304
|
}
|
|
302
|
-
.gloom-toast-success { border-color: #54c99f; }
|
|
303
|
-
.gloom-toast-error { border-color: #ff6b5f; }
|
|
304
305
|
.gloom-toast-action {
|
|
305
306
|
margin-top: 8px;
|
|
306
|
-
color: #101417;
|
|
307
|
-
background: #d8dde3;
|
|
308
|
-
border: 0;
|
|
309
307
|
padding: 4px 8px;
|
|
310
308
|
border-radius: 4px;
|
|
311
309
|
}
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/** @jsxImportSource react */
|
|
3
|
-
import { useCallback, useMemo, useState, type ReactNode } from "react";
|
|
3
|
+
import { useCallback, useMemo, useState, type CSSProperties, type ReactNode } from "react";
|
|
4
|
+
import { colors } from "../../../theme/colors";
|
|
4
5
|
import { ToastHostProvider, type ToastHost, type ToastOptions } from "../../../ui/toast";
|
|
5
6
|
|
|
7
|
+
type WebToastType = "info" | "success" | "error";
|
|
8
|
+
|
|
6
9
|
interface ToastEntry {
|
|
7
10
|
id: number;
|
|
8
11
|
body: string;
|
|
9
|
-
type:
|
|
12
|
+
type: WebToastType;
|
|
10
13
|
action?: ToastOptions["action"];
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
let nextToastId = 1;
|
|
14
17
|
|
|
18
|
+
function webToastBorderColor(type: WebToastType): string {
|
|
19
|
+
if (type === "success") return colors.positive;
|
|
20
|
+
if (type === "error") return colors.negative;
|
|
21
|
+
return colors.borderFocused;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getToastStyle(type: WebToastType): CSSProperties {
|
|
25
|
+
return {
|
|
26
|
+
background: colors.panel,
|
|
27
|
+
borderColor: webToastBorderColor(type),
|
|
28
|
+
color: colors.text,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getToastActionStyle(): CSSProperties {
|
|
33
|
+
return {
|
|
34
|
+
background: colors.selected,
|
|
35
|
+
border: `1px solid ${colors.borderFocused}`,
|
|
36
|
+
color: colors.selectedText,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
15
40
|
export function WebToastHostProvider({ children }: { children: ReactNode }) {
|
|
16
41
|
const [toasts, setToasts] = useState<ToastEntry[]>([]);
|
|
17
42
|
|
|
@@ -33,11 +58,12 @@ export function WebToastHostProvider({ children }: { children: ReactNode }) {
|
|
|
33
58
|
return (
|
|
34
59
|
<div className="gloom-toast-viewport">
|
|
35
60
|
{toasts.map((toast) => (
|
|
36
|
-
<div key={toast.id} className=
|
|
61
|
+
<div key={toast.id} className="gloom-toast" style={getToastStyle(toast.type)}>
|
|
37
62
|
<div>{toast.body}</div>
|
|
38
63
|
{toast.action && (
|
|
39
64
|
<button
|
|
40
65
|
className="gloom-toast-action"
|
|
66
|
+
style={getToastActionStyle()}
|
|
41
67
|
onClick={() => {
|
|
42
68
|
toast.action?.onClick();
|
|
43
69
|
dismiss(toast.id);
|