gloomberb 0.16.2 → 0.16.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/brokers/account-cache.ts +45 -0
- package/src/cli/command-options.ts +74 -0
- package/src/cli/commands/command-utils.ts +29 -0
- package/src/cli/commands/market.ts +170 -54
- package/src/cli/commands/overview.ts +62 -19
- package/src/cli/commands/ticker.ts +138 -43
- package/src/cli/desktop-pane-shot.ts +3 -0
- package/src/cli/help.ts +26 -6
- package/src/cli/history-rows.ts +187 -15
- package/src/cli/index.ts +12 -7
- package/src/cli/options.ts +5 -1
- package/src/cli/pane-functions/capabilities.ts +5 -0
- package/src/cli/pane-functions/dom.ts +69 -16
- package/src/cli/pane-functions/function-help.ts +4 -2
- package/src/cli/pane-functions/headless.ts +163 -31
- package/src/cli/pane-functions/index.ts +9 -2
- package/src/cli/pane-functions/report.ts +8 -1
- package/src/cli/pane-functions/resolver.ts +28 -2
- package/src/cli/pane-functions/screenshot.ts +4 -0
- package/src/cli/registry.ts +6 -3
- package/src/cli/report-tables.ts +460 -0
- package/src/cli/result.ts +13 -3
- package/src/components/data-table/export.ts +6 -51
- package/src/components/data-table/view.tsx +3 -1
- package/src/components/layout/shell/active-drag.ts +67 -35
- package/src/components/layout/shell/cursor-occlusion.ts +6 -5
- package/src/components/layout/shell/drag/index.ts +12 -7
- package/src/components/layout/shell/drag/live.ts +122 -0
- package/src/components/layout/shell/drag/overlays.tsx +179 -35
- package/src/components/layout/shell/drag/runtime.ts +62 -50
- package/src/components/layout/shell/drag/slide.ts +10 -0
- package/src/components/layout/shell/index.tsx +37 -51
- package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
- package/src/components/layout/shell/native/surfaces.ts +30 -14
- package/src/components/layout/shell/pane/layers.tsx +419 -179
- package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
- package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
- package/src/data/json-persistence.ts +17 -3
- package/src/data/json-storage.ts +2 -0
- package/src/data/memory-resource-store.ts +46 -0
- package/src/market-data/market/freshness.ts +45 -0
- package/src/market-data/quotes/freshness.ts +5 -0
- package/src/plugins/builtin/analytics/pane-model.ts +10 -6
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/catalysts/detail.tsx +1 -1
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/holders/client.ts +3 -6
- package/src/plugins/builtin/holders/format.ts +31 -1
- package/src/plugins/builtin/holders/headless.ts +26 -5
- package/src/plugins/builtin/holders/pane.tsx +22 -13
- package/src/plugins/builtin/holders/report-header.ts +46 -5
- package/src/plugins/builtin/kelly-sizer/pane.tsx +12 -6
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
- package/src/plugins/builtin/members/pane.tsx +10 -0
- package/src/plugins/builtin/perps/pane.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
- package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
- package/src/plugins/builtin/portfolio-list/cli/render.ts +2 -0
- package/src/plugins/builtin/portfolio-list/headless.ts +2 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
- package/src/plugins/builtin/shared/members-menu.tsx +260 -0
- package/src/plugins/builtin/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/themes/pane.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
- package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +20 -4
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
- package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/index.ts +5 -5
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
- package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
- package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
- package/src/plugins/builtin/world-venue-map/model.ts +16 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
- package/src/renderers/browser/app-services.ts +3 -1
- package/src/renderers/dom/data-table/dom.ts +31 -0
- package/src/renderers/dom/data-table/index.tsx +5 -0
- package/src/renderers/dom/host/box.tsx +113 -17
- package/src/renderers/dom/host/drag-shield.ts +24 -0
- package/src/renderers/dom/host/live-frame.ts +54 -0
- package/src/renderers/dom/host/mouse.ts +8 -1
- package/src/renderers/dom/host/scroll-box.tsx +6 -5
- package/src/renderers/dom/host/tabs.tsx +3 -2
- package/src/renderers/dom/styles.css +13 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +3 -0
- package/src/renderers/electrobun/view/host/native.ts +0 -10
- package/src/renderers/electrobun/view/host/window-move.ts +28 -0
- package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
- package/src/renderers/electrobun/view/ui-host.tsx +2 -5
- package/src/renderers/opentui/ui-host.tsx +7 -1
- package/src/sources/gloomberb-cloud/normalizers.ts +16 -20
- package/src/sources/provider-router/batches.ts +3 -3
- package/src/sources/provider-router/financial-routes.ts +2 -2
- package/src/sources/provider-router/financials.ts +70 -7
- package/src/sources/provider-router/primary.ts +2 -2
- package/src/state/pane-scroll-registry.tsx +67 -35
- package/src/test-support/margin-account.ts +108 -0
- package/src/time-series/resolution.ts +15 -0
- package/src/types/financials.ts +32 -3
- package/src/ui/host.tsx +12 -0
- package/src/ui/index.tsx +8 -3
- package/src/ui/scroll-box-axis.ts +26 -0
- package/src/utils/currency-units.ts +8 -3
- package/src/utils/depositary-receipt.ts +54 -10
- package/src/utils/display-number.ts +63 -0
- package/src/utils/fundamentals.ts +17 -1
- package/src/utils/price-history-integrity.ts +19 -6
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
useRef,
|
|
7
7
|
type CSSProperties,
|
|
8
8
|
type MouseEvent,
|
|
9
|
+
type PointerEvent,
|
|
9
10
|
type ReactNode,
|
|
10
11
|
type Ref,
|
|
11
12
|
} from "react";
|
|
@@ -19,8 +20,56 @@ import {
|
|
|
19
20
|
cellMouseEvent,
|
|
20
21
|
requestWebFrame,
|
|
21
22
|
} from "./mouse";
|
|
23
|
+
import { hideDragShield, showDragShield } from "./drag-shield";
|
|
24
|
+
import { applyLiveFrame } from "./live-frame";
|
|
22
25
|
import { cleanDomProps, commonStyle } from "./style";
|
|
23
26
|
|
|
27
|
+
/** Farther than this from the pointer, in px, a predicted point is noise. */
|
|
28
|
+
const MAX_PREDICTION_PX = 48;
|
|
29
|
+
|
|
30
|
+
type PointerMoveEvent = globalThis.MouseEvent & {
|
|
31
|
+
pointerId?: number;
|
|
32
|
+
getPredictedEvents?: () => Array<{ clientX: number; clientY: number }>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function isPointerMove(event: globalThis.MouseEvent): boolean {
|
|
36
|
+
return typeof (event as PointerMoveEvent).pointerId === "number";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Where the browser predicts the pointer will be a moment after this move, when
|
|
41
|
+
* it predicts at all (Chrome does for the mouse; WebKit returns none). The
|
|
42
|
+
* event itself already sits at the last of its coalesced points.
|
|
43
|
+
*/
|
|
44
|
+
function predictedPoint(event: globalThis.MouseEvent): { clientX: number; clientY: number } | null {
|
|
45
|
+
const predicted = (event as PointerMoveEvent).getPredictedEvents?.();
|
|
46
|
+
const next = predicted?.[0];
|
|
47
|
+
if (!next) return null;
|
|
48
|
+
const dx = next.clientX - event.clientX;
|
|
49
|
+
const dy = next.clientY - event.clientY;
|
|
50
|
+
return dx * dx + dy * dy <= MAX_PREDICTION_PX * MAX_PREDICTION_PX ? next : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function capturePointer(element: HTMLElement | null, pointerId: number | null): { element: HTMLElement; pointerId: number } | null {
|
|
54
|
+
if (!element || pointerId === null || typeof element.setPointerCapture !== "function") return null;
|
|
55
|
+
try {
|
|
56
|
+
element.setPointerCapture(pointerId);
|
|
57
|
+
return { element, pointerId };
|
|
58
|
+
} catch {
|
|
59
|
+
// The pointer is already up or belongs to another frame: drag uncaptured.
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function releasePointer(captured: { element: HTMLElement; pointerId: number } | null): void {
|
|
65
|
+
if (!captured) return;
|
|
66
|
+
try {
|
|
67
|
+
if (captured.element.hasPointerCapture?.(captured.pointerId)) captured.element.releasePointerCapture(captured.pointerId);
|
|
68
|
+
} catch {
|
|
69
|
+
// Released already, by the browser on pointerup.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
24
73
|
export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { children?: ReactNode }>(
|
|
25
74
|
function WebBox({ children, ...props }, ref: Ref<HTMLDivElement>) {
|
|
26
75
|
const elementRef = useRef<HTMLDivElement | null>(null);
|
|
@@ -34,7 +83,13 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
34
83
|
? props.hoverBackgroundColor
|
|
35
84
|
: undefined;
|
|
36
85
|
|
|
37
|
-
useImperativeHandle(ref, () =>
|
|
86
|
+
useImperativeHandle(ref, () => {
|
|
87
|
+
const handle = cellBoundsForElement(() => elementRef.current, () => propsRef.current);
|
|
88
|
+
handle.setLiveFrame = (frame) => {
|
|
89
|
+
if (elementRef.current) applyLiveFrame(elementRef.current, frame, propsRef.current);
|
|
90
|
+
};
|
|
91
|
+
return handle as unknown as HTMLDivElement;
|
|
92
|
+
}, []);
|
|
38
93
|
|
|
39
94
|
const cancelPendingFrame = () => {
|
|
40
95
|
if (frameRef.current !== null) {
|
|
@@ -63,7 +118,11 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
63
118
|
flushPendingFrameMouseHandlers();
|
|
64
119
|
};
|
|
65
120
|
|
|
66
|
-
const scheduleFrameMouseHandler = (
|
|
121
|
+
const scheduleFrameMouseHandler = (
|
|
122
|
+
event: MouseLikeEvent,
|
|
123
|
+
type: "move" | "drag",
|
|
124
|
+
ahead: { clientX: number; clientY: number } | null = null,
|
|
125
|
+
) => {
|
|
67
126
|
// The DOM keeps firing mousemove while a button is held. A move means no
|
|
68
127
|
// button anywhere else in the app, so drags must not surface as moves.
|
|
69
128
|
if (type === "move" && draggingRef.current) return;
|
|
@@ -74,7 +133,7 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
74
133
|
return;
|
|
75
134
|
}
|
|
76
135
|
|
|
77
|
-
const nextEvent = cellMouseEvent(event, type);
|
|
136
|
+
const nextEvent = cellMouseEvent(event, type, ahead);
|
|
78
137
|
if (type === "move") {
|
|
79
138
|
pendingMoveRef.current = nextEvent;
|
|
80
139
|
} else {
|
|
@@ -89,27 +148,42 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
89
148
|
};
|
|
90
149
|
|
|
91
150
|
const lastPointRef = useRef({ x: 0, y: 0 });
|
|
151
|
+
// The pointer that pressed this box, so a drag can capture it.
|
|
152
|
+
const pressedPointerRef = useRef<number | null>(null);
|
|
92
153
|
const dragSessionRef = useRef<{
|
|
93
154
|
move: (event: globalThis.MouseEvent) => void;
|
|
94
155
|
up: (event: globalThis.MouseEvent) => void;
|
|
95
156
|
cancel: () => void;
|
|
157
|
+
captured: { element: HTMLElement; pointerId: number } | null;
|
|
158
|
+
/** Pointer events are arriving, so the mousemove after each is a repeat. */
|
|
159
|
+
pointerMoves: boolean;
|
|
160
|
+
/** Moved far enough to be a drag: the drag shield is up. */
|
|
161
|
+
shielded: boolean;
|
|
96
162
|
} | null>(null);
|
|
97
|
-
|
|
98
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Ends the press. "end" reports a release at `event` (or the last point);
|
|
165
|
+
* "cancel" reports the drag abandoned (the window lost focus or the system
|
|
166
|
+
* took the pointer), so a move can put things back.
|
|
167
|
+
*/
|
|
168
|
+
const endDocumentDragRef = useRef<(event: globalThis.MouseEvent | null, outcome: "end" | "cancel") => void>(() => {});
|
|
169
|
+
endDocumentDragRef.current = (event, outcome) => {
|
|
99
170
|
const session = dragSessionRef.current;
|
|
100
171
|
const wasDragging = draggingRef.current;
|
|
101
172
|
if (!session && !wasDragging) return;
|
|
102
173
|
draggingRef.current = false;
|
|
174
|
+
const shielded = session?.shielded === true;
|
|
103
175
|
dragSessionRef.current = null;
|
|
104
|
-
|
|
176
|
+
if (shielded) hideDragShield();
|
|
105
177
|
if (session) {
|
|
178
|
+
document.removeEventListener("pointermove", session.move);
|
|
106
179
|
document.removeEventListener("mousemove", session.move);
|
|
107
180
|
document.removeEventListener("mouseup", session.up);
|
|
108
181
|
document.removeEventListener("pointerup", session.up);
|
|
109
182
|
document.removeEventListener("pointercancel", session.cancel);
|
|
110
183
|
window.removeEventListener("blur", session.cancel);
|
|
184
|
+
releasePointer(session.captured);
|
|
111
185
|
}
|
|
112
|
-
if (!
|
|
186
|
+
if (!wasDragging) return;
|
|
113
187
|
const point = event ?? new MouseEvent("mouseup", {
|
|
114
188
|
clientX: lastPointRef.current.x,
|
|
115
189
|
clientY: lastPointRef.current.y,
|
|
@@ -119,14 +193,14 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
119
193
|
flushPendingFrameNow();
|
|
120
194
|
callMouseHandler(propsRef.current.onMouse, point, "up");
|
|
121
195
|
callMouseHandler(propsRef.current.onMouseUp, point, "up");
|
|
122
|
-
callMouseHandler(propsRef.current.onMouseDragEnd, point, "drag-end");
|
|
196
|
+
callMouseHandler(propsRef.current.onMouseDragEnd, point, outcome === "cancel" ? "drag-cancel" : "drag-end");
|
|
123
197
|
};
|
|
124
198
|
|
|
125
199
|
useEffect(() => () => {
|
|
126
200
|
cancelPendingFrame();
|
|
127
201
|
pendingMoveRef.current = null;
|
|
128
202
|
pendingDragRef.current = null;
|
|
129
|
-
endDocumentDragRef.current(null,
|
|
203
|
+
endDocumentDragRef.current(null, "end");
|
|
130
204
|
}, []);
|
|
131
205
|
|
|
132
206
|
const handlesWheel = typeof props.onMouseScroll === "function";
|
|
@@ -146,7 +220,7 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
146
220
|
pendingMoveRef.current = null;
|
|
147
221
|
// A press that never saw mouseup (the window move took the pointer) is still tracked.
|
|
148
222
|
// Finish it before the new press, so its drag-end cannot clear the drag this press starts.
|
|
149
|
-
endDocumentDragRef.current(null,
|
|
223
|
+
endDocumentDragRef.current(null, "end");
|
|
150
224
|
callMouseHandler(propsRef.current.onMouseDown, event, "down");
|
|
151
225
|
if (event.button !== 0 || (event.isPropagationStopped() && !hasDirectDrag)) return;
|
|
152
226
|
if (!hasSyntheticDrag && !hasDirectDrag) return;
|
|
@@ -157,27 +231,41 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
157
231
|
const originY = event.clientY;
|
|
158
232
|
const move = (moveEvent: globalThis.MouseEvent) => {
|
|
159
233
|
if (!draggingRef.current) return;
|
|
234
|
+
const current = dragSessionRef.current;
|
|
235
|
+
if (isPointerMove(moveEvent)) {
|
|
236
|
+
if (current) current.pointerMoves = true;
|
|
237
|
+
} else if (current?.pointerMoves) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
160
240
|
lastPointRef.current = { x: moveEvent.clientX, y: moveEvent.clientY };
|
|
161
241
|
// The button is up and mouseup never arrived. End the drag instead of following the cursor.
|
|
162
242
|
if (moveEvent.buttons === 0) {
|
|
163
|
-
endDocumentDragRef.current(moveEvent,
|
|
243
|
+
endDocumentDragRef.current(moveEvent, "end");
|
|
164
244
|
return;
|
|
165
245
|
}
|
|
166
246
|
const dx = moveEvent.clientX - originX;
|
|
167
247
|
const dy = moveEvent.clientY - originY;
|
|
168
|
-
if (!
|
|
248
|
+
if (current && !current.shielded) {
|
|
169
249
|
if (dx * dx + dy * dy < 9) return;
|
|
170
|
-
|
|
250
|
+
current.shielded = true;
|
|
251
|
+
showDragShield();
|
|
252
|
+
// Captured once it is a drag (a click keeps its usual target), the
|
|
253
|
+
// moves skip hit testing and leave every other element's hover state
|
|
254
|
+
// alone: nothing under the pointer restyles or repaints while a pane,
|
|
255
|
+
// divider or chart is dragged across it. The document listeners still
|
|
256
|
+
// see every move.
|
|
257
|
+
if (!current.captured) current.captured = capturePointer(elementRef.current, pressedPointerRef.current);
|
|
171
258
|
}
|
|
172
|
-
scheduleFrameMouseHandler(moveEvent, "drag");
|
|
259
|
+
scheduleFrameMouseHandler(moveEvent, "drag", predictedPoint(moveEvent));
|
|
173
260
|
};
|
|
174
261
|
const up = (upEvent: globalThis.MouseEvent) => {
|
|
175
|
-
endDocumentDragRef.current(upEvent,
|
|
262
|
+
endDocumentDragRef.current(upEvent, "end");
|
|
176
263
|
};
|
|
177
264
|
const cancel = () => {
|
|
178
|
-
endDocumentDragRef.current(null,
|
|
265
|
+
endDocumentDragRef.current(null, "cancel");
|
|
179
266
|
};
|
|
180
|
-
dragSessionRef.current = { move, up, cancel };
|
|
267
|
+
dragSessionRef.current = { move, up, cancel, captured: null, pointerMoves: false, shielded: false };
|
|
268
|
+
document.addEventListener("pointermove", move);
|
|
181
269
|
document.addEventListener("mousemove", move);
|
|
182
270
|
document.addEventListener("mouseup", up);
|
|
183
271
|
document.addEventListener("pointerup", up);
|
|
@@ -185,6 +273,13 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
185
273
|
window.addEventListener("blur", cancel);
|
|
186
274
|
};
|
|
187
275
|
|
|
276
|
+
const handlesDrag = typeof props.onMouse === "function"
|
|
277
|
+
|| typeof props.onMouseDrag === "function"
|
|
278
|
+
|| typeof props.onMouseDragEnd === "function";
|
|
279
|
+
const handlePointerDown = (event: PointerEvent) => {
|
|
280
|
+
pressedPointerRef.current = event.pointerId;
|
|
281
|
+
};
|
|
282
|
+
|
|
188
283
|
const handleMouseDownCapture = (event: MouseEvent) => {
|
|
189
284
|
callMouseHandler(propsRef.current.onMouseDownCapture, event, "down");
|
|
190
285
|
};
|
|
@@ -204,6 +299,7 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
204
299
|
data-gloom-hover-bg={hoverBackgroundColor ? "true" : undefined}
|
|
205
300
|
ref={elementRef}
|
|
206
301
|
onMouseDownCapture={typeof props.onMouseDownCapture === "function" ? handleMouseDownCapture : undefined}
|
|
302
|
+
onPointerDown={handlesDrag ? handlePointerDown : undefined}
|
|
207
303
|
onMouseDown={handleMouseDown}
|
|
208
304
|
onMouseOver={handleMouseOver}
|
|
209
305
|
onMouseMove={(event) => scheduleFrameMouseHandler(event, "move")}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
let shield: HTMLElement | null = null;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* While a press drags, one transparent sheet covers the window: it keeps the
|
|
5
|
+
* grabbing cursor wherever the pointer goes and takes the hover, so nothing
|
|
6
|
+
* under the pointer restyles while a pane, divider or chart moves across it.
|
|
7
|
+
* It is a single element on purpose: a class on body matched by descendant
|
|
8
|
+
* rules restyled every element with a role (thousands on a busy layout) in
|
|
9
|
+
* the frame the drag began, and again when it ended.
|
|
10
|
+
*/
|
|
11
|
+
export function showDragShield(): void {
|
|
12
|
+
document.body.classList.add("gloom-dragging");
|
|
13
|
+
if (shield?.isConnected) return;
|
|
14
|
+
shield = document.createElement("div");
|
|
15
|
+
shield.setAttribute("data-gloom-role", "drag-shield");
|
|
16
|
+
shield.setAttribute("aria-hidden", "true");
|
|
17
|
+
document.body.appendChild(shield);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function hideDragShield(): void {
|
|
21
|
+
document.body.classList.remove("gloom-dragging");
|
|
22
|
+
shield?.remove();
|
|
23
|
+
shield = null;
|
|
24
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import type { LiveBoxFrame } from "../../../ui/host";
|
|
3
|
+
import { WEB_CELL_HEIGHT, WEB_CELL_WIDTH } from "../../../theme/font-scale";
|
|
4
|
+
import { commonStyle } from "./style";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Blink lays the whole page out into compositor layers again whenever a
|
|
8
|
+
* style transform changes, even on a `will-change` layer: 4 to 9 ms a frame
|
|
9
|
+
* on a ten-pane desktop. The same transform held by a running animation's
|
|
10
|
+
* keyframes only updates the compositor (under 1 ms), at the same latency.
|
|
11
|
+
* WebKit has no such pass, and there an animation per frame costs more than
|
|
12
|
+
* the plain style, so it keeps the style.
|
|
13
|
+
*/
|
|
14
|
+
const ANIMATION_CARRIES_SLIDE = typeof navigator !== "undefined"
|
|
15
|
+
&& /Chrome\//.test(navigator.userAgent)
|
|
16
|
+
&& typeof Element !== "undefined"
|
|
17
|
+
&& typeof Element.prototype.animate === "function";
|
|
18
|
+
|
|
19
|
+
const slides = new WeakMap<HTMLElement, Animation>();
|
|
20
|
+
|
|
21
|
+
function renderedValue(value: unknown): string {
|
|
22
|
+
return typeof value === "string" ? value : "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function slide(element: HTMLElement, transform: string): void {
|
|
26
|
+
if (!ANIMATION_CARRIES_SLIDE) {
|
|
27
|
+
element.style.transform = transform;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const keyframes = [{ transform }, { transform }];
|
|
31
|
+
const running = slides.get(element);
|
|
32
|
+
if (running) (running.effect as KeyframeEffect).setKeyframes(keyframes);
|
|
33
|
+
else slides.set(element, element.animate(keyframes, { duration: 1e9, fill: "both" }));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Draws a box where a drag has it this frame by restyling its element,
|
|
38
|
+
* outside React: an offset on its own compositor layer, with no layout or
|
|
39
|
+
* repaint. `null` hands the properties back to what React last rendered.
|
|
40
|
+
*/
|
|
41
|
+
export function applyLiveFrame(element: HTMLElement, frame: LiveBoxFrame | null, props: Record<string, unknown>): void {
|
|
42
|
+
const style = element.style;
|
|
43
|
+
if (!frame) {
|
|
44
|
+
const rendered: CSSProperties = { ...commonStyle(props), ...(props.style as CSSProperties | undefined) };
|
|
45
|
+
slides.get(element)?.cancel();
|
|
46
|
+
slides.delete(element);
|
|
47
|
+
style.transform = renderedValue(rendered.transform);
|
|
48
|
+
style.willChange = renderedValue(rendered.willChange);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Promoted once, before the first frame of the drag.
|
|
52
|
+
if (style.willChange !== "transform") style.willChange = "transform";
|
|
53
|
+
slide(element, `translate3d(${frame.dx * WEB_CELL_WIDTH}px, ${frame.dy * WEB_CELL_HEIGHT}px, 0)`);
|
|
54
|
+
}
|
|
@@ -54,7 +54,12 @@ function resolveScroll(event: MouseLikeEvent) {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
/**
|
|
58
|
+
* `ahead` is where the browser predicts the pointer will be a moment later
|
|
59
|
+
* (`getPredictedEvents`), for drawing what follows it; hit tests and releases
|
|
60
|
+
* keep the real point.
|
|
61
|
+
*/
|
|
62
|
+
export function cellMouseEvent(event: MouseLikeEvent, type?: string, ahead?: { clientX: number; clientY: number } | null) {
|
|
58
63
|
const preciseX = event.clientX / WEB_CELL_WIDTH;
|
|
59
64
|
const preciseY = event.clientY / WEB_CELL_HEIGHT;
|
|
60
65
|
return {
|
|
@@ -63,6 +68,8 @@ export function cellMouseEvent(event: MouseLikeEvent, type?: string) {
|
|
|
63
68
|
y: Math.max(0, Math.floor(preciseY)),
|
|
64
69
|
preciseX: Math.max(0, preciseX),
|
|
65
70
|
preciseY: Math.max(0, preciseY),
|
|
71
|
+
predictedX: ahead ? Math.max(0, ahead.clientX / WEB_CELL_WIDTH) : undefined,
|
|
72
|
+
predictedY: ahead ? Math.max(0, ahead.clientY / WEB_CELL_HEIGHT) : undefined,
|
|
66
73
|
pixelX: event.clientX,
|
|
67
74
|
pixelY: event.clientY,
|
|
68
75
|
button: event.button,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "react";
|
|
14
14
|
import type { ScrollBoxRenderable } from "../../../ui/host";
|
|
15
15
|
import { WEB_CELL_HEIGHT, WEB_CELL_WIDTH } from "../../../theme/font-scale";
|
|
16
|
+
import { isHeaderStripScrollBox, scrollBoxScrollsVertically } from "../../../ui/scroll-box-axis";
|
|
16
17
|
import { callMouseHandler } from "./mouse";
|
|
17
18
|
import { cleanDomProps, commonStyle } from "./style";
|
|
18
19
|
import { useScrollbarActivity } from "../scrollbar-activity";
|
|
@@ -48,15 +49,15 @@ function contentInsetStyle(props: Record<string, unknown>, contentOptions: unkno
|
|
|
48
49
|
export const WebScrollBox = forwardRef<ScrollBoxRenderable, Record<string, unknown> & { children?: ReactNode }>(
|
|
49
50
|
function WebScrollBox({ children, contentOptions, ...props }, ref) {
|
|
50
51
|
const elementRef = useRef<HTMLDivElement | null>(null);
|
|
51
|
-
const isHeaderLikeScroller = props
|
|
52
|
+
const isHeaderLikeScroller = isHeaderStripScrollBox(props);
|
|
52
53
|
/**
|
|
53
54
|
* OpenTUI's ScrollBox scrolls vertically unless told otherwise, so this one
|
|
54
55
|
* has to as well: a pane that renders a ScrollBox and nothing more expects
|
|
55
|
-
* the wheel
|
|
56
|
-
*
|
|
57
|
-
*
|
|
56
|
+
* the wheel and the arrow keys to work (the pane keyboard scroll registry
|
|
57
|
+
* reads the same rule). The one-row horizontal strips (tab bars, table
|
|
58
|
+
* headers, chart legends) are the exception and scroll on their axis.
|
|
58
59
|
*/
|
|
59
|
-
const scrollYEnabled = props
|
|
60
|
+
const scrollYEnabled = scrollBoxScrollsVertically(props);
|
|
60
61
|
const [horizontalScrollBarVisible, setHorizontalScrollBarVisible] = useState(
|
|
61
62
|
() => props.scrollX === true && !isHeaderLikeScroller,
|
|
62
63
|
);
|
|
@@ -13,6 +13,7 @@ import { WebMenu } from "../desktop/menu";
|
|
|
13
13
|
import { WebPopover } from "../desktop/popover";
|
|
14
14
|
import { useHorizontalOverflow } from "./overflow-fade";
|
|
15
15
|
import { useTopSurfaceColor } from "./top-surface";
|
|
16
|
+
import { hideDragShield, showDragShield } from "./drag-shield";
|
|
16
17
|
|
|
17
18
|
type CssVars = CSSProperties & Record<`--${string}`, string>;
|
|
18
19
|
|
|
@@ -106,7 +107,7 @@ export function WebTabs({
|
|
|
106
107
|
const cleanup = () => {
|
|
107
108
|
document.removeEventListener("mousemove", handleMove);
|
|
108
109
|
document.removeEventListener("mouseup", handleUp);
|
|
109
|
-
|
|
110
|
+
hideDragShield();
|
|
110
111
|
dragCleanupRef.current = null;
|
|
111
112
|
dragOffsetXRef.current = 0;
|
|
112
113
|
dragSourceWidthRef.current = 0;
|
|
@@ -120,7 +121,7 @@ export function WebTabs({
|
|
|
120
121
|
moveEvent.preventDefault();
|
|
121
122
|
dragOffsetXRef.current = offsetX;
|
|
122
123
|
sourceElement.style.transform = `translateX(${offsetX}px) scale(1.03)`;
|
|
123
|
-
|
|
124
|
+
showDragShield();
|
|
124
125
|
setDragTargetValue(resolveTarget(sourceBounds.left + offsetX + sourceBounds.width / 2));
|
|
125
126
|
};
|
|
126
127
|
const handleUp = (upEvent: MouseEvent) => {
|
|
@@ -159,10 +159,21 @@ button, [data-gloom-interactive="true"] { cursor: pointer; }
|
|
|
159
159
|
[data-gloom-role="pane-restore"] {
|
|
160
160
|
cursor: pointer;
|
|
161
161
|
}
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
/* Up for the length of a drag (see drag-shield.ts): the grabbing cursor
|
|
163
|
+
everywhere, and no hover under the pointer. */
|
|
164
|
+
[data-gloom-role="drag-shield"] {
|
|
165
|
+
position: fixed;
|
|
166
|
+
inset: 0;
|
|
167
|
+
z-index: 2147483647;
|
|
164
168
|
cursor: grabbing;
|
|
165
169
|
}
|
|
170
|
+
/* Surfaces that drag a pane, its size or a split keep the whole pointer
|
|
171
|
+
stream: a touch or pen press on them never starts a pan or zoom. */
|
|
172
|
+
[data-gloom-role="pane-header"][data-drag-surface="pane"],
|
|
173
|
+
[data-gloom-role="resize-handle"],
|
|
174
|
+
[data-gloom-role="dock-divider"] {
|
|
175
|
+
touch-action: none;
|
|
176
|
+
}
|
|
166
177
|
[data-gloom-role="pane-title"] {
|
|
167
178
|
overflow: hidden;
|
|
168
179
|
text-overflow: ellipsis;
|
|
@@ -310,9 +321,6 @@ body.gloom-dragging [data-gloom-role="pane-header"] {
|
|
|
310
321
|
[data-gloom-role="chart-surface"] {
|
|
311
322
|
cursor: crosshair;
|
|
312
323
|
}
|
|
313
|
-
body.gloom-dragging [data-gloom-role="chart-surface"] {
|
|
314
|
-
cursor: grabbing;
|
|
315
|
-
}
|
|
316
324
|
[data-gloom-role="tab-list"] {
|
|
317
325
|
scrollbar-width: none;
|
|
318
326
|
-webkit-overflow-scrolling: touch;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
saveConfig,
|
|
7
7
|
} from "../../../../data/config/store";
|
|
8
8
|
import type { UpdateProgress } from "../../../../updater";
|
|
9
|
+
import { deleteBrokerAccountRecord, saveBrokerAccountRecord } from "../../../../brokers/account-cache";
|
|
9
10
|
import type {
|
|
10
11
|
DesktopBackendRequestResponse,
|
|
11
12
|
DesktopCoreRequest,
|
|
@@ -105,6 +106,12 @@ export async function handleDesktopBackendRequest(
|
|
|
105
106
|
case "session.delete":
|
|
106
107
|
backend.requireServices().persistence.sessions.delete(request.payload.sessionId);
|
|
107
108
|
return null;
|
|
109
|
+
case "resources.set":
|
|
110
|
+
saveBrokerAccountRecord(backend.requireServices().persistence.resources, request.payload.record);
|
|
111
|
+
return null;
|
|
112
|
+
case "resources.delete":
|
|
113
|
+
deleteBrokerAccountRecord(backend.requireServices().persistence.resources, request.payload.key);
|
|
114
|
+
return null;
|
|
108
115
|
default: {
|
|
109
116
|
const exhaustive: never = request;
|
|
110
117
|
throw new Error(`Unknown core backend method: ${String(exhaustive)}`);
|
|
@@ -511,6 +511,8 @@ export class DesktopBackend {
|
|
|
511
511
|
case "config.import":
|
|
512
512
|
case "session.set":
|
|
513
513
|
case "session.delete":
|
|
514
|
+
case "resources.set":
|
|
515
|
+
case "resources.delete":
|
|
514
516
|
return handleDesktopBackendRequest(this, rpc, request);
|
|
515
517
|
default: {
|
|
516
518
|
const exhaustive: never = request;
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
initDataDir,
|
|
9
9
|
} from "../../../../data/config/store";
|
|
10
10
|
import type { AppConfig } from "../../../../types/config";
|
|
11
|
+
import { listPersistedBrokerAccountRecords } from "../../../../brokers/account-cache";
|
|
12
|
+
import type { CachedResourceRecord } from "../../../../data/resource-store";
|
|
11
13
|
import {
|
|
12
14
|
loadDesktopPluginState,
|
|
13
15
|
} from "./plugin-state";
|
|
@@ -54,6 +56,16 @@ export function desktopRendererCapabilityManifests(registry: CapabilityRegistry)
|
|
|
54
56
|
return registry.manifests({ rendererOnly: true, includeDisabled: true });
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
/** The view's resource store lives in memory; it starts from the account snapshots saved here. */
|
|
60
|
+
function loadSavedResources(services: AppServices, config: AppConfig): CachedResourceRecord[] {
|
|
61
|
+
try {
|
|
62
|
+
return listPersistedBrokerAccountRecords(services.persistence.resources, config.brokerInstances);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.warn("saved broker accounts could not be read", error);
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
57
69
|
function buildInitializationPayload(
|
|
58
70
|
backend: DesktopBackend,
|
|
59
71
|
config: AppConfig,
|
|
@@ -66,6 +78,7 @@ function buildInitializationPayload(
|
|
|
66
78
|
desktopSnapshot: backend.workspace?.getSnapshot() ?? null,
|
|
67
79
|
desktopThemePreview: backend.stateBroadcaster.currentThemePreview,
|
|
68
80
|
pluginState: loadDesktopPluginState(services.pluginRegistry),
|
|
81
|
+
savedResources: loadSavedResources(services, config),
|
|
69
82
|
capabilityManifests: desktopRendererCapabilityManifests(services.pluginRegistry.capabilities),
|
|
70
83
|
desktopPlatform: process.platform,
|
|
71
84
|
windowKind: windowTarget.kind,
|
|
@@ -3,6 +3,7 @@ import type { PaneRuntimeState } from "../../../core/state/app/state";
|
|
|
3
3
|
import type { DesktopDockPreviewState, DesktopSharedStateSnapshot, DesktopThemePreviewState } from "../../../types/desktop-window";
|
|
4
4
|
import type { DesktopApplicationMenuCommand } from "../../../types/desktop-menu";
|
|
5
5
|
import type { AppConfig } from "../../../types/config";
|
|
6
|
+
import type { CachedResourceRecord, ResourceCacheKey } from "../../../data/resource-store";
|
|
6
7
|
import type { TickerRecord } from "../../../types/ticker";
|
|
7
8
|
import type { ReleaseInfo, UpdateCheckResult, UpdateProgress } from "../../../updater";
|
|
8
9
|
import type { CapabilityManifest } from "../../../capabilities";
|
|
@@ -18,6 +19,8 @@ export interface ElectrobunBackendInit {
|
|
|
18
19
|
desktopSnapshot: DesktopSharedStateSnapshot | null;
|
|
19
20
|
desktopThemePreview: DesktopThemePreviewState;
|
|
20
21
|
pluginState: Record<string, Record<string, unknown>>;
|
|
22
|
+
/** Saved broker account snapshots, which the view's in-memory store starts from. */
|
|
23
|
+
savedResources?: CachedResourceRecord[];
|
|
21
24
|
capabilityManifests: CapabilityManifest[];
|
|
22
25
|
desktopPlatform: string;
|
|
23
26
|
windowKind: "main" | "detached";
|
|
@@ -226,6 +229,9 @@ interface DesktopBackendRequestMap {
|
|
|
226
229
|
response: null;
|
|
227
230
|
};
|
|
228
231
|
"session.delete": { request: { sessionId: string }; response: null };
|
|
232
|
+
/** Saves or drops a broker account snapshot in the cache database; nothing else is accepted. */
|
|
233
|
+
"resources.set": { request: { record: CachedResourceRecord }; response: null };
|
|
234
|
+
"resources.delete": { request: { key: ResourceCacheKey }; response: null };
|
|
229
235
|
}
|
|
230
236
|
|
|
231
237
|
export type DesktopBackendRequestMethod = keyof DesktopBackendRequestMap;
|
|
@@ -251,7 +257,10 @@ export type DesktopWorkspaceRequest = DesktopBackendRequestFor<Extract<DesktopBa
|
|
|
251
257
|
export type DesktopPluginStateRequest = DesktopBackendRequestFor<Extract<DesktopBackendRequestMethod, `pluginState.${string}`>>;
|
|
252
258
|
export type DesktopHostRequest = DesktopBackendRequestFor<Extract<DesktopBackendRequestMethod, `host.${string}`>>;
|
|
253
259
|
export type DesktopCoreRequest = DesktopBackendRequestFor<
|
|
254
|
-
Extract<
|
|
260
|
+
Extract<
|
|
261
|
+
DesktopBackendRequestMethod,
|
|
262
|
+
`update.${string}` | `ticker.${string}` | `config.${string}` | `session.${string}` | `resources.${string}`
|
|
263
|
+
>
|
|
255
264
|
>;
|
|
256
265
|
|
|
257
266
|
interface BackendRequestPayload {
|
|
@@ -397,6 +397,9 @@ function HydratePayload({
|
|
|
397
397
|
tickers: new Map(payload.tickers.map((ticker) => [ticker.metadata.ticker, ticker])),
|
|
398
398
|
});
|
|
399
399
|
dispatch({ type: "HYDRATE_FINANCIALS", financials: new Map(payload.financials) });
|
|
400
|
+
for (const [instanceId, accounts] of Object.entries(payload.brokerAccounts ?? {})) {
|
|
401
|
+
dispatch({ type: "SET_BROKER_ACCOUNTS", instanceId, accounts });
|
|
402
|
+
}
|
|
400
403
|
dispatch({ type: "SET_INITIALIZED" });
|
|
401
404
|
|
|
402
405
|
return waitForShotReadiness();
|
|
@@ -14,16 +14,6 @@ const contextMenuActionScope = new DesktopContextMenuActionScope(
|
|
|
14
14
|
CONTEXT_MENU_ACTION_TTL_MS,
|
|
15
15
|
);
|
|
16
16
|
|
|
17
|
-
export function startElectrobunWindowDrag(): void {
|
|
18
|
-
window.__electrobunInternalBridge?.postMessage(JSON.stringify([
|
|
19
|
-
JSON.stringify({
|
|
20
|
-
type: "message",
|
|
21
|
-
id: "startWindowMove",
|
|
22
|
-
payload: { id: window.__electrobunWindowId },
|
|
23
|
-
}),
|
|
24
|
-
]));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
17
|
export async function showDesktopContextMenu(items: ContextMenuItem[]): Promise<boolean> {
|
|
28
18
|
if (!NATIVE_CONTEXT_MENU_SUPPORTED) return false;
|
|
29
19
|
contextMenuActionScope.clear();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function postWindowMove(id: "startWindowMove" | "stopWindowMove"): void {
|
|
2
|
+
window.__electrobunInternalBridge?.postMessage(JSON.stringify([
|
|
3
|
+
JSON.stringify({ type: "message", id, payload: { id: window.__electrobunWindowId } }),
|
|
4
|
+
]));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let endWindowMove: (() => void) | null = null;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Moves the window with the pointer until the button comes up. Electrobun
|
|
11
|
+
* stops the move when its native monitor sees the release, but only if the
|
|
12
|
+
* start reached it first: the start goes through the Bun process, and a quick
|
|
13
|
+
* click releases before it lands, leaving the window following the bare
|
|
14
|
+
* pointer until the next click. The release is sent from here too, as
|
|
15
|
+
* Electrobun's own drag regions do; it always arrives after the start.
|
|
16
|
+
*/
|
|
17
|
+
export function startElectrobunWindowDrag(): void {
|
|
18
|
+
// A press whose release never reached the page (it ended outside the window) is over.
|
|
19
|
+
endWindowMove?.();
|
|
20
|
+
postWindowMove("startWindowMove");
|
|
21
|
+
const end = () => {
|
|
22
|
+
window.removeEventListener("mouseup", end, true);
|
|
23
|
+
if (endWindowMove === end) endWindowMove = null;
|
|
24
|
+
postWindowMove("stopWindowMove");
|
|
25
|
+
};
|
|
26
|
+
endWindowMove = end;
|
|
27
|
+
window.addEventListener("mouseup", end, true);
|
|
28
|
+
}
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
SESSION_SAVE_DEBOUNCE_MS,
|
|
5
5
|
} from "../../../../state/persist-scheduler";
|
|
6
6
|
import { backendRequest, getElectrobunBackendInitSnapshot } from "../backend-rpc";
|
|
7
|
-
import {
|
|
7
|
+
import { WriteThroughResourceStore } from "../../../../data/memory-resource-store";
|
|
8
|
+
import { isBrokerAccountSnapshotKey } from "../../../../brokers/account-cache";
|
|
8
9
|
|
|
9
10
|
class RemoteSessionStore {
|
|
10
11
|
private snapshot = getElectrobunBackendInitSnapshot()?.sessionSnapshot ?? null;
|
|
@@ -123,9 +124,21 @@ class RemotePluginStateStore {
|
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Caches stay in the view's memory, except broker account snapshots: the Bun
|
|
129
|
+
* process saves those in the cache database, as the terminal does, so a
|
|
130
|
+
* broker portfolio opens with its last known equity and cash.
|
|
131
|
+
*/
|
|
132
|
+
function createViewResourceStore(): WriteThroughResourceStore {
|
|
133
|
+
return new WriteThroughResourceStore(getElectrobunBackendInitSnapshot()?.savedResources ?? [], {
|
|
134
|
+
set: (record) => void backendRequest("resources.set", { record }).catch(() => {}),
|
|
135
|
+
delete: (key) => void backendRequest("resources.delete", { key }).catch(() => {}),
|
|
136
|
+
}, isBrokerAccountSnapshotKey);
|
|
137
|
+
}
|
|
138
|
+
|
|
126
139
|
export class RemotePersistence {
|
|
127
140
|
readonly tickers = {};
|
|
128
|
-
readonly resources =
|
|
141
|
+
readonly resources = createViewResourceStore();
|
|
129
142
|
readonly pluginState = new RemotePluginStateStore(getElectrobunBackendInitSnapshot()?.pluginState ?? {});
|
|
130
143
|
readonly sessions = new RemoteSessionStore();
|
|
131
144
|
|
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
import type { RendererHost, UiHost } from "../../../ui/host";
|
|
3
3
|
import { flushUsageCounts } from "../../../telemetry/usage-counts";
|
|
4
4
|
import { backendRequest } from "./backend-rpc";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
showDesktopContextMenu,
|
|
8
|
-
startElectrobunWindowDrag,
|
|
9
|
-
} from "./host/native";
|
|
5
|
+
import { NATIVE_CONTEXT_MENU_SUPPORTED, showDesktopContextMenu } from "./host/native";
|
|
6
|
+
import { startElectrobunWindowDrag } from "./host/window-move";
|
|
10
7
|
import { createDomUiHost } from "../../dom/dom-ui-host";
|
|
11
8
|
|
|
12
9
|
export function createWebUiHost(desktopPlatform?: string): UiHost {
|