gloomberb 0.9.4 → 0.9.6
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 +49 -8
- package/package.json +4 -3
- package/patches/@opentui%2Fcore@0.3.2.patch +231 -29
- package/patches/react-reconciler@0.33.0.patch +10 -0
- package/src/app/global-shortcuts.test.tsx +20 -1
- package/src/app/global-shortcuts.ts +2 -0
- package/src/app/pane-runtime/index.tsx +11 -4
- package/src/app/pane-runtime/pane-settings-runtime.tsx +1 -1
- package/src/app.tsx +41 -17
- package/src/cli/commands/remote.ts +206 -0
- package/src/cli/entry.ts +8 -1
- package/src/cli/index.ts +2 -0
- package/src/cli/native-smoke.ts +5 -0
- package/src/components/chart/core/keyboard.ts +10 -1
- package/src/components/chart/native/bitmap-support.test.ts +27 -0
- package/src/components/chart/native/bitmap-support.ts +55 -0
- package/src/components/chart/native/kitty/index.test.ts +47 -0
- package/src/components/chart/native/surface/visibility.ts +8 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +4 -0
- package/src/components/chart/static/chart/bitmap.ts +16 -9
- package/src/components/chart/static/chart/surface.test.tsx +59 -4
- package/src/components/chart/static/chart/surface.tsx +4 -0
- package/src/components/chart/stock-chart/index.test.ts +7 -0
- package/src/components/command-bar/list/view.tsx +48 -1
- package/src/components/command-bar/multi-select-picker.tsx +7 -0
- package/src/components/command-bar/routing/navigation-state.ts +10 -1
- package/src/components/command-bar/surface/index.test.tsx +14 -0
- package/src/components/command-bar/theme-picker.tsx +91 -79
- package/src/components/command-bar/workflow/field-row.tsx +30 -5
- package/src/components/data-table/view.tsx +0 -12
- package/src/components/index.ts +7 -0
- package/src/components/layout/floating-pane.tsx +3 -3
- package/src/components/layout/pane/footer/index.tsx +23 -0
- package/src/components/layout/pane/header.tsx +5 -0
- package/src/components/layout/pane/index.tsx +3 -3
- package/src/components/layout/shell/action-menu-overlay.tsx +1 -1
- package/src/components/layout/shell/cursor-occlusion.ts +9 -1
- package/src/components/layout/shell/index.test.tsx +2 -0
- package/src/components/layout/shell/index.tsx +16 -4
- package/src/components/layout/shell/pane/actions.ts +21 -7
- package/src/components/layout/shell/pane/layers.tsx +2 -2
- package/src/components/layout/status-bar.tsx +5 -5
- package/src/components/metric-treemap/index.tsx +317 -0
- package/src/components/metric-treemap/layout.test.ts +77 -0
- package/src/components/metric-treemap/layout.ts +476 -0
- package/src/components/pane-settings-dialog/desktop.tsx +1 -1
- package/src/components/price-performance.tsx +1 -1
- package/src/components/price-sparkline/view.tsx +15 -14
- package/src/components/speedometer-gauge/terminal.tsx +18 -15
- package/src/components/table-view-shared.tsx +0 -10
- package/src/components/ticker/list-table-view.tsx +1 -7
- package/src/components/ticker/list-table.test.tsx +0 -4
- package/src/components/toggle-list.tsx +10 -0
- package/src/components/ui/button.tsx +12 -0
- package/src/components/ui/data-table/index.tsx +51 -0
- package/src/components/ui/data-table/opentui/index.tsx +4 -10
- package/src/components/ui/data-table/types.ts +6 -4
- package/src/components/ui/fields.tsx +18 -0
- package/src/components/ui/list-view.tsx +84 -4
- package/src/components/ui/message-composer.tsx +22 -0
- package/src/components/ui/tabs.tsx +46 -2
- package/src/components/ui/ui.test.tsx +0 -9
- package/src/core/state/app/layout-reducer.ts +7 -1
- package/src/core/state/app/layout.ts +6 -0
- package/src/core/state/app/state.ts +23 -1
- package/src/core/state/app/types.ts +4 -2
- package/src/plugins/builtin/account-management/form-components.tsx +2 -2
- package/src/plugins/builtin/alerts/pane.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +5 -18
- package/src/plugins/builtin/chat/chat.test.tsx +1 -7
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -1
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +1 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +2 -2
- package/src/plugins/builtin/chat/sidebar.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +2 -8
- package/src/plugins/builtin/cloud/auth-actions.tsx +2 -2
- package/src/plugins/builtin/cloud/plugin.tsx +4 -13
- package/src/plugins/builtin/correlation/matrix/symbol-cell.tsx +1 -1
- package/src/plugins/builtin/debug/index.tsx +3 -4
- package/src/plugins/builtin/help/components.tsx +1 -1
- package/src/plugins/builtin/holders/treemap.tsx +28 -297
- package/src/plugins/builtin/holders/types.ts +0 -34
- package/src/plugins/builtin/market-heatmap/data.test.ts +121 -0
- package/src/plugins/builtin/market-heatmap/data.ts +296 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +320 -0
- package/src/plugins/builtin/options/table.ts +5 -5
- package/src/plugins/builtin/plugin-groups.ts +2 -0
- package/src/plugins/builtin/portfolio-list/grid.tsx +197 -0
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +41 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +78 -22
- package/src/plugins/ibkr/gateway/service/index.test.ts +22 -0
- package/src/plugins/ibkr/gateway/service/index.ts +6 -9
- package/src/plugins/ibkr/gateway/service/native-loader.ts +12 -0
- package/src/plugins/ibkr/trade/ticket-panel.tsx +1 -1
- package/src/remote/app-host.tsx +77 -0
- package/src/remote/client.ts +34 -0
- package/src/remote/command-bar.ts +98 -0
- package/src/remote/controller-utils.ts +111 -0
- package/src/remote/controller.test.ts +365 -0
- package/src/remote/controller.ts +434 -0
- package/src/remote/endpoint.ts +41 -0
- package/src/remote/json-patch.test.ts +31 -0
- package/src/remote/json-patch.ts +89 -0
- package/src/remote/layout-helpers.ts +62 -0
- package/src/remote/resources.ts +186 -0
- package/src/remote/revision.ts +18 -0
- package/src/remote/schema.ts +143 -0
- package/src/remote/semantic-helpers.ts +161 -0
- package/src/remote/semantic-tree.tsx +112 -0
- package/src/remote/server.test.ts +50 -0
- package/src/remote/server.ts +121 -0
- package/src/remote/types.ts +119 -0
- package/src/renderers/electrobun/bun/index.ts +55 -1
- package/src/renderers/electrobun/shared/protocol.ts +11 -1
- package/src/renderers/electrobun/view/backend-rpc.ts +28 -1
- package/src/renderers/electrobun/view/build-assets.ts +1 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +0 -8
- package/src/renderers/electrobun/view/data-table/row.tsx +2 -12
- package/src/renderers/electrobun/view/host/box.tsx +11 -1
- package/src/renderers/electrobun/view/host/mouse.test.ts +37 -0
- package/src/renderers/electrobun/view/host/mouse.ts +2 -0
- package/src/renderers/electrobun/view/host/style.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +5 -1
- package/src/renderers/electrobun/view/native-stubs/ibkr-native-loader.ts +3 -0
- package/src/renderers/electrobun/view/styles.css +7 -0
- package/src/renderers/opentui/chart-surface.test.tsx +101 -0
- package/src/renderers/opentui/chart-surface.tsx +15 -20
- package/src/renderers/opentui/native-smoke.ts +1 -0
- package/src/renderers/opentui/start.tsx +32 -0
- package/src/sources/yahoo-finance/http.ts +31 -4
- package/src/state/app/context/index.test.tsx +47 -0
- package/src/state/app/context/index.tsx +48 -18
- package/src/ui/host.tsx +1 -0
- package/src/ui/index.tsx +183 -7
- package/src/version.ts +1 -1
- package/src/plugins/builtin/holders/treemap-layout.ts +0 -231
|
@@ -2,6 +2,7 @@ import { Box, Span, Text, TextAttributes, useUiCapabilities } from "../../../../
|
|
|
2
2
|
import { useRef } from "react";
|
|
3
3
|
import { colors, blendHex } from "../../../../theme/colors";
|
|
4
4
|
import { getShortcutHintWidth, ShortcutHint } from "../../../ui/shortcut-hint";
|
|
5
|
+
import { useRemoteUiNode } from "../../../../remote/semantic-tree";
|
|
5
6
|
import {
|
|
6
7
|
EMPTY_FOOTER,
|
|
7
8
|
hasPaneFooterContent,
|
|
@@ -50,6 +51,15 @@ function stopMouseEvent(event?: { stopPropagation?: () => void; preventDefault?:
|
|
|
50
51
|
|
|
51
52
|
function SegmentView({ segment }: { segment: PaneFooterSegment }) {
|
|
52
53
|
const interactive = !!segment.onPress && !segment.disabled;
|
|
54
|
+
useRemoteUiNode(interactive ? {
|
|
55
|
+
role: "pane-footer-segment",
|
|
56
|
+
label: segment.parts.map((part) => part.text).join(" "),
|
|
57
|
+
disabled: segment.disabled,
|
|
58
|
+
actions: {
|
|
59
|
+
press: () => segment.onPress?.(),
|
|
60
|
+
},
|
|
61
|
+
metadata: { id: segment.id },
|
|
62
|
+
} : null);
|
|
53
63
|
const attributes = segment.parts.some((part) => part.bold) || interactive ? TextAttributes.BOLD : 0;
|
|
54
64
|
const triggerMouseDownRef = useRef(false);
|
|
55
65
|
const startSegmentPress = (event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
|
|
@@ -93,6 +103,19 @@ function totalHintsWidth(hints: PaneHint[]): number {
|
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
function HintView({ hint, prefixSpace }: { hint: PaneHint; prefixSpace: boolean }) {
|
|
106
|
+
useRemoteUiNode({
|
|
107
|
+
role: "pane-hint",
|
|
108
|
+
label: `${hint.key}${hint.label}`,
|
|
109
|
+
disabled: hint.disabled,
|
|
110
|
+
actions: {
|
|
111
|
+
press: hint.onPress ? () => hint.onPress?.() : undefined,
|
|
112
|
+
},
|
|
113
|
+
metadata: {
|
|
114
|
+
id: hint.id,
|
|
115
|
+
key: hint.key,
|
|
116
|
+
label: hint.label,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
96
119
|
return (
|
|
97
120
|
<ShortcutHint
|
|
98
121
|
hotkey={hint.key}
|
|
@@ -14,6 +14,7 @@ interface PaneHeaderProps {
|
|
|
14
14
|
windowModeSelected?: boolean;
|
|
15
15
|
floating?: boolean;
|
|
16
16
|
showActions?: boolean;
|
|
17
|
+
onHeaderMouseMove?: (event: any) => void;
|
|
17
18
|
onHeaderMouseDown?: (event: any) => void;
|
|
18
19
|
onHeaderMouseDrag?: (event: any) => void;
|
|
19
20
|
onHeaderMouseDragEnd?: (event: any) => void;
|
|
@@ -111,6 +112,7 @@ export function PaneHeader({
|
|
|
111
112
|
windowModeSelected = false,
|
|
112
113
|
floating = false,
|
|
113
114
|
showActions = false,
|
|
115
|
+
onHeaderMouseMove,
|
|
114
116
|
onHeaderMouseDown,
|
|
115
117
|
onHeaderMouseDrag,
|
|
116
118
|
onHeaderMouseDragEnd,
|
|
@@ -143,6 +145,7 @@ export function PaneHeader({
|
|
|
143
145
|
data-focused={focused ? "true" : "false"}
|
|
144
146
|
data-window-mode-selected={windowModeSelected ? "true" : "false"}
|
|
145
147
|
onMouseDown={onHeaderMouseDown}
|
|
148
|
+
onMouseMove={onHeaderMouseMove}
|
|
146
149
|
onMouseDrag={onHeaderMouseDrag}
|
|
147
150
|
onMouseDragEnd={onHeaderMouseDragEnd}
|
|
148
151
|
onContextMenu={onHeaderContextMenu}
|
|
@@ -226,6 +229,7 @@ export function PaneHeader({
|
|
|
226
229
|
backgroundColor={backgroundColor}
|
|
227
230
|
flexDirection="row"
|
|
228
231
|
onMouseDown={handleTerminalHeaderMouseDown}
|
|
232
|
+
onMouseMove={onHeaderMouseMove}
|
|
229
233
|
onMouseDrag={onHeaderMouseDrag}
|
|
230
234
|
onMouseDragEnd={onHeaderMouseDragEnd}
|
|
231
235
|
>
|
|
@@ -263,6 +267,7 @@ export function PaneHeader({
|
|
|
263
267
|
backgroundColor={backgroundColor}
|
|
264
268
|
flexDirection="row"
|
|
265
269
|
onMouseDown={handleTerminalHeaderMouseDown}
|
|
270
|
+
onMouseMove={onHeaderMouseMove}
|
|
266
271
|
onMouseDrag={onHeaderMouseDrag}
|
|
267
272
|
onMouseDragEnd={onHeaderMouseDragEnd}
|
|
268
273
|
>
|
|
@@ -16,7 +16,7 @@ interface PaneWrapperProps {
|
|
|
16
16
|
flexGrow?: number;
|
|
17
17
|
showActions?: boolean;
|
|
18
18
|
onMouseDown?: (event: any) => void;
|
|
19
|
-
|
|
19
|
+
onHeaderMouseMove?: (event: any) => void;
|
|
20
20
|
onHeaderMouseDown?: (event: any) => void;
|
|
21
21
|
onHeaderMouseDrag?: (event: any) => void;
|
|
22
22
|
onHeaderMouseDragEnd?: (event: any) => void;
|
|
@@ -36,7 +36,7 @@ export function PaneWrapper({
|
|
|
36
36
|
flexGrow,
|
|
37
37
|
showActions = false,
|
|
38
38
|
onMouseDown,
|
|
39
|
-
|
|
39
|
+
onHeaderMouseMove,
|
|
40
40
|
onHeaderMouseDown,
|
|
41
41
|
onHeaderMouseDrag,
|
|
42
42
|
onHeaderMouseDragEnd,
|
|
@@ -76,7 +76,6 @@ export function PaneWrapper({
|
|
|
76
76
|
showBorderColor: true,
|
|
77
77
|
})}
|
|
78
78
|
onMouseDown={onMouseDown}
|
|
79
|
-
onMouseMove={onMouseMove}
|
|
80
79
|
>
|
|
81
80
|
{title && (
|
|
82
81
|
<PaneHeader
|
|
@@ -85,6 +84,7 @@ export function PaneWrapper({
|
|
|
85
84
|
focused={focused}
|
|
86
85
|
windowModeSelected={windowModeSelected}
|
|
87
86
|
showActions={showActions}
|
|
87
|
+
onHeaderMouseMove={onHeaderMouseMove}
|
|
88
88
|
onHeaderMouseDown={onHeaderMouseDown}
|
|
89
89
|
onHeaderMouseDrag={onHeaderMouseDrag}
|
|
90
90
|
onHeaderMouseDragEnd={onHeaderMouseDragEnd}
|
|
@@ -52,7 +52,7 @@ export function ShellActionMenuOverlay({
|
|
|
52
52
|
height={1}
|
|
53
53
|
width={innerWidth}
|
|
54
54
|
backgroundColor={hovered ? colors.selected : colors.panel}
|
|
55
|
-
|
|
55
|
+
onMouseOver={() => onHoverItem(item.id)}
|
|
56
56
|
onMouseDown={(mouseEvent: any) => {
|
|
57
57
|
mouseEvent.stopPropagation();
|
|
58
58
|
mouseEvent.preventDefault();
|
|
@@ -23,6 +23,8 @@ interface ResolveShellCursorOcclusionRectsOptions {
|
|
|
23
23
|
width: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
const MAX_RENDERABLE_ANCESTOR_DEPTH = 256;
|
|
27
|
+
|
|
26
28
|
export function resolveShellCursorOcclusionRects({
|
|
27
29
|
contentHeight,
|
|
28
30
|
dragFloatingRect,
|
|
@@ -102,7 +104,13 @@ function readRenderableBounds(renderable: unknown): LayoutBounds | null {
|
|
|
102
104
|
|
|
103
105
|
function readRenderablePaneId(renderable: unknown): string | null {
|
|
104
106
|
let current = renderable;
|
|
105
|
-
|
|
107
|
+
const seen = new Set<object>();
|
|
108
|
+
let depth = 0;
|
|
109
|
+
|
|
110
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
111
|
+
if (depth >= MAX_RENDERABLE_ANCESTOR_DEPTH) return null;
|
|
112
|
+
seen.add(current);
|
|
113
|
+
depth += 1;
|
|
106
114
|
const record = current as Record<string, unknown>;
|
|
107
115
|
const paneId = record["data-gloom-pane-id"];
|
|
108
116
|
if (typeof paneId === "string") return paneId;
|
|
@@ -1271,6 +1271,7 @@ describe("Shell", () => {
|
|
|
1271
1271
|
layouts: [{ name: "Default", layout: cloneLayout(mixedLayout) }],
|
|
1272
1272
|
}),
|
|
1273
1273
|
focusedPaneId: "ticker-detail:main",
|
|
1274
|
+
previousFocusedPaneId: "portfolio-list:main",
|
|
1274
1275
|
};
|
|
1275
1276
|
const actions: Array<any> = [];
|
|
1276
1277
|
|
|
@@ -1294,6 +1295,7 @@ describe("Shell", () => {
|
|
|
1294
1295
|
expect(updateLayout?.layout.instances.map((instance: { instanceId: string }) => instance.instanceId)).toEqual(["portfolio-list:main"]);
|
|
1295
1296
|
expect(updateLayout?.layout.floating).toEqual([]);
|
|
1296
1297
|
expect(updateLayout?.layout.dockRoot).toEqual({ kind: "pane", instanceId: "portfolio-list:main" });
|
|
1298
|
+
expect(updateLayout?.focusedPaneId).toBe("portfolio-list:main");
|
|
1297
1299
|
});
|
|
1298
1300
|
|
|
1299
1301
|
test("closes the focused floating pane with Cmd+W", async () => {
|
|
@@ -95,6 +95,7 @@ export function Shell({
|
|
|
95
95
|
const config = useAppSelector((state) => state.config);
|
|
96
96
|
const paneState = useAppSelector((state) => state.paneState);
|
|
97
97
|
const focusedPaneId = useAppSelector(selectFocusedPaneId);
|
|
98
|
+
const previousFocusedPaneId = useAppSelector((state) => state.previousFocusedPaneId);
|
|
98
99
|
const activePanel = useAppSelector((state) => state.activePanel);
|
|
99
100
|
const commandBarOpen = useAppSelector(selectCommandBarOpen);
|
|
100
101
|
const inputCaptured = useAppSelector((state) => state.inputCaptured);
|
|
@@ -154,15 +155,18 @@ export function Shell({
|
|
|
154
155
|
), [nativePaneChrome]);
|
|
155
156
|
const bounds = useMemo<LayoutBounds>(() => ({ x: 0, y: 0, width, height: contentHeight }), [contentHeight, width]);
|
|
156
157
|
|
|
157
|
-
const persistLayout = useCallback((nextLayout: LayoutConfig, options?: { pushHistory?: boolean }) => {
|
|
158
|
+
const persistLayout = useCallback((nextLayout: LayoutConfig, options?: { pushHistory?: boolean; focusedPaneId?: string | null }) => {
|
|
158
159
|
if (options?.pushHistory !== false) {
|
|
159
160
|
dispatch({ type: "PUSH_LAYOUT_HISTORY" });
|
|
160
161
|
}
|
|
161
|
-
|
|
162
|
+
const hasFocusTarget = !!options && Object.prototype.hasOwnProperty.call(options, "focusedPaneId");
|
|
163
|
+
dispatch(hasFocusTarget
|
|
164
|
+
? { type: "UPDATE_LAYOUT", layout: nextLayout, focusedPaneId: options.focusedPaneId ?? null }
|
|
165
|
+
: { type: "UPDATE_LAYOUT", layout: nextLayout });
|
|
162
166
|
scheduleConfigSave(syncConfigActiveLayoutState(
|
|
163
167
|
{ ...config, layout: nextLayout },
|
|
164
168
|
paneState,
|
|
165
|
-
focusedPaneId,
|
|
169
|
+
hasFocusTarget ? (options.focusedPaneId ?? null) : focusedPaneId,
|
|
166
170
|
activePanel,
|
|
167
171
|
));
|
|
168
172
|
}, [activePanel, config, dispatch, focusedPaneId, paneState]);
|
|
@@ -261,6 +265,7 @@ export function Shell({
|
|
|
261
265
|
nativePaneChrome,
|
|
262
266
|
paneMap,
|
|
263
267
|
persistLayout,
|
|
268
|
+
previousFocusedPaneId,
|
|
264
269
|
pluginRegistry,
|
|
265
270
|
rendererHost,
|
|
266
271
|
visibleLayout,
|
|
@@ -525,7 +530,14 @@ export function Shell({
|
|
|
525
530
|
height={nativePaneChrome ? undefined : contentHeight}
|
|
526
531
|
position={nativePaneChrome ? "relative" : undefined}
|
|
527
532
|
overflow="hidden"
|
|
528
|
-
{...(!nativePaneChrome
|
|
533
|
+
{...(!nativePaneChrome
|
|
534
|
+
? {
|
|
535
|
+
onMouseDown: handleMouse,
|
|
536
|
+
onMouseDrag: handleMouse,
|
|
537
|
+
onMouseDragEnd: handleMouse,
|
|
538
|
+
onMouseUp: handleMouse,
|
|
539
|
+
}
|
|
540
|
+
: {})}
|
|
529
541
|
>
|
|
530
542
|
<Box
|
|
531
543
|
position="absolute"
|
|
@@ -14,6 +14,15 @@ import type { LayoutConfig } from "../../../../types/config";
|
|
|
14
14
|
import type { RendererHost } from "../../../../ui";
|
|
15
15
|
import { capturePaneScreenshotPngBase64 } from "../../../../utils/dom-screenshot";
|
|
16
16
|
|
|
17
|
+
function removedFocusRestoreOptions(
|
|
18
|
+
layout: LayoutConfig,
|
|
19
|
+
currentFocusedPaneId: string | null,
|
|
20
|
+
paneId: string | null,
|
|
21
|
+
): { focusedPaneId: string } | undefined {
|
|
22
|
+
if (currentFocusedPaneId && isPaneInLayout(layout, currentFocusedPaneId)) return undefined;
|
|
23
|
+
return paneId && isPaneInLayout(layout, paneId) ? { focusedPaneId: paneId } : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
interface UseShellPaneActionsOptions {
|
|
18
27
|
closePaneMenu: () => void;
|
|
19
28
|
contentHeight: number;
|
|
@@ -22,7 +31,8 @@ interface UseShellPaneActionsOptions {
|
|
|
22
31
|
focusPane: (paneId: string) => void;
|
|
23
32
|
nativePaneChrome: boolean;
|
|
24
33
|
paneMap: Map<string, ResolvedPane>;
|
|
25
|
-
persistLayout: (nextLayout: LayoutConfig, options?: { pushHistory?: boolean }) => void;
|
|
34
|
+
persistLayout: (nextLayout: LayoutConfig, options?: { pushHistory?: boolean; focusedPaneId?: string | null }) => void;
|
|
35
|
+
previousFocusedPaneId: string | null;
|
|
26
36
|
pluginRegistry: PluginRegistry;
|
|
27
37
|
rendererHost: RendererHost;
|
|
28
38
|
visibleLayout: LayoutConfig;
|
|
@@ -38,6 +48,7 @@ export function useShellPaneActions({
|
|
|
38
48
|
nativePaneChrome,
|
|
39
49
|
paneMap,
|
|
40
50
|
persistLayout,
|
|
51
|
+
previousFocusedPaneId,
|
|
41
52
|
pluginRegistry,
|
|
42
53
|
rendererHost,
|
|
43
54
|
visibleLayout,
|
|
@@ -70,15 +81,17 @@ export function useShellPaneActions({
|
|
|
70
81
|
|
|
71
82
|
const closeFocusedPane = useCallback(() => {
|
|
72
83
|
if (!focusedPaneId || !isPaneInLayout(visibleLayout, focusedPaneId)) return false;
|
|
73
|
-
|
|
84
|
+
const nextLayout = removePane(visibleLayout, focusedPaneId);
|
|
85
|
+
persistLayout(nextLayout, removedFocusRestoreOptions(nextLayout, focusedPaneId, previousFocusedPaneId));
|
|
74
86
|
return true;
|
|
75
|
-
}, [focusedPaneId, persistLayout, visibleLayout]);
|
|
87
|
+
}, [focusedPaneId, persistLayout, previousFocusedPaneId, visibleLayout]);
|
|
76
88
|
|
|
77
89
|
const closeAllFloatingPanes = useCallback(() => {
|
|
78
90
|
if (visibleLayout.floating.length === 0) return false;
|
|
79
|
-
|
|
91
|
+
const nextLayout = removeFloatingPanes(visibleLayout);
|
|
92
|
+
persistLayout(nextLayout, removedFocusRestoreOptions(nextLayout, focusedPaneId, previousFocusedPaneId));
|
|
80
93
|
return true;
|
|
81
|
-
}, [persistLayout, visibleLayout]);
|
|
94
|
+
}, [focusedPaneId, persistLayout, previousFocusedPaneId, visibleLayout]);
|
|
82
95
|
|
|
83
96
|
const copyFocusedPaneScreenshot = useCallback(() => {
|
|
84
97
|
if (!focusedPaneId || !nativePaneChrome || !rendererHost.copyPngImage) return false;
|
|
@@ -118,8 +131,9 @@ export function useShellPaneActions({
|
|
|
118
131
|
}, [contentHeight, persistLayout, visibleLayout, width]);
|
|
119
132
|
|
|
120
133
|
const handleFloatingClose = useCallback((paneId: string) => {
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
const nextLayout = removePane(visibleLayout, paneId);
|
|
135
|
+
persistLayout(nextLayout, removedFocusRestoreOptions(nextLayout, focusedPaneId, previousFocusedPaneId));
|
|
136
|
+
}, [focusedPaneId, persistLayout, previousFocusedPaneId, visibleLayout]);
|
|
123
137
|
|
|
124
138
|
return {
|
|
125
139
|
closeAllFloatingPanes,
|
|
@@ -132,7 +132,7 @@ export function ShellPaneLayers({
|
|
|
132
132
|
windowModeSelected={windowModeSelected}
|
|
133
133
|
footer={footer}
|
|
134
134
|
onMouseDown={nativePaneChrome ? (event) => handleNativePaneMouseDown(leaf.instanceId, event) : undefined}
|
|
135
|
-
|
|
135
|
+
onHeaderMouseMove={() => setHoveredPaneIfChanged(leaf.instanceId)}
|
|
136
136
|
onHeaderMouseDown={nativePaneChrome && !transientFocusActive ? (event) => startNativeDockedDrag(leaf.instanceId, rect, event) : undefined}
|
|
137
137
|
onHeaderMouseDrag={nativePaneChrome && !transientFocusActive ? handleNativeDrag : undefined}
|
|
138
138
|
onHeaderMouseDragEnd={nativePaneChrome && !transientFocusActive ? handleNativeDrag : undefined}
|
|
@@ -192,7 +192,7 @@ export function ShellPaneLayers({
|
|
|
192
192
|
showActions={showActions}
|
|
193
193
|
footer={footer}
|
|
194
194
|
onMouseDown={nativePaneChrome ? (event) => handleNativePaneMouseDown(pane.instance.instanceId, event) : undefined}
|
|
195
|
-
|
|
195
|
+
onHeaderMouseMove={() => setHoveredPaneIfChanged(pane.instance.instanceId)}
|
|
196
196
|
onHeaderMouseDown={nativePaneChrome ? (event) => startNativeFloatingDrag(pane.instance.instanceId, preview, event) : undefined}
|
|
197
197
|
onHeaderMouseDrag={nativePaneChrome ? handleNativeDrag : undefined}
|
|
198
198
|
onHeaderMouseDragEnd={nativePaneChrome ? handleNativeDrag : undefined}
|
|
@@ -364,7 +364,7 @@ function CommandBarHint({
|
|
|
364
364
|
<Text
|
|
365
365
|
fg={hovered ? colors.text : colors.textDim}
|
|
366
366
|
{...(!nativePaneChrome ? { bg: hovered ? hoverBg() : undefined } : {})}
|
|
367
|
-
|
|
367
|
+
onMouseOver={() => setHoveredControl((current) => (current === "command-bar" ? current : "command-bar"))}
|
|
368
368
|
onMouseDown={openCommandBar}
|
|
369
369
|
{...(nativePaneChrome ? { "data-gloom-interactive": "true" } : {})}
|
|
370
370
|
>
|
|
@@ -385,7 +385,7 @@ function NativeGridlockTip({
|
|
|
385
385
|
<Text
|
|
386
386
|
fg={hoveredControl === "gridlock-tip" ? colors.textBright : colors.borderFocused}
|
|
387
387
|
attributes={TextAttributes.BOLD}
|
|
388
|
-
|
|
388
|
+
onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip" ? current : "gridlock-tip"))}
|
|
389
389
|
onMouseDown={handleGridlockTip}
|
|
390
390
|
data-gloom-interactive="true"
|
|
391
391
|
>
|
|
@@ -393,7 +393,7 @@ function NativeGridlockTip({
|
|
|
393
393
|
</Text>
|
|
394
394
|
<Text
|
|
395
395
|
fg={hoveredControl === "gridlock-tip-dismiss" ? colors.text : colors.textDim}
|
|
396
|
-
|
|
396
|
+
onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip-dismiss" ? current : "gridlock-tip-dismiss"))}
|
|
397
397
|
onMouseDown={dismissGridlockTip}
|
|
398
398
|
data-gloom-interactive="true"
|
|
399
399
|
>
|
|
@@ -415,14 +415,14 @@ function TerminalGridlockTip({
|
|
|
415
415
|
<Box width={1} />
|
|
416
416
|
<Box
|
|
417
417
|
backgroundColor={hoveredControl === "gridlock-tip" ? hoverBg() : colors.header}
|
|
418
|
-
|
|
418
|
+
onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip" ? current : "gridlock-tip"))}
|
|
419
419
|
onMouseDown={handleGridlockTip}
|
|
420
420
|
>
|
|
421
421
|
<Text fg={colors.headerText}> Gridlock All </Text>
|
|
422
422
|
</Box>
|
|
423
423
|
<Text
|
|
424
424
|
fg={hoveredControl === "gridlock-tip-dismiss" ? colors.text : colors.textDim}
|
|
425
|
-
|
|
425
|
+
onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip-dismiss" ? current : "gridlock-tip-dismiss"))}
|
|
426
426
|
onMouseDown={dismissGridlockTip}
|
|
427
427
|
>
|
|
428
428
|
{" x"}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { useMemo, useState, type CSSProperties } from "react";
|
|
2
|
+
import { Box, Text, TextAttributes, useUiCapabilities } from "../../ui";
|
|
3
|
+
import { blendHex, colors, priceColor } from "../../theme/colors";
|
|
4
|
+
import { blendForContrast, higherContrast } from "../../theme/color-utils";
|
|
5
|
+
import { padTo } from "../../utils/format";
|
|
6
|
+
import {
|
|
7
|
+
buildMetricTreemap,
|
|
8
|
+
buildMetricTreemapRects,
|
|
9
|
+
type FloatMetricTreemapTile,
|
|
10
|
+
type MetricTreemapItem,
|
|
11
|
+
type MetricTreemapTile,
|
|
12
|
+
} from "./layout";
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
buildMetricTreemapNavigationTiles,
|
|
16
|
+
findMetricTreemapNeighbor,
|
|
17
|
+
type MetricTreemapDirection,
|
|
18
|
+
type MetricTreemapItem,
|
|
19
|
+
} from "./layout";
|
|
20
|
+
|
|
21
|
+
type PreventableMouseEvent = { preventDefault(): void };
|
|
22
|
+
|
|
23
|
+
export interface MetricTreemapSurfaceProps<T> {
|
|
24
|
+
items: MetricTreemapItem<T>[];
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
selectedId: string | null;
|
|
28
|
+
onSelect: (item: MetricTreemapItem<T>) => void;
|
|
29
|
+
onActivate?: (item: MetricTreemapItem<T>) => void;
|
|
30
|
+
emptyStateTitle?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function metricTileColor(item: MetricTreemapItem): string {
|
|
34
|
+
if (item.colorValue == null) return colors.neutral;
|
|
35
|
+
return priceColor(item.colorValue);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function desktopMetricTileColor(item: MetricTreemapItem): string {
|
|
39
|
+
if (item.colorValue == null || item.colorValue === 0) {
|
|
40
|
+
return colors.neutral;
|
|
41
|
+
}
|
|
42
|
+
const intensity = Math.max(0.38, Math.min(0.88, Math.log1p(Math.abs(item.colorValue)) / Math.log1p(30)));
|
|
43
|
+
return blendHex(colors.panel, item.colorValue > 0 ? colors.positive : colors.negative, intensity);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const TILE_TEXT_MIN_CONTRAST = 4.5;
|
|
47
|
+
|
|
48
|
+
function tileTextColor(backgroundColor: string): string {
|
|
49
|
+
const preferred = higherContrast(
|
|
50
|
+
higherContrast(colors.text, colors.textBright, backgroundColor),
|
|
51
|
+
colors.selectedText,
|
|
52
|
+
backgroundColor,
|
|
53
|
+
);
|
|
54
|
+
const fallback = higherContrast("#000000", "#ffffff", backgroundColor);
|
|
55
|
+
return blendForContrast(preferred, backgroundColor, fallback, TILE_TEXT_MIN_CONTRAST);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function visibleLines(item: MetricTreemapItem): string[] {
|
|
59
|
+
return [
|
|
60
|
+
item.label,
|
|
61
|
+
item.primaryText,
|
|
62
|
+
item.secondaryText,
|
|
63
|
+
item.tertiaryText,
|
|
64
|
+
].filter((line): line is string => !!line);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function Tile<T>({ tile, selected, onSelect, onActivate }: {
|
|
68
|
+
tile: MetricTreemapTile<T>;
|
|
69
|
+
selected: boolean;
|
|
70
|
+
onSelect: () => void;
|
|
71
|
+
onActivate?: () => void;
|
|
72
|
+
}) {
|
|
73
|
+
const renderWidth = Math.max(1, tile.width - (tile.width > 2 ? 1 : 0));
|
|
74
|
+
const renderHeight = Math.max(1, tile.height - (tile.height > 2 ? 1 : 0));
|
|
75
|
+
const innerWidth = Math.max(1, renderWidth - 1);
|
|
76
|
+
const backgroundColor = selected ? colors.selected : metricTileColor(tile.item);
|
|
77
|
+
const textColor = tileTextColor(backgroundColor);
|
|
78
|
+
const attributes = selected ? TextAttributes.BOLD : TextAttributes.NONE;
|
|
79
|
+
const lines = visibleLines(tile.item);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Box
|
|
83
|
+
position="absolute"
|
|
84
|
+
left={tile.x}
|
|
85
|
+
top={tile.y}
|
|
86
|
+
width={renderWidth}
|
|
87
|
+
height={renderHeight}
|
|
88
|
+
backgroundColor={backgroundColor}
|
|
89
|
+
onMouseDown={(event: PreventableMouseEvent) => {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
onSelect();
|
|
92
|
+
}}
|
|
93
|
+
onMouseOver={onSelect}
|
|
94
|
+
onMouseMove={onSelect}
|
|
95
|
+
onDoubleClick={onActivate}
|
|
96
|
+
>
|
|
97
|
+
{lines.slice(0, renderHeight).map((line, index) => (
|
|
98
|
+
<Text key={`${tile.item.id}:${index}`} fg={textColor} attributes={attributes}>
|
|
99
|
+
{padTo(line, innerWidth)}
|
|
100
|
+
</Text>
|
|
101
|
+
))}
|
|
102
|
+
</Box>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function pct(value: number, total: number): string {
|
|
107
|
+
return `${total > 0 ? value / total * 100 : 0}%`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function DesktopTile<T>({ tile, chartWidth, chartHeight, selected, hovered, onSelect, onActivate, onHover }: {
|
|
111
|
+
tile: FloatMetricTreemapTile<T>;
|
|
112
|
+
chartWidth: number;
|
|
113
|
+
chartHeight: number;
|
|
114
|
+
selected: boolean;
|
|
115
|
+
hovered: boolean;
|
|
116
|
+
onSelect: () => void;
|
|
117
|
+
onActivate?: () => void;
|
|
118
|
+
onHover: (hovered: boolean) => void;
|
|
119
|
+
}) {
|
|
120
|
+
const lines = visibleLines(tile.item);
|
|
121
|
+
const canShowDetails = tile.width >= 7 && tile.height >= 3;
|
|
122
|
+
const canShowExtra = tile.height >= 3.7;
|
|
123
|
+
const canShowLabel = tile.width >= 4 && tile.height >= 1.4;
|
|
124
|
+
const isTiny = tile.width < 5 || tile.height < 2;
|
|
125
|
+
const backgroundColor = desktopMetricTileColor(tile.item);
|
|
126
|
+
const textColor = tileTextColor(backgroundColor);
|
|
127
|
+
const style: CSSProperties = {
|
|
128
|
+
position: "absolute",
|
|
129
|
+
left: `calc(${pct(tile.x, chartWidth)} + 1px)`,
|
|
130
|
+
top: `calc(${pct(tile.y, chartHeight)} + 1px)`,
|
|
131
|
+
width: `max(1px, calc(${pct(tile.width, chartWidth)} - 2px))`,
|
|
132
|
+
height: `max(1px, calc(${pct(tile.height, chartHeight)} - 2px))`,
|
|
133
|
+
minWidth: 1,
|
|
134
|
+
minHeight: 1,
|
|
135
|
+
padding: isTiny ? "3px 4px" : "7px 8px",
|
|
136
|
+
overflow: "hidden",
|
|
137
|
+
borderRadius: 5,
|
|
138
|
+
border: selected
|
|
139
|
+
? `1px solid ${colors.textBright}`
|
|
140
|
+
: hovered
|
|
141
|
+
? `1px solid ${blendHex(colors.textBright, backgroundColor, 0.55)}`
|
|
142
|
+
: `1px solid ${blendHex(backgroundColor, colors.textBright, 0.11)}`,
|
|
143
|
+
boxShadow: selected
|
|
144
|
+
? `inset 0 0 0 1px ${blendHex(backgroundColor, colors.textBright, 0.55)}`
|
|
145
|
+
: hovered
|
|
146
|
+
? `inset 0 0 0 1px ${blendHex(backgroundColor, colors.textBright, 0.18)}`
|
|
147
|
+
: "none",
|
|
148
|
+
backgroundColor,
|
|
149
|
+
color: textColor,
|
|
150
|
+
display: "flex",
|
|
151
|
+
flexDirection: "column",
|
|
152
|
+
alignItems: "stretch",
|
|
153
|
+
justifyContent: "flex-start",
|
|
154
|
+
gap: 2,
|
|
155
|
+
cursor: "pointer",
|
|
156
|
+
transition: "border-color 120ms ease, box-shadow 120ms ease, filter 120ms ease",
|
|
157
|
+
filter: hovered ? "brightness(1.08)" : undefined,
|
|
158
|
+
};
|
|
159
|
+
const textStyle: CSSProperties = {
|
|
160
|
+
display: "block",
|
|
161
|
+
minWidth: 0,
|
|
162
|
+
width: "100%",
|
|
163
|
+
overflow: "hidden",
|
|
164
|
+
textOverflow: "ellipsis",
|
|
165
|
+
whiteSpace: "nowrap",
|
|
166
|
+
lineHeight: 1.1,
|
|
167
|
+
letterSpacing: 0,
|
|
168
|
+
textShadow: `0 1px 1px ${blendHex(backgroundColor, colors.bg, 0.35)}`,
|
|
169
|
+
};
|
|
170
|
+
const startHover = () => {
|
|
171
|
+
onHover(true);
|
|
172
|
+
onSelect();
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<Box
|
|
177
|
+
data-gloom-role="metric-treemap-desktop-tile"
|
|
178
|
+
style={style}
|
|
179
|
+
onMouseDown={(event: PreventableMouseEvent) => {
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
onSelect();
|
|
182
|
+
}}
|
|
183
|
+
onDoubleClick={onActivate}
|
|
184
|
+
onMouseOver={startHover}
|
|
185
|
+
onMouseMove={startHover}
|
|
186
|
+
onMouseOut={() => onHover(false)}
|
|
187
|
+
>
|
|
188
|
+
{canShowLabel && (
|
|
189
|
+
<Text
|
|
190
|
+
fg={textColor}
|
|
191
|
+
attributes={selected ? TextAttributes.BOLD : TextAttributes.NONE}
|
|
192
|
+
style={{
|
|
193
|
+
...textStyle,
|
|
194
|
+
fontSize: isTiny ? 11 : 13,
|
|
195
|
+
fontWeight: 700,
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
{lines[0]}
|
|
199
|
+
</Text>
|
|
200
|
+
)}
|
|
201
|
+
{canShowDetails && lines.slice(1, canShowExtra ? 4 : 3).map((line, index) => (
|
|
202
|
+
<Text key={`${tile.item.id}:detail:${index}`} fg={textColor} style={{ ...textStyle, fontSize: 12, fontWeight: 600 }}>
|
|
203
|
+
{line}
|
|
204
|
+
</Text>
|
|
205
|
+
))}
|
|
206
|
+
</Box>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function DesktopMetricTreemapSurface<T>({ items, width, height, selectedId, onSelect, onActivate, cellAspect, emptyStateTitle }: MetricTreemapSurfaceProps<T> & {
|
|
211
|
+
cellAspect: number;
|
|
212
|
+
}) {
|
|
213
|
+
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
|
214
|
+
const chartWidth = Math.max(1, width - 2);
|
|
215
|
+
const tiles = useMemo(() => buildMetricTreemapRects(items, chartWidth, height, cellAspect), [cellAspect, chartWidth, height, items]);
|
|
216
|
+
|
|
217
|
+
if (tiles.length === 0) {
|
|
218
|
+
return (
|
|
219
|
+
<Box width={width} height={height} paddingX={1} paddingY={1}>
|
|
220
|
+
<Text fg={colors.textDim}>{emptyStateTitle ?? "No chart data"}</Text>
|
|
221
|
+
</Box>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<Box
|
|
227
|
+
width={width}
|
|
228
|
+
height={height}
|
|
229
|
+
paddingX={1}
|
|
230
|
+
style={{
|
|
231
|
+
backgroundColor: colors.bg,
|
|
232
|
+
overflow: "hidden",
|
|
233
|
+
}}
|
|
234
|
+
>
|
|
235
|
+
<Box
|
|
236
|
+
data-gloom-role="metric-treemap"
|
|
237
|
+
width={chartWidth}
|
|
238
|
+
height={height}
|
|
239
|
+
style={{
|
|
240
|
+
position: "relative",
|
|
241
|
+
width: "100%",
|
|
242
|
+
height: "100%",
|
|
243
|
+
backgroundColor: colors.bg,
|
|
244
|
+
overflow: "hidden",
|
|
245
|
+
}}
|
|
246
|
+
>
|
|
247
|
+
{tiles.map((tile) => (
|
|
248
|
+
<DesktopTile
|
|
249
|
+
key={tile.item.id}
|
|
250
|
+
tile={tile}
|
|
251
|
+
chartWidth={chartWidth}
|
|
252
|
+
chartHeight={height}
|
|
253
|
+
selected={tile.item.id === selectedId}
|
|
254
|
+
hovered={tile.item.id === hoveredId}
|
|
255
|
+
onSelect={() => onSelect(tile.item)}
|
|
256
|
+
onActivate={onActivate ? () => onActivate(tile.item) : undefined}
|
|
257
|
+
onHover={(isHovered) => setHoveredId((current) => (isHovered ? tile.item.id : current === tile.item.id ? null : current))}
|
|
258
|
+
/>
|
|
259
|
+
))}
|
|
260
|
+
</Box>
|
|
261
|
+
</Box>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function MetricTreemapSurface<T>({
|
|
266
|
+
items,
|
|
267
|
+
width,
|
|
268
|
+
height,
|
|
269
|
+
selectedId,
|
|
270
|
+
onSelect,
|
|
271
|
+
onActivate,
|
|
272
|
+
emptyStateTitle,
|
|
273
|
+
}: MetricTreemapSurfaceProps<T>) {
|
|
274
|
+
const { cellWidthPx = 8, cellHeightPx = 18, nativePaneChrome } = useUiCapabilities();
|
|
275
|
+
const chartWidth = Math.max(1, width - 2);
|
|
276
|
+
const cellAspect = Math.max(0.5, Math.min(4, cellHeightPx / Math.max(1, cellWidthPx)));
|
|
277
|
+
const tiles = useMemo(() => buildMetricTreemap(items, chartWidth, height, cellAspect), [cellAspect, chartWidth, height, items]);
|
|
278
|
+
|
|
279
|
+
if (nativePaneChrome) {
|
|
280
|
+
return (
|
|
281
|
+
<DesktopMetricTreemapSurface
|
|
282
|
+
items={items}
|
|
283
|
+
width={width}
|
|
284
|
+
height={height}
|
|
285
|
+
selectedId={selectedId}
|
|
286
|
+
onSelect={onSelect}
|
|
287
|
+
onActivate={onActivate}
|
|
288
|
+
cellAspect={cellAspect}
|
|
289
|
+
emptyStateTitle={emptyStateTitle}
|
|
290
|
+
/>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (tiles.length === 0) {
|
|
295
|
+
return (
|
|
296
|
+
<Box width={width} height={height} paddingX={1} paddingY={1}>
|
|
297
|
+
<Text fg={colors.textDim}>{emptyStateTitle ?? "No chart data"}</Text>
|
|
298
|
+
</Box>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<Box width={width} height={height} paddingX={1} backgroundColor={colors.bg}>
|
|
304
|
+
<Box position="relative" width={chartWidth} height={height} backgroundColor={colors.bg}>
|
|
305
|
+
{tiles.map((tile) => (
|
|
306
|
+
<Tile
|
|
307
|
+
key={tile.item.id}
|
|
308
|
+
tile={tile}
|
|
309
|
+
selected={tile.item.id === selectedId}
|
|
310
|
+
onSelect={() => onSelect(tile.item)}
|
|
311
|
+
onActivate={onActivate ? () => onActivate(tile.item) : undefined}
|
|
312
|
+
/>
|
|
313
|
+
))}
|
|
314
|
+
</Box>
|
|
315
|
+
</Box>
|
|
316
|
+
);
|
|
317
|
+
}
|