@aperture-browser/session-react 0.0.0-init.0
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/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/headless-CKFacFX3.js +2135 -0
- package/dist/headless-CKFacFX3.js.map +1 -0
- package/dist/headless.d.ts +313 -0
- package/dist/headless.js +2 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +2212 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +70 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2212 @@
|
|
|
1
|
+
import { a as makeApertureRuntime, c as collaborationPaintLifetimeMs, d as RuntimeProviderMissingError, f as useBaseUrl, g as useRuntime, h as useRunApi, i as ApertureProvider, l as useLiveSession, m as useFork, n as parseShareToken, o as useBrowserControl, p as useEffectCallback, r as useSharedSession, s as SessionViewport, t as LiveSessionError, u as RuntimeProvider } from "./headless-CKFacFX3.js";
|
|
2
|
+
import { Fragment, createContext, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import { VIEWPORT_DEVICE_SCALE_FACTORS, VIEWPORT_PRESETS, createViewportPreset, formatViewportScale } from "@aperture-browser/live-session";
|
|
5
|
+
import * as Data from "effect/Data";
|
|
6
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { ArrowLeft, ArrowRight, CheckIcon, ChevronRightIcon, Circle, CircleCheckIcon, Copy, Download, Gauge, Globe2, Info, InfoIcon, Link2Off, Loader2, Loader2Icon, Lock, LockOpen, Maximize2, Monitor, MoreVertical, MousePointer2, OctagonXIcon, PanelBottom, PanelRight, Pencil, Plus, Radio, RefreshCw, RotateCcw, Share2, Square, TriangleAlertIcon, Unplug, Wrench, X } from "lucide-react";
|
|
8
|
+
import { cva } from "class-variance-authority";
|
|
9
|
+
import { clsx } from "clsx";
|
|
10
|
+
import { twMerge } from "tailwind-merge";
|
|
11
|
+
import { useTheme } from "next-themes";
|
|
12
|
+
import { Toaster, toast } from "sonner";
|
|
13
|
+
import { Tooltip } from "@base-ui/react/tooltip";
|
|
14
|
+
import * as ResizablePrimitive from "react-resizable-panels";
|
|
15
|
+
import { usePanelRef } from "react-resizable-panels";
|
|
16
|
+
import * as Schedule from "effect/Schedule";
|
|
17
|
+
import { Button } from "@base-ui/react/button";
|
|
18
|
+
import { ContextMenu } from "@base-ui/react/context-menu";
|
|
19
|
+
import { Input } from "@base-ui/react/input";
|
|
20
|
+
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|
21
|
+
import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
22
|
+
import { ScrollArea } from "@base-ui/react/scroll-area";
|
|
23
|
+
import "@base-ui/react/separator";
|
|
24
|
+
import { Menu } from "@base-ui/react/menu";
|
|
25
|
+
import { Avatar } from "@base-ui/react/avatar";
|
|
26
|
+
import { Popover } from "@base-ui/react/popover";
|
|
27
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
28
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
29
|
+
//#region ../ui/src/utils.ts
|
|
30
|
+
function cn(...inputs) {
|
|
31
|
+
return twMerge(clsx(inputs));
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region ../ui/src/components/empty.tsx
|
|
35
|
+
function Empty({ className, ...props }) {
|
|
36
|
+
return /* @__PURE__ */ jsx("div", {
|
|
37
|
+
"data-slot": "empty",
|
|
38
|
+
className: cn("flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance", className),
|
|
39
|
+
...props
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function EmptyHeader({ className, ...props }) {
|
|
43
|
+
return /* @__PURE__ */ jsx("div", {
|
|
44
|
+
"data-slot": "empty-header",
|
|
45
|
+
className: cn("flex max-w-sm flex-col items-center gap-2", className),
|
|
46
|
+
...props
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const emptyMediaVariants = cva("mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0", {
|
|
50
|
+
variants: { variant: {
|
|
51
|
+
default: "bg-transparent",
|
|
52
|
+
icon: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4"
|
|
53
|
+
} },
|
|
54
|
+
defaultVariants: { variant: "default" }
|
|
55
|
+
});
|
|
56
|
+
function EmptyMedia({ className, variant = "default", ...props }) {
|
|
57
|
+
return /* @__PURE__ */ jsx("div", {
|
|
58
|
+
"data-slot": "empty-icon",
|
|
59
|
+
"data-variant": variant,
|
|
60
|
+
className: cn(emptyMediaVariants({
|
|
61
|
+
variant,
|
|
62
|
+
className
|
|
63
|
+
})),
|
|
64
|
+
...props
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function EmptyTitle({ className, ...props }) {
|
|
68
|
+
return /* @__PURE__ */ jsx("div", {
|
|
69
|
+
"data-slot": "empty-title",
|
|
70
|
+
className: cn("text-sm font-medium tracking-tight", className),
|
|
71
|
+
...props
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function EmptyDescription({ className, ...props }) {
|
|
75
|
+
return /* @__PURE__ */ jsx("div", {
|
|
76
|
+
"data-slot": "empty-description",
|
|
77
|
+
className: cn("text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", className),
|
|
78
|
+
...props
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region ../ui/src/components/sonner.tsx
|
|
83
|
+
const Toaster$1 = ({ ...props }) => {
|
|
84
|
+
const { theme = "system" } = useTheme();
|
|
85
|
+
return /* @__PURE__ */ jsx(Toaster, {
|
|
86
|
+
theme,
|
|
87
|
+
className: "toaster group",
|
|
88
|
+
icons: {
|
|
89
|
+
success: /* @__PURE__ */ jsx(CircleCheckIcon, { className: "size-4" }),
|
|
90
|
+
info: /* @__PURE__ */ jsx(InfoIcon, { className: "size-4" }),
|
|
91
|
+
warning: /* @__PURE__ */ jsx(TriangleAlertIcon, { className: "size-4" }),
|
|
92
|
+
error: /* @__PURE__ */ jsx(OctagonXIcon, { className: "size-4" }),
|
|
93
|
+
loading: /* @__PURE__ */ jsx(Loader2Icon, { className: "size-4 animate-spin" })
|
|
94
|
+
},
|
|
95
|
+
style: {
|
|
96
|
+
"--normal-bg": "var(--popover)",
|
|
97
|
+
"--normal-text": "var(--popover-foreground)",
|
|
98
|
+
"--normal-border": "var(--border)",
|
|
99
|
+
"--border-radius": "var(--radius)"
|
|
100
|
+
},
|
|
101
|
+
toastOptions: { classNames: { toast: "cn-toast" } },
|
|
102
|
+
...props
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region ../ui/src/portal.tsx
|
|
107
|
+
const PortalContainerContext = createContext(null);
|
|
108
|
+
function PortalContainerProvider({ container, children }) {
|
|
109
|
+
return /* @__PURE__ */ jsx(PortalContainerContext.Provider, {
|
|
110
|
+
value: container,
|
|
111
|
+
children
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function usePortalContainer() {
|
|
115
|
+
return useContext(PortalContainerContext) ?? void 0;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region ../ui/src/components/tooltip.tsx
|
|
119
|
+
function TooltipProvider({ delay = 0, ...props }) {
|
|
120
|
+
return /* @__PURE__ */ jsx(Tooltip.Provider, {
|
|
121
|
+
"data-slot": "tooltip-provider",
|
|
122
|
+
delay,
|
|
123
|
+
...props
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function Tooltip$1({ ...props }) {
|
|
127
|
+
return /* @__PURE__ */ jsx(Tooltip.Root, {
|
|
128
|
+
"data-slot": "tooltip",
|
|
129
|
+
...props
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function TooltipTrigger({ ...props }) {
|
|
133
|
+
return /* @__PURE__ */ jsx(Tooltip.Trigger, {
|
|
134
|
+
"data-slot": "tooltip-trigger",
|
|
135
|
+
...props
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function TooltipContent({ className, side = "top", sideOffset = 4, align = "center", alignOffset = 0, children, ...props }) {
|
|
139
|
+
return /* @__PURE__ */ jsx(Tooltip.Portal, {
|
|
140
|
+
container: usePortalContainer(),
|
|
141
|
+
children: /* @__PURE__ */ jsx(Tooltip.Positioner, {
|
|
142
|
+
align,
|
|
143
|
+
alignOffset,
|
|
144
|
+
side,
|
|
145
|
+
sideOffset,
|
|
146
|
+
className: "isolate z-50",
|
|
147
|
+
children: /* @__PURE__ */ jsxs(Tooltip.Popup, {
|
|
148
|
+
"data-slot": "tooltip-content",
|
|
149
|
+
className: cn("z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
|
|
150
|
+
...props,
|
|
151
|
+
children: [children, /* @__PURE__ */ jsx(Tooltip.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" })]
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/features.ts
|
|
158
|
+
function resolveSessionFeatures(features) {
|
|
159
|
+
return {
|
|
160
|
+
tabs: true,
|
|
161
|
+
navigation: true,
|
|
162
|
+
addressBar: true,
|
|
163
|
+
presence: true,
|
|
164
|
+
drawing: true,
|
|
165
|
+
devTools: true,
|
|
166
|
+
menus: true,
|
|
167
|
+
statusBadge: true,
|
|
168
|
+
...features
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region ../ui/src/components/button.tsx
|
|
173
|
+
const buttonVariants = cva("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm leading-none font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
|
|
174
|
+
variants: {
|
|
175
|
+
variant: {
|
|
176
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
177
|
+
outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
178
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
179
|
+
ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
180
|
+
destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
181
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
182
|
+
},
|
|
183
|
+
size: {
|
|
184
|
+
default: "h-8 gap-1.5 px-2.5 pt-px has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
185
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 pt-px text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
186
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 pt-px text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
187
|
+
lg: "h-9 gap-1.5 px-2.5 pt-px has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
188
|
+
icon: "size-8",
|
|
189
|
+
"icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
190
|
+
"icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
191
|
+
"icon-lg": "size-9"
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
defaultVariants: {
|
|
195
|
+
variant: "default",
|
|
196
|
+
size: "default"
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
function Button$1({ className, variant = "default", size = "default", ...props }) {
|
|
200
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
201
|
+
"data-slot": "button",
|
|
202
|
+
className: cn(buttonVariants({
|
|
203
|
+
variant,
|
|
204
|
+
size,
|
|
205
|
+
className
|
|
206
|
+
})),
|
|
207
|
+
...props
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region ../ui/src/components/context-menu.tsx
|
|
212
|
+
function ContextMenu$1({ ...props }) {
|
|
213
|
+
return /* @__PURE__ */ jsx(ContextMenu.Root, {
|
|
214
|
+
"data-slot": "context-menu",
|
|
215
|
+
...props
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function ContextMenuTrigger({ className, ...props }) {
|
|
219
|
+
return /* @__PURE__ */ jsx(ContextMenu.Trigger, {
|
|
220
|
+
"data-slot": "context-menu-trigger",
|
|
221
|
+
className: cn("select-none", className),
|
|
222
|
+
...props
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function ContextMenuContent({ className, align = "start", alignOffset = 4, side = "right", sideOffset = 0, ...props }) {
|
|
226
|
+
return /* @__PURE__ */ jsx(ContextMenu.Portal, {
|
|
227
|
+
container: usePortalContainer(),
|
|
228
|
+
children: /* @__PURE__ */ jsx(ContextMenu.Positioner, {
|
|
229
|
+
className: "isolate z-50 outline-none",
|
|
230
|
+
align,
|
|
231
|
+
alignOffset,
|
|
232
|
+
side,
|
|
233
|
+
sideOffset,
|
|
234
|
+
children: /* @__PURE__ */ jsx(ContextMenu.Popup, {
|
|
235
|
+
"data-slot": "context-menu-content",
|
|
236
|
+
className: cn("z-50 max-h-(--available-height) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
|
|
237
|
+
...props
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function ContextMenuGroup({ ...props }) {
|
|
243
|
+
return /* @__PURE__ */ jsx(ContextMenu.Group, {
|
|
244
|
+
"data-slot": "context-menu-group",
|
|
245
|
+
...props
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function ContextMenuLabel({ className, inset, ...props }) {
|
|
249
|
+
return /* @__PURE__ */ jsx(ContextMenu.GroupLabel, {
|
|
250
|
+
"data-slot": "context-menu-label",
|
|
251
|
+
"data-inset": inset,
|
|
252
|
+
className: cn("px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7", className),
|
|
253
|
+
...props
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function ContextMenuItem({ className, inset, variant = "default", ...props }) {
|
|
257
|
+
return /* @__PURE__ */ jsx(ContextMenu.Item, {
|
|
258
|
+
"data-slot": "context-menu-item",
|
|
259
|
+
"data-inset": inset,
|
|
260
|
+
"data-variant": variant,
|
|
261
|
+
className: cn("group/context-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 focus:*:[svg]:text-accent-foreground data-[variant=destructive]:*:[svg]:text-destructive", className),
|
|
262
|
+
...props
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function ContextMenuRadioGroup({ ...props }) {
|
|
266
|
+
return /* @__PURE__ */ jsx(ContextMenu.RadioGroup, {
|
|
267
|
+
"data-slot": "context-menu-radio-group",
|
|
268
|
+
...props
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
function ContextMenuRadioItem({ className, children, inset, ...props }) {
|
|
272
|
+
return /* @__PURE__ */ jsxs(ContextMenu.RadioItem, {
|
|
273
|
+
"data-slot": "context-menu-radio-item",
|
|
274
|
+
"data-inset": inset,
|
|
275
|
+
className: cn("relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
|
|
276
|
+
...props,
|
|
277
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
278
|
+
className: "pointer-events-none absolute right-2",
|
|
279
|
+
children: /* @__PURE__ */ jsx(ContextMenu.RadioItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, {}) })
|
|
280
|
+
}), children]
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
function ContextMenuSeparator({ className, ...props }) {
|
|
284
|
+
return /* @__PURE__ */ jsx(ContextMenu.Separator, {
|
|
285
|
+
"data-slot": "context-menu-separator",
|
|
286
|
+
className: cn("-mx-1 my-1 h-px bg-border", className),
|
|
287
|
+
...props
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region ../ui/src/components/input.tsx
|
|
292
|
+
function Input$1({ className, type, ...props }) {
|
|
293
|
+
return /* @__PURE__ */ jsx(Input, {
|
|
294
|
+
type,
|
|
295
|
+
"data-slot": "input",
|
|
296
|
+
className: cn("h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className),
|
|
297
|
+
...props
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region ../ui/src/components/input-group.tsx
|
|
302
|
+
function InputGroup({ className, ...props }) {
|
|
303
|
+
return /* @__PURE__ */ jsx("div", {
|
|
304
|
+
"data-slot": "input-group",
|
|
305
|
+
role: "group",
|
|
306
|
+
className: cn("group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5", className),
|
|
307
|
+
...props
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
cva("flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4", {
|
|
311
|
+
variants: { align: {
|
|
312
|
+
"inline-start": "order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]",
|
|
313
|
+
"inline-end": "order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]",
|
|
314
|
+
"block-start": "order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
|
|
315
|
+
"block-end": "order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2"
|
|
316
|
+
} },
|
|
317
|
+
defaultVariants: { align: "inline-start" }
|
|
318
|
+
});
|
|
319
|
+
cva("flex items-center gap-2 text-sm shadow-none", {
|
|
320
|
+
variants: { size: {
|
|
321
|
+
xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
|
|
322
|
+
sm: "",
|
|
323
|
+
"icon-xs": "size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0",
|
|
324
|
+
"icon-sm": "size-8 p-0 has-[>svg]:p-0"
|
|
325
|
+
} },
|
|
326
|
+
defaultVariants: { size: "xs" }
|
|
327
|
+
});
|
|
328
|
+
function InputGroupInput({ className, ...props }) {
|
|
329
|
+
return /* @__PURE__ */ jsx(Input$1, {
|
|
330
|
+
"data-slot": "input-group-control",
|
|
331
|
+
className: cn("flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent", className),
|
|
332
|
+
...props
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region ../ui/src/components/scroll-area.tsx
|
|
337
|
+
function ScrollArea$1({ className, viewportClassName, scrollbars = "vertical", children, ...props }) {
|
|
338
|
+
const vertical = scrollbars === "vertical" || scrollbars === "both";
|
|
339
|
+
const horizontal = scrollbars === "horizontal" || scrollbars === "both";
|
|
340
|
+
return /* @__PURE__ */ jsxs(ScrollArea.Root, {
|
|
341
|
+
"data-slot": "scroll-area",
|
|
342
|
+
className: cn("relative", className),
|
|
343
|
+
...props,
|
|
344
|
+
children: [
|
|
345
|
+
/* @__PURE__ */ jsx(ScrollArea.Viewport, {
|
|
346
|
+
"data-slot": "scroll-area-viewport",
|
|
347
|
+
style: {
|
|
348
|
+
overflowX: horizontal ? "scroll" : "hidden",
|
|
349
|
+
overflowY: vertical ? "scroll" : "hidden"
|
|
350
|
+
},
|
|
351
|
+
className: cn("size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1", viewportClassName),
|
|
352
|
+
children
|
|
353
|
+
}),
|
|
354
|
+
vertical ? /* @__PURE__ */ jsx(ScrollBar, {}) : null,
|
|
355
|
+
horizontal ? /* @__PURE__ */ jsx(ScrollBar, { orientation: "horizontal" }) : null,
|
|
356
|
+
/* @__PURE__ */ jsx(ScrollArea.Corner, {})
|
|
357
|
+
]
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
function ScrollBar({ className, orientation = "vertical", ...props }) {
|
|
361
|
+
return /* @__PURE__ */ jsx(ScrollArea.Scrollbar, {
|
|
362
|
+
"data-slot": "scroll-area-scrollbar",
|
|
363
|
+
"data-orientation": orientation,
|
|
364
|
+
orientation,
|
|
365
|
+
className: cn("flex touch-none p-px transition-colors select-none hover:bg-muted/50 data-[orientation=horizontal]:h-2.5 data-[orientation=horizontal]:flex-col data-[orientation=horizontal]:border-t data-[orientation=horizontal]:border-t-transparent data-[orientation=vertical]:h-full data-[orientation=vertical]:w-2.5 data-[orientation=vertical]:border-l data-[orientation=vertical]:border-l-transparent", className),
|
|
366
|
+
...props,
|
|
367
|
+
children: /* @__PURE__ */ jsx(ScrollArea.Thumb, {
|
|
368
|
+
"data-slot": "scroll-area-thumb",
|
|
369
|
+
className: "relative flex-1 rounded-full bg-foreground/35 transition-colors hover:bg-foreground/55"
|
|
370
|
+
})
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/clipboard.ts
|
|
375
|
+
/** The browser refused to write to the clipboard. */
|
|
376
|
+
var ClipboardError = class extends Data.TaggedError("ClipboardError") {
|
|
377
|
+
message = "Clipboard write is unavailable";
|
|
378
|
+
};
|
|
379
|
+
const copyText = (value) => Effect.suspend(() => {
|
|
380
|
+
const clipboard = navigator.clipboard;
|
|
381
|
+
if (clipboard?.writeText) return Effect.tryPromise({
|
|
382
|
+
try: () => clipboard.writeText(value),
|
|
383
|
+
catch: (cause) => new ClipboardError({ cause })
|
|
384
|
+
});
|
|
385
|
+
const textArea = document.createElement("textarea");
|
|
386
|
+
textArea.value = value;
|
|
387
|
+
textArea.readOnly = true;
|
|
388
|
+
textArea.style.position = "fixed";
|
|
389
|
+
textArea.style.top = "0";
|
|
390
|
+
textArea.style.left = "-9999px";
|
|
391
|
+
document.body.append(textArea);
|
|
392
|
+
textArea.select();
|
|
393
|
+
const copied = document.execCommand("copy");
|
|
394
|
+
textArea.remove();
|
|
395
|
+
return copied ? Effect.void : Effect.fail(new ClipboardError({}));
|
|
396
|
+
});
|
|
397
|
+
/** Copies `value`, then runs `onCopied`, or shows an error toast if the copy fails. */
|
|
398
|
+
const copyTextWithToast = (value, onCopied = () => {}) => copyText(value).pipe(Effect.andThen(Effect.sync(onCopied)), Effect.catchTag("ClipboardError", (error) => Effect.sync(() => {
|
|
399
|
+
console.warn("Copy failed", error);
|
|
400
|
+
toast.error("Copy failed");
|
|
401
|
+
})));
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region src/components/browser-tab-strip.tsx
|
|
404
|
+
const BROWSER_TAB_DRAG_KIND = "browser-tab";
|
|
405
|
+
function BrowserTabStrip({ targets, activeTargetId, recordingTargetIds, devToolsTargetIds, disabled, mutationDisabled, onActivate, onCreate, onDuplicate, onClose, onReload, onReorder }) {
|
|
406
|
+
if (targets.length === 0) return /* @__PURE__ */ jsxs("div", {
|
|
407
|
+
className: "flex h-8 min-w-0 flex-1 items-center gap-2 px-2 text-xs text-muted-foreground",
|
|
408
|
+
children: [/* @__PURE__ */ jsx("span", { children: "No tabs" }), /* @__PURE__ */ jsx(NewTabButton, {
|
|
409
|
+
disabled: disabled || mutationDisabled,
|
|
410
|
+
onCreate
|
|
411
|
+
})]
|
|
412
|
+
});
|
|
413
|
+
return /* @__PURE__ */ jsx(ScrollArea$1, {
|
|
414
|
+
scrollbars: "horizontal",
|
|
415
|
+
className: "h-8 min-w-0 flex-1",
|
|
416
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
417
|
+
className: "flex min-w-max items-end gap-0.5 px-1 pt-1",
|
|
418
|
+
children: [targets.map((target, index) => {
|
|
419
|
+
return /* @__PURE__ */ jsx(BrowserTab, {
|
|
420
|
+
target,
|
|
421
|
+
active: target.id === activeTargetId,
|
|
422
|
+
recording: recordingTargetIds.has(target.id),
|
|
423
|
+
devToolsOpen: devToolsTargetIds.has(target.id),
|
|
424
|
+
disabled,
|
|
425
|
+
mutationDisabled,
|
|
426
|
+
onActivate,
|
|
427
|
+
onDuplicate,
|
|
428
|
+
onClose,
|
|
429
|
+
onReload,
|
|
430
|
+
onReorder,
|
|
431
|
+
closeOtherTargetIds: targets.filter((current) => current.id !== target.id).map((current) => current.id),
|
|
432
|
+
closeRightTargetIds: targets.slice(index + 1).map((current) => current.id)
|
|
433
|
+
}, target.id);
|
|
434
|
+
}), /* @__PURE__ */ jsx(NewTabButton, {
|
|
435
|
+
disabled: disabled || mutationDisabled,
|
|
436
|
+
onCreate
|
|
437
|
+
})]
|
|
438
|
+
})
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
function BrowserTab({ target, active, recording, devToolsOpen, disabled, mutationDisabled, onActivate, onDuplicate, onClose, onReload, onReorder, closeOtherTargetIds, closeRightTargetIds }) {
|
|
442
|
+
const tabRef = useRef(null);
|
|
443
|
+
const [dragging, setDragging] = useState(false);
|
|
444
|
+
const [dropPlacement, setDropPlacement] = useState(null);
|
|
445
|
+
const label = simplifyUrl(target.url);
|
|
446
|
+
const copyUrl = useEffectCallback((url) => copyTextWithToast(url), []);
|
|
447
|
+
useEffect(() => {
|
|
448
|
+
const element = tabRef.current;
|
|
449
|
+
if (!element) return;
|
|
450
|
+
return combine(draggable({
|
|
451
|
+
element,
|
|
452
|
+
getInitialData: () => ({
|
|
453
|
+
kind: BROWSER_TAB_DRAG_KIND,
|
|
454
|
+
targetId: target.id
|
|
455
|
+
}),
|
|
456
|
+
onDragStart: () => setDragging(true),
|
|
457
|
+
onDrop: () => setDragging(false)
|
|
458
|
+
}), dropTargetForElements({
|
|
459
|
+
element,
|
|
460
|
+
canDrop: ({ source }) => {
|
|
461
|
+
return isBrowserTabDragData(source.data) && source.data.targetId !== target.id;
|
|
462
|
+
},
|
|
463
|
+
getData: () => ({
|
|
464
|
+
kind: BROWSER_TAB_DRAG_KIND,
|
|
465
|
+
targetId: target.id
|
|
466
|
+
}),
|
|
467
|
+
onDrag: ({ location, self }) => {
|
|
468
|
+
setDropPlacement(dropPlacementFromClientX(self.element, location.current.input.clientX));
|
|
469
|
+
},
|
|
470
|
+
onDragEnter: ({ location, self }) => {
|
|
471
|
+
setDropPlacement(dropPlacementFromClientX(self.element, location.current.input.clientX));
|
|
472
|
+
},
|
|
473
|
+
onDragLeave: () => setDropPlacement(null),
|
|
474
|
+
onDrop: ({ source, self, location }) => {
|
|
475
|
+
setDropPlacement(null);
|
|
476
|
+
if (!isBrowserTabDragData(source.data)) return;
|
|
477
|
+
onReorder(source.data.targetId, target.id, dropPlacementFromClientX(self.element, location.current.input.clientX));
|
|
478
|
+
}
|
|
479
|
+
}));
|
|
480
|
+
}, [onReorder, target.id]);
|
|
481
|
+
return /* @__PURE__ */ jsxs(ContextMenu$1, { children: [/* @__PURE__ */ jsxs(ContextMenuTrigger, {
|
|
482
|
+
render: /* @__PURE__ */ jsx("div", {
|
|
483
|
+
ref: tabRef,
|
|
484
|
+
"data-browser-tab": true,
|
|
485
|
+
className: cn("group relative flex h-7 w-52 max-w-[38vw] min-w-28 cursor-grab select-none items-center gap-1.5 rounded-t-lg border border-b-0 px-2 text-left text-xs transition-[background-color,border-color,color,opacity] active:cursor-grabbing", active ? "border-border bg-background text-foreground" : "border-transparent bg-muted/55 text-muted-foreground hover:bg-muted", dragging && "opacity-60"),
|
|
486
|
+
title: target.url || "about:blank",
|
|
487
|
+
onMouseDown: (event) => {
|
|
488
|
+
if (event.button === 1) event.preventDefault();
|
|
489
|
+
},
|
|
490
|
+
onAuxClick: (event) => {
|
|
491
|
+
if (event.button === 1 && !disabled && !mutationDisabled) {
|
|
492
|
+
event.preventDefault();
|
|
493
|
+
onClose(target.id);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}),
|
|
497
|
+
children: [
|
|
498
|
+
/* @__PURE__ */ jsx("span", { className: cn("pointer-events-none absolute inset-y-1 left-0 w-0.5 rounded-full bg-primary opacity-0", dropPlacement === "before" && "opacity-100") }),
|
|
499
|
+
/* @__PURE__ */ jsx("span", { className: cn("pointer-events-none absolute inset-y-1 right-0 w-0.5 rounded-full bg-primary opacity-0", dropPlacement === "after" && "opacity-100") }),
|
|
500
|
+
/* @__PURE__ */ jsxs("button", {
|
|
501
|
+
type: "button",
|
|
502
|
+
"aria-current": active ? "page" : void 0,
|
|
503
|
+
disabled,
|
|
504
|
+
className: "flex min-w-0 flex-1 items-center gap-1.5 text-left",
|
|
505
|
+
onClick: () => onActivate(target.id),
|
|
506
|
+
children: [
|
|
507
|
+
/* @__PURE__ */ jsx(TabFavicon, { url: target.url }),
|
|
508
|
+
devToolsOpen ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Wrench, {
|
|
509
|
+
"aria-hidden": true,
|
|
510
|
+
className: "size-3.5 shrink-0 text-muted-foreground"
|
|
511
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
512
|
+
className: "sr-only",
|
|
513
|
+
children: "DevTools open"
|
|
514
|
+
})] }) : null,
|
|
515
|
+
recording ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
516
|
+
"aria-hidden": true,
|
|
517
|
+
className: "size-2 shrink-0 rounded-full bg-destructive"
|
|
518
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
519
|
+
className: "sr-only",
|
|
520
|
+
children: "Recording"
|
|
521
|
+
})] }) : null,
|
|
522
|
+
/* @__PURE__ */ jsx("span", {
|
|
523
|
+
className: "min-w-0 truncate font-mono",
|
|
524
|
+
children: label
|
|
525
|
+
})
|
|
526
|
+
]
|
|
527
|
+
}),
|
|
528
|
+
/* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
529
|
+
render: /* @__PURE__ */ jsx(Button$1, {
|
|
530
|
+
type: "button",
|
|
531
|
+
variant: "ghost",
|
|
532
|
+
size: "icon-xs",
|
|
533
|
+
className: "shrink-0 opacity-55 hover:opacity-100 group-hover:opacity-100",
|
|
534
|
+
"aria-label": "Close tab",
|
|
535
|
+
disabled: disabled || mutationDisabled,
|
|
536
|
+
onClick: () => onClose(target.id)
|
|
537
|
+
}),
|
|
538
|
+
children: /* @__PURE__ */ jsx(X, {})
|
|
539
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
540
|
+
side: "bottom",
|
|
541
|
+
children: "Close tab"
|
|
542
|
+
})] })
|
|
543
|
+
]
|
|
544
|
+
}), /* @__PURE__ */ jsxs(ContextMenuContent, {
|
|
545
|
+
className: "min-w-48",
|
|
546
|
+
children: [
|
|
547
|
+
/* @__PURE__ */ jsxs(ContextMenuGroup, { children: [
|
|
548
|
+
/* @__PURE__ */ jsx(ContextMenuItem, {
|
|
549
|
+
disabled: disabled || mutationDisabled,
|
|
550
|
+
onClick: () => onReload(target.id),
|
|
551
|
+
children: "Reload"
|
|
552
|
+
}),
|
|
553
|
+
/* @__PURE__ */ jsx(ContextMenuItem, {
|
|
554
|
+
disabled: disabled || mutationDisabled,
|
|
555
|
+
onClick: () => onDuplicate(target),
|
|
556
|
+
children: "Duplicate"
|
|
557
|
+
}),
|
|
558
|
+
/* @__PURE__ */ jsx(ContextMenuItem, {
|
|
559
|
+
onClick: () => copyUrl(target.url || "about:blank"),
|
|
560
|
+
children: "Copy URL"
|
|
561
|
+
})
|
|
562
|
+
] }),
|
|
563
|
+
/* @__PURE__ */ jsx(ContextMenuSeparator, {}),
|
|
564
|
+
/* @__PURE__ */ jsxs(ContextMenuGroup, { children: [
|
|
565
|
+
/* @__PURE__ */ jsx(ContextMenuItem, {
|
|
566
|
+
disabled: disabled || mutationDisabled,
|
|
567
|
+
onClick: () => onClose(target.id),
|
|
568
|
+
children: "Close"
|
|
569
|
+
}),
|
|
570
|
+
/* @__PURE__ */ jsx(ContextMenuItem, {
|
|
571
|
+
disabled: disabled || mutationDisabled || closeOtherTargetIds.length === 0,
|
|
572
|
+
onClick: () => {
|
|
573
|
+
for (const targetId of closeOtherTargetIds) onClose(targetId);
|
|
574
|
+
},
|
|
575
|
+
children: "Close other tabs"
|
|
576
|
+
}),
|
|
577
|
+
/* @__PURE__ */ jsx(ContextMenuItem, {
|
|
578
|
+
disabled: disabled || mutationDisabled || closeRightTargetIds.length === 0,
|
|
579
|
+
onClick: () => {
|
|
580
|
+
for (const targetId of closeRightTargetIds) onClose(targetId);
|
|
581
|
+
},
|
|
582
|
+
children: "Close tabs to the right"
|
|
583
|
+
})
|
|
584
|
+
] })
|
|
585
|
+
]
|
|
586
|
+
})] });
|
|
587
|
+
}
|
|
588
|
+
function NewTabButton({ disabled, onCreate }) {
|
|
589
|
+
return /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
590
|
+
render: /* @__PURE__ */ jsx(Button$1, {
|
|
591
|
+
type: "button",
|
|
592
|
+
variant: "ghost",
|
|
593
|
+
size: "icon-xs",
|
|
594
|
+
className: "mb-px shrink-0",
|
|
595
|
+
"aria-label": "New tab",
|
|
596
|
+
disabled,
|
|
597
|
+
onClick: onCreate
|
|
598
|
+
}),
|
|
599
|
+
children: /* @__PURE__ */ jsx(Plus, {})
|
|
600
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
601
|
+
side: "bottom",
|
|
602
|
+
children: "New tab"
|
|
603
|
+
})] });
|
|
604
|
+
}
|
|
605
|
+
function TabFavicon({ url }) {
|
|
606
|
+
const [failed, setFailed] = useState(false);
|
|
607
|
+
const faviconUrl = resolveFaviconUrl(url);
|
|
608
|
+
useEffect(() => {
|
|
609
|
+
setFailed(false);
|
|
610
|
+
}, [faviconUrl]);
|
|
611
|
+
if (!faviconUrl || failed) return /* @__PURE__ */ jsx(Globe2, { className: "size-4 shrink-0 text-muted-foreground" });
|
|
612
|
+
return /* @__PURE__ */ jsx("img", {
|
|
613
|
+
src: faviconUrl,
|
|
614
|
+
alt: "",
|
|
615
|
+
className: "size-4 shrink-0",
|
|
616
|
+
onError: () => setFailed(true)
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
function resolveFaviconUrl(url) {
|
|
620
|
+
try {
|
|
621
|
+
const parsed = new URL(url);
|
|
622
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
623
|
+
return `${parsed.origin}/favicon.ico`;
|
|
624
|
+
} catch {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function simplifyUrl(url) {
|
|
629
|
+
if (!url || url === "about:blank") return "about:blank";
|
|
630
|
+
try {
|
|
631
|
+
const parsed = new URL(url);
|
|
632
|
+
return `${parsed.host}${parsed.pathname === "/" ? "" : parsed.pathname}`;
|
|
633
|
+
} catch {
|
|
634
|
+
return url;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function isBrowserTabDragData(data) {
|
|
638
|
+
return data.kind === BROWSER_TAB_DRAG_KIND && typeof data.targetId === "string";
|
|
639
|
+
}
|
|
640
|
+
function dropPlacementFromClientX(element, clientX) {
|
|
641
|
+
const rect = element.getBoundingClientRect();
|
|
642
|
+
return clientX < rect.left + rect.width / 2 ? "before" : "after";
|
|
643
|
+
}
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region ../ui/src/components/label.tsx
|
|
646
|
+
function Label({ className, ...props }) {
|
|
647
|
+
return /* @__PURE__ */ jsx("label", {
|
|
648
|
+
"data-slot": "label",
|
|
649
|
+
className: cn("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", className),
|
|
650
|
+
...props
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
//#endregion
|
|
654
|
+
//#region ../ui/src/components/field.tsx
|
|
655
|
+
function FieldGroup({ className, ...props }) {
|
|
656
|
+
return /* @__PURE__ */ jsx("div", {
|
|
657
|
+
"data-slot": "field-group",
|
|
658
|
+
className: cn("group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4", className),
|
|
659
|
+
...props
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
const fieldVariants = cva("group/field flex w-full gap-2 data-[invalid=true]:text-destructive", {
|
|
663
|
+
variants: { orientation: {
|
|
664
|
+
vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
|
|
665
|
+
horizontal: "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
666
|
+
responsive: "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
|
|
667
|
+
} },
|
|
668
|
+
defaultVariants: { orientation: "vertical" }
|
|
669
|
+
});
|
|
670
|
+
function Field({ className, orientation = "vertical", ...props }) {
|
|
671
|
+
return /* @__PURE__ */ jsx("div", {
|
|
672
|
+
role: "group",
|
|
673
|
+
"data-slot": "field",
|
|
674
|
+
"data-orientation": orientation,
|
|
675
|
+
className: cn(fieldVariants({ orientation }), className),
|
|
676
|
+
...props
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
function FieldLabel({ className, ...props }) {
|
|
680
|
+
return /* @__PURE__ */ jsx(Label, {
|
|
681
|
+
"data-slot": "field-label",
|
|
682
|
+
className: cn("group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10", "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col", className),
|
|
683
|
+
...props
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region ../ui/src/components/dropdown-menu.tsx
|
|
688
|
+
function DropdownMenu({ ...props }) {
|
|
689
|
+
return /* @__PURE__ */ jsx(Menu.Root, {
|
|
690
|
+
"data-slot": "dropdown-menu",
|
|
691
|
+
...props
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
function DropdownMenuTrigger({ ...props }) {
|
|
695
|
+
return /* @__PURE__ */ jsx(Menu.Trigger, {
|
|
696
|
+
"data-slot": "dropdown-menu-trigger",
|
|
697
|
+
...props
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
function DropdownMenuContent({ align = "start", alignOffset = 0, side = "bottom", sideOffset = 4, className, ...props }) {
|
|
701
|
+
return /* @__PURE__ */ jsx(Menu.Portal, {
|
|
702
|
+
container: usePortalContainer(),
|
|
703
|
+
children: /* @__PURE__ */ jsx(Menu.Positioner, {
|
|
704
|
+
className: "isolate z-50 outline-none",
|
|
705
|
+
align,
|
|
706
|
+
alignOffset,
|
|
707
|
+
side,
|
|
708
|
+
sideOffset,
|
|
709
|
+
children: /* @__PURE__ */ jsx(Menu.Popup, {
|
|
710
|
+
"data-slot": "dropdown-menu-content",
|
|
711
|
+
className: cn("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", className),
|
|
712
|
+
...props
|
|
713
|
+
})
|
|
714
|
+
})
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
function DropdownMenuGroup({ ...props }) {
|
|
718
|
+
return /* @__PURE__ */ jsx(Menu.Group, {
|
|
719
|
+
"data-slot": "dropdown-menu-group",
|
|
720
|
+
...props
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
function DropdownMenuLabel({ className, inset, ...props }) {
|
|
724
|
+
return /* @__PURE__ */ jsx("div", {
|
|
725
|
+
"data-slot": "dropdown-menu-label",
|
|
726
|
+
"data-inset": inset,
|
|
727
|
+
className: cn("px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7", className),
|
|
728
|
+
...props
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
|
|
732
|
+
return /* @__PURE__ */ jsx(Menu.Item, {
|
|
733
|
+
"data-slot": "dropdown-menu-item",
|
|
734
|
+
"data-inset": inset,
|
|
735
|
+
"data-variant": variant,
|
|
736
|
+
className: cn("group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive", className),
|
|
737
|
+
...props
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
function DropdownMenuSub({ ...props }) {
|
|
741
|
+
return /* @__PURE__ */ jsx(Menu.SubmenuRoot, {
|
|
742
|
+
"data-slot": "dropdown-menu-sub",
|
|
743
|
+
...props
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
function DropdownMenuSubTrigger({ className, inset, children, ...props }) {
|
|
747
|
+
return /* @__PURE__ */ jsxs(Menu.SubmenuTrigger, {
|
|
748
|
+
"data-slot": "dropdown-menu-sub-trigger",
|
|
749
|
+
"data-inset": inset,
|
|
750
|
+
className: cn("flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
|
|
751
|
+
...props,
|
|
752
|
+
children: [children, /* @__PURE__ */ jsx(ChevronRightIcon, { className: "ml-auto" })]
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
function DropdownMenuSubContent({ align = "start", alignOffset = -3, side = "right", sideOffset = 0, className, ...props }) {
|
|
756
|
+
return /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
757
|
+
"data-slot": "dropdown-menu-sub-content",
|
|
758
|
+
className: cn("w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
|
|
759
|
+
align,
|
|
760
|
+
alignOffset,
|
|
761
|
+
side,
|
|
762
|
+
sideOffset,
|
|
763
|
+
...props
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
function DropdownMenuCheckboxItem({ className, children, checked, inset, ...props }) {
|
|
767
|
+
return /* @__PURE__ */ jsxs(Menu.CheckboxItem, {
|
|
768
|
+
"data-slot": "dropdown-menu-checkbox-item",
|
|
769
|
+
"data-inset": inset,
|
|
770
|
+
className: cn("relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
|
|
771
|
+
checked,
|
|
772
|
+
...props,
|
|
773
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
774
|
+
className: "pointer-events-none absolute right-2 flex items-center justify-center",
|
|
775
|
+
"data-slot": "dropdown-menu-checkbox-item-indicator",
|
|
776
|
+
children: /* @__PURE__ */ jsx(Menu.CheckboxItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, {}) })
|
|
777
|
+
}), children]
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
function DropdownMenuRadioGroup({ ...props }) {
|
|
781
|
+
return /* @__PURE__ */ jsx(Menu.RadioGroup, {
|
|
782
|
+
"data-slot": "dropdown-menu-radio-group",
|
|
783
|
+
...props
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
function DropdownMenuRadioItem({ className, children, inset, ...props }) {
|
|
787
|
+
return /* @__PURE__ */ jsxs(Menu.RadioItem, {
|
|
788
|
+
"data-slot": "dropdown-menu-radio-item",
|
|
789
|
+
"data-inset": inset,
|
|
790
|
+
className: cn("relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
|
|
791
|
+
...props,
|
|
792
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
793
|
+
className: "pointer-events-none absolute right-2 flex items-center justify-center",
|
|
794
|
+
"data-slot": "dropdown-menu-radio-item-indicator",
|
|
795
|
+
children: /* @__PURE__ */ jsx(Menu.RadioItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, {}) })
|
|
796
|
+
}), children]
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
function DropdownMenuSeparator({ className, ...props }) {
|
|
800
|
+
return /* @__PURE__ */ jsx(Menu.Separator, {
|
|
801
|
+
"data-slot": "dropdown-menu-separator",
|
|
802
|
+
className: cn("-mx-1 my-1 h-px bg-border", className),
|
|
803
|
+
...props
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
//#endregion
|
|
807
|
+
//#region src/components/browser-toolbar-menus.tsx
|
|
808
|
+
const STREAM_PRESETS = [
|
|
809
|
+
{
|
|
810
|
+
id: "low",
|
|
811
|
+
label: "Low",
|
|
812
|
+
detail: "20 fps · max 800 kbps",
|
|
813
|
+
settings: {
|
|
814
|
+
fps: 20,
|
|
815
|
+
bitrateKbps: 800
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
id: "balanced",
|
|
820
|
+
label: "Balanced",
|
|
821
|
+
detail: "30 fps · max 3000 kbps",
|
|
822
|
+
settings: {
|
|
823
|
+
fps: 30,
|
|
824
|
+
bitrateKbps: 3e3
|
|
825
|
+
}
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
id: "high",
|
|
829
|
+
label: "High",
|
|
830
|
+
detail: "60 fps · max 20000 kbps",
|
|
831
|
+
settings: {
|
|
832
|
+
fps: 60,
|
|
833
|
+
bitrateKbps: 2e4
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
];
|
|
837
|
+
const STREAM_LIMITS = {
|
|
838
|
+
fps: {
|
|
839
|
+
min: 1,
|
|
840
|
+
max: 120
|
|
841
|
+
},
|
|
842
|
+
bitrateKbps: {
|
|
843
|
+
min: 100,
|
|
844
|
+
max: 5e4
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
const VIEWPORT_LIMITS = {
|
|
848
|
+
width: {
|
|
849
|
+
min: 1,
|
|
850
|
+
max: 16384
|
|
851
|
+
},
|
|
852
|
+
height: {
|
|
853
|
+
min: 1,
|
|
854
|
+
max: 16384
|
|
855
|
+
},
|
|
856
|
+
deviceScaleFactor: {
|
|
857
|
+
min: .25,
|
|
858
|
+
max: 4
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
function BrowserMenus({ control, cdpUrl, shareUrls, busy, connected, localCursorEnabled, onLocalCursorChange, onReconnect, onSessionDetails, now }) {
|
|
862
|
+
const runningRecordings = control.recordings.filter((recording) => recording.status === "starting" || recording.status === "running");
|
|
863
|
+
const recordingActive = runningRecordings.length > 0;
|
|
864
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
865
|
+
className: "shrink-0 sm:hidden",
|
|
866
|
+
children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
867
|
+
render: /* @__PURE__ */ jsx(DropdownMenuTrigger, { render: /* @__PURE__ */ jsx(Button$1, {
|
|
868
|
+
type: "button",
|
|
869
|
+
variant: recordingActive ? "destructive" : "ghost",
|
|
870
|
+
size: "icon-sm",
|
|
871
|
+
"aria-label": recordingActive ? "Browser menu, recording active" : "Browser menu"
|
|
872
|
+
}) }),
|
|
873
|
+
children: recordingActive ? /* @__PURE__ */ jsx(Circle, { fill: "currentColor" }) : /* @__PURE__ */ jsx(MoreVertical, {})
|
|
874
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
875
|
+
side: "bottom",
|
|
876
|
+
children: recordingActive ? "Browser menu, recording active" : "Browser menu"
|
|
877
|
+
})] }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
878
|
+
align: "end",
|
|
879
|
+
className: "w-72",
|
|
880
|
+
children: [
|
|
881
|
+
/* @__PURE__ */ jsx(RestMenuItems, {
|
|
882
|
+
control,
|
|
883
|
+
cdpUrl,
|
|
884
|
+
shareUrls,
|
|
885
|
+
onSessionDetails
|
|
886
|
+
}),
|
|
887
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
888
|
+
/* @__PURE__ */ jsx(RecordingMenuItems, {
|
|
889
|
+
control,
|
|
890
|
+
connected: connected && control.collaboration.role === "owner",
|
|
891
|
+
runningRecordings,
|
|
892
|
+
now
|
|
893
|
+
}),
|
|
894
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
895
|
+
/* @__PURE__ */ jsx(ViewportStreamMenuItems, {
|
|
896
|
+
control,
|
|
897
|
+
connected: connected && control.collaboration.role !== "viewer",
|
|
898
|
+
localCursorEnabled,
|
|
899
|
+
onLocalCursorChange,
|
|
900
|
+
busy,
|
|
901
|
+
onReconnect
|
|
902
|
+
})
|
|
903
|
+
]
|
|
904
|
+
})] })
|
|
905
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
906
|
+
className: "hidden shrink-0 items-center gap-0.5 sm:flex",
|
|
907
|
+
children: [
|
|
908
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
909
|
+
render: /* @__PURE__ */ jsx(DropdownMenuTrigger, { render: /* @__PURE__ */ jsx(Button$1, {
|
|
910
|
+
type: "button",
|
|
911
|
+
variant: recordingActive ? "destructive" : "ghost",
|
|
912
|
+
size: "icon-sm",
|
|
913
|
+
"aria-label": recordingActive ? "Recording in progress" : "Recording"
|
|
914
|
+
}) }),
|
|
915
|
+
children: /* @__PURE__ */ jsx(Circle, { fill: recordingActive ? "currentColor" : "none" })
|
|
916
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
917
|
+
side: "bottom",
|
|
918
|
+
children: recordingActive ? "Recording in progress" : "Recording"
|
|
919
|
+
})] }), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
920
|
+
align: "end",
|
|
921
|
+
className: "w-72",
|
|
922
|
+
children: /* @__PURE__ */ jsx(RecordingMenuItems, {
|
|
923
|
+
control,
|
|
924
|
+
connected: connected && control.collaboration.role === "owner",
|
|
925
|
+
runningRecordings,
|
|
926
|
+
now
|
|
927
|
+
})
|
|
928
|
+
})] }),
|
|
929
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
930
|
+
render: /* @__PURE__ */ jsx(DropdownMenuTrigger, { render: /* @__PURE__ */ jsx(Button$1, {
|
|
931
|
+
type: "button",
|
|
932
|
+
variant: "ghost",
|
|
933
|
+
size: "icon-sm",
|
|
934
|
+
"aria-label": "Viewport and stream"
|
|
935
|
+
}) }),
|
|
936
|
+
children: /* @__PURE__ */ jsx(Monitor, {})
|
|
937
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
938
|
+
side: "bottom",
|
|
939
|
+
children: "Viewport and stream"
|
|
940
|
+
})] }), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
941
|
+
align: "end",
|
|
942
|
+
className: "w-56",
|
|
943
|
+
children: /* @__PURE__ */ jsx(ViewportStreamMenuItems, {
|
|
944
|
+
control,
|
|
945
|
+
connected: connected && control.collaboration.role !== "viewer",
|
|
946
|
+
localCursorEnabled,
|
|
947
|
+
onLocalCursorChange,
|
|
948
|
+
busy,
|
|
949
|
+
onReconnect
|
|
950
|
+
})
|
|
951
|
+
})] }),
|
|
952
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
953
|
+
render: /* @__PURE__ */ jsx(DropdownMenuTrigger, { render: /* @__PURE__ */ jsx(Button$1, {
|
|
954
|
+
type: "button",
|
|
955
|
+
variant: "ghost",
|
|
956
|
+
size: "icon-sm",
|
|
957
|
+
"aria-label": "More browser actions"
|
|
958
|
+
}) }),
|
|
959
|
+
children: /* @__PURE__ */ jsx(MoreVertical, {})
|
|
960
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
961
|
+
side: "bottom",
|
|
962
|
+
children: "More browser actions"
|
|
963
|
+
})] }), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
964
|
+
align: "end",
|
|
965
|
+
className: "w-48",
|
|
966
|
+
children: /* @__PURE__ */ jsx(RestMenuItems, {
|
|
967
|
+
control,
|
|
968
|
+
cdpUrl,
|
|
969
|
+
shareUrls,
|
|
970
|
+
onSessionDetails
|
|
971
|
+
})
|
|
972
|
+
})] })
|
|
973
|
+
]
|
|
974
|
+
})] });
|
|
975
|
+
}
|
|
976
|
+
function RestMenuItems({ control, cdpUrl, shareUrls, onSessionDetails }) {
|
|
977
|
+
const copy = useEffectCallback((value, copied) => copyTextWithToast(value, () => toast.success(copied)), []);
|
|
978
|
+
return /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
|
|
979
|
+
/* @__PURE__ */ jsx(InputControlMenuItem, { control }),
|
|
980
|
+
onSessionDetails ? /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
981
|
+
onClick: onSessionDetails,
|
|
982
|
+
children: [/* @__PURE__ */ jsx(Info, {}), "Session details"]
|
|
983
|
+
}) : null,
|
|
984
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
985
|
+
disabled: !cdpUrl,
|
|
986
|
+
onClick: () => {
|
|
987
|
+
if (!cdpUrl) return;
|
|
988
|
+
copy(cdpUrl, "CDP URL copied");
|
|
989
|
+
},
|
|
990
|
+
children: [/* @__PURE__ */ jsx(Copy, {}), "Copy CDP URL"]
|
|
991
|
+
}),
|
|
992
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
993
|
+
disabled: !shareUrls,
|
|
994
|
+
onClick: () => {
|
|
995
|
+
if (!shareUrls) return;
|
|
996
|
+
copy(shareUrls.editor, "Editor URL copied");
|
|
997
|
+
},
|
|
998
|
+
children: [/* @__PURE__ */ jsx(Share2, {}), "Copy editor URL"]
|
|
999
|
+
}),
|
|
1000
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1001
|
+
disabled: !shareUrls,
|
|
1002
|
+
onClick: () => {
|
|
1003
|
+
if (!shareUrls) return;
|
|
1004
|
+
copy(shareUrls.viewer, "Viewer URL copied");
|
|
1005
|
+
},
|
|
1006
|
+
children: [/* @__PURE__ */ jsx(Share2, {}), "Copy viewer URL"]
|
|
1007
|
+
})
|
|
1008
|
+
] });
|
|
1009
|
+
}
|
|
1010
|
+
function InputControlMenuItem({ control }) {
|
|
1011
|
+
const collaboration = control.collaboration;
|
|
1012
|
+
const targetId = control.activeTargetId;
|
|
1013
|
+
const explicitControlClaimed = collaboration.holderClientId !== null && collaboration.leaseMode === "explicit";
|
|
1014
|
+
const localControlClaimed = collaboration.hasControl && collaboration.leaseMode === "explicit";
|
|
1015
|
+
let label = "Control offline";
|
|
1016
|
+
let disabled = collaboration.phase !== "connected" || !targetId;
|
|
1017
|
+
let action = () => void 0;
|
|
1018
|
+
if (collaboration.role === "viewer") {
|
|
1019
|
+
label = "View only";
|
|
1020
|
+
disabled = true;
|
|
1021
|
+
} else if (collaboration.hasControl && collaboration.leaseMode === "explicit") {
|
|
1022
|
+
label = "Release control";
|
|
1023
|
+
action = () => {
|
|
1024
|
+
collaboration.release();
|
|
1025
|
+
};
|
|
1026
|
+
} else if (collaboration.hasControl) {
|
|
1027
|
+
label = "Claim control";
|
|
1028
|
+
action = () => {
|
|
1029
|
+
if (targetId) collaboration.claim(targetId, "explicit");
|
|
1030
|
+
};
|
|
1031
|
+
} else {
|
|
1032
|
+
const holder = collaboration.participants.find((participant) => participant.clientId === collaboration.holderClientId);
|
|
1033
|
+
const canPreempt = collaboration.role === "owner" && holder?.role === "editor";
|
|
1034
|
+
const controlBlocked = explicitControlClaimed && !canPreempt;
|
|
1035
|
+
label = controlBlocked ? "Control in use" : "Claim control";
|
|
1036
|
+
disabled = disabled || controlBlocked;
|
|
1037
|
+
action = () => {
|
|
1038
|
+
if (targetId) collaboration.claim(targetId, "explicit");
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
return /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1042
|
+
disabled,
|
|
1043
|
+
onClick: action,
|
|
1044
|
+
children: [localControlClaimed ? /* @__PURE__ */ jsx(Lock, {}) : /* @__PURE__ */ jsx(LockOpen, {}), label]
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
function RecordingMenuItems({ control, connected, runningRecordings, now }) {
|
|
1048
|
+
const recordingAvailable = connected && control.collaboration.role === "owner";
|
|
1049
|
+
const canStart = recordingAvailable && Boolean(control.activeTargetId) && !control.recordingBusy;
|
|
1050
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
|
|
1051
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Recording" }),
|
|
1052
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1053
|
+
disabled: !canStart,
|
|
1054
|
+
onClick: () => control.startRecording("tab"),
|
|
1055
|
+
children: [/* @__PURE__ */ jsx(Circle, {}), /* @__PURE__ */ jsxs("span", {
|
|
1056
|
+
className: "flex min-w-0 flex-col",
|
|
1057
|
+
children: [/* @__PURE__ */ jsx("span", { children: "Record this tab" }), /* @__PURE__ */ jsx("span", {
|
|
1058
|
+
className: "text-xs text-muted-foreground",
|
|
1059
|
+
children: "Stay pinned to this target"
|
|
1060
|
+
})]
|
|
1061
|
+
})]
|
|
1062
|
+
}),
|
|
1063
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1064
|
+
disabled: !canStart,
|
|
1065
|
+
onClick: () => control.startRecording("viewer"),
|
|
1066
|
+
children: [/* @__PURE__ */ jsx(Monitor, {}), /* @__PURE__ */ jsxs("span", {
|
|
1067
|
+
className: "flex min-w-0 flex-col",
|
|
1068
|
+
children: [/* @__PURE__ */ jsx("span", { children: "Record this viewer" }), /* @__PURE__ */ jsx("span", {
|
|
1069
|
+
className: "text-xs text-muted-foreground",
|
|
1070
|
+
children: recordingAvailable ? "Follow tab switches" : "Owner connection required"
|
|
1071
|
+
})]
|
|
1072
|
+
})]
|
|
1073
|
+
})
|
|
1074
|
+
] }), runningRecordings.map((recording) => {
|
|
1075
|
+
const target = control.targets.find((candidate) => candidate.id === recording.targetId);
|
|
1076
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
|
|
1077
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: /* @__PURE__ */ jsxs("span", {
|
|
1078
|
+
className: "flex min-w-0 items-center justify-between gap-2",
|
|
1079
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
1080
|
+
className: "truncate",
|
|
1081
|
+
children: [
|
|
1082
|
+
recording.mode === "viewer" ? "Viewer" : "Tab",
|
|
1083
|
+
":",
|
|
1084
|
+
" ",
|
|
1085
|
+
target?.title || recording.targetId.slice(0, 8)
|
|
1086
|
+
]
|
|
1087
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1088
|
+
className: "shrink-0 tabular-nums",
|
|
1089
|
+
children: formatElapsed(recording.startedAt, now)
|
|
1090
|
+
})]
|
|
1091
|
+
}) }),
|
|
1092
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1093
|
+
disabled: control.recordingBusy,
|
|
1094
|
+
onClick: () => control.stopRecording(recording.recordingId),
|
|
1095
|
+
children: [/* @__PURE__ */ jsx(Download, {}), "Stop and download"]
|
|
1096
|
+
}),
|
|
1097
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1098
|
+
disabled: control.recordingBusy,
|
|
1099
|
+
onClick: () => control.cancelRecording(recording.recordingId),
|
|
1100
|
+
children: [/* @__PURE__ */ jsx(X, {}), "Cancel without download"]
|
|
1101
|
+
})
|
|
1102
|
+
] })] }, recording.recordingId);
|
|
1103
|
+
})] });
|
|
1104
|
+
}
|
|
1105
|
+
function ViewportStreamMenuItems({ control, connected, localCursorEnabled, onLocalCursorChange, busy, onReconnect }) {
|
|
1106
|
+
return /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
|
|
1107
|
+
/* @__PURE__ */ jsx(ViewportMenu, {
|
|
1108
|
+
control,
|
|
1109
|
+
connected
|
|
1110
|
+
}),
|
|
1111
|
+
control.mediaVideoProfiles.length > 0 ? /* @__PURE__ */ jsx(StreamMenu, {
|
|
1112
|
+
control,
|
|
1113
|
+
qualityUpdatesEnabled: control.collaboration.role !== "viewer"
|
|
1114
|
+
}) : null,
|
|
1115
|
+
/* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
|
|
1116
|
+
disabled: !connected || control.collaboration.role === "viewer",
|
|
1117
|
+
checked: control.remoteCursorEnabled,
|
|
1118
|
+
onCheckedChange: control.setRemoteCursorEnabled,
|
|
1119
|
+
children: [/* @__PURE__ */ jsx(MousePointer2, {}), "Remote cursor"]
|
|
1120
|
+
}),
|
|
1121
|
+
/* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
|
|
1122
|
+
checked: localCursorEnabled,
|
|
1123
|
+
onCheckedChange: onLocalCursorChange,
|
|
1124
|
+
children: [/* @__PURE__ */ jsx(MousePointer2, {}), "Local cursor"]
|
|
1125
|
+
}),
|
|
1126
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1127
|
+
disabled: busy,
|
|
1128
|
+
onClick: onReconnect,
|
|
1129
|
+
children: [/* @__PURE__ */ jsx(RotateCcw, {}), "Reconnect"]
|
|
1130
|
+
})
|
|
1131
|
+
] });
|
|
1132
|
+
}
|
|
1133
|
+
function StreamMenu({ control, qualityUpdatesEnabled }) {
|
|
1134
|
+
const settings = control.mediaStreamSettings;
|
|
1135
|
+
const source = control.mediaPath === "websocket-live" ? "jpeg" : qualityUpdatesEnabled ? settings?.profile : "webrtc";
|
|
1136
|
+
const disabled = control.mediaSwitching || control.phase !== "connected";
|
|
1137
|
+
return /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [/* @__PURE__ */ jsx(Gauge, {}), "Stream"] }), /* @__PURE__ */ jsx(DropdownMenuSubContent, {
|
|
1138
|
+
className: "w-64",
|
|
1139
|
+
children: /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
|
|
1140
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Source" }),
|
|
1141
|
+
/* @__PURE__ */ jsxs(DropdownMenuRadioGroup, {
|
|
1142
|
+
value: source,
|
|
1143
|
+
onValueChange: (value) => {
|
|
1144
|
+
if (value === "jpeg") {
|
|
1145
|
+
control.selectMediaStream({ kind: "jpeg" });
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
if (value === "webrtc") {
|
|
1149
|
+
control.selectMediaStream({ kind: "webrtc-retry" });
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
const profile = control.mediaVideoProfiles.find((candidate) => candidate.id === value);
|
|
1153
|
+
if (profile && settings) control.selectMediaStream({
|
|
1154
|
+
kind: "webrtc",
|
|
1155
|
+
quality: {
|
|
1156
|
+
...settings,
|
|
1157
|
+
profile: profile.id
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
},
|
|
1161
|
+
children: [
|
|
1162
|
+
!qualityUpdatesEnabled ? /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
|
|
1163
|
+
value: "webrtc",
|
|
1164
|
+
disabled,
|
|
1165
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
1166
|
+
className: "flex min-w-0 flex-col",
|
|
1167
|
+
children: [/* @__PURE__ */ jsx("span", { children: "WebRTC" }), /* @__PURE__ */ jsx("span", {
|
|
1168
|
+
className: "text-xs text-muted-foreground",
|
|
1169
|
+
children: "Live video"
|
|
1170
|
+
})]
|
|
1171
|
+
})
|
|
1172
|
+
}) : null,
|
|
1173
|
+
qualityUpdatesEnabled ? control.mediaVideoProfiles.map((profile) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
|
|
1174
|
+
value: profile.id,
|
|
1175
|
+
disabled: disabled || !settings,
|
|
1176
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
1177
|
+
className: "flex min-w-0 flex-col",
|
|
1178
|
+
children: [/* @__PURE__ */ jsx("span", { children: profile.label }), /* @__PURE__ */ jsxs("span", {
|
|
1179
|
+
className: "text-xs text-muted-foreground",
|
|
1180
|
+
children: [profile.codec, " · WebRTC"]
|
|
1181
|
+
})]
|
|
1182
|
+
})
|
|
1183
|
+
}, profile.id)) : null,
|
|
1184
|
+
/* @__PURE__ */ jsx(DropdownMenuRadioItem, {
|
|
1185
|
+
value: "jpeg",
|
|
1186
|
+
disabled,
|
|
1187
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
1188
|
+
className: "flex min-w-0 flex-col",
|
|
1189
|
+
children: [/* @__PURE__ */ jsx("span", { children: "JPEG" }), /* @__PURE__ */ jsx("span", {
|
|
1190
|
+
className: "text-xs text-muted-foreground",
|
|
1191
|
+
children: "WebSocket raster stream"
|
|
1192
|
+
})]
|
|
1193
|
+
})
|
|
1194
|
+
})
|
|
1195
|
+
]
|
|
1196
|
+
}),
|
|
1197
|
+
qualityUpdatesEnabled && control.mediaPath === "webrtc-live" && settings ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1198
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1199
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Quality" }),
|
|
1200
|
+
/* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
|
|
1201
|
+
value: activeStreamPresetId(settings),
|
|
1202
|
+
onValueChange: (value) => {
|
|
1203
|
+
const preset = STREAM_PRESETS.find((item) => item.id === value);
|
|
1204
|
+
if (preset) control.setWebRTCStreamSettings({
|
|
1205
|
+
...settings,
|
|
1206
|
+
...preset.settings
|
|
1207
|
+
});
|
|
1208
|
+
},
|
|
1209
|
+
children: STREAM_PRESETS.map((preset) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
|
|
1210
|
+
value: preset.id,
|
|
1211
|
+
disabled,
|
|
1212
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
1213
|
+
className: "flex min-w-0 flex-col",
|
|
1214
|
+
children: [/* @__PURE__ */ jsx("span", { children: preset.label }), /* @__PURE__ */ jsx("span", {
|
|
1215
|
+
className: "text-xs text-muted-foreground",
|
|
1216
|
+
children: preset.detail
|
|
1217
|
+
})]
|
|
1218
|
+
})
|
|
1219
|
+
}, preset.id))
|
|
1220
|
+
}),
|
|
1221
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1222
|
+
/* @__PURE__ */ jsx(CustomStreamSettings, {
|
|
1223
|
+
settings,
|
|
1224
|
+
disabled,
|
|
1225
|
+
onApply: control.setWebRTCStreamSettings
|
|
1226
|
+
})
|
|
1227
|
+
] }) : null
|
|
1228
|
+
] })
|
|
1229
|
+
})] });
|
|
1230
|
+
}
|
|
1231
|
+
function ViewportMenu({ control, connected }) {
|
|
1232
|
+
return /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [/* @__PURE__ */ jsx(Monitor, {}), "Viewport"] }), /* @__PURE__ */ jsxs(DropdownMenuSubContent, {
|
|
1233
|
+
className: "w-64",
|
|
1234
|
+
children: [
|
|
1235
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Viewport" }),
|
|
1236
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1237
|
+
disabled: !connected || !control.browserViewportSize,
|
|
1238
|
+
onClick: () => control.setViewportToBrowserSize(),
|
|
1239
|
+
children: [/* @__PURE__ */ jsx(Maximize2, {}), "Set to browser size"]
|
|
1240
|
+
}),
|
|
1241
|
+
/* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
|
|
1242
|
+
disabled: !connected || !control.browserViewportSize,
|
|
1243
|
+
checked: control.viewportAutoSync,
|
|
1244
|
+
onCheckedChange: control.setViewportAutoSync,
|
|
1245
|
+
children: [/* @__PURE__ */ jsx(Monitor, {}), "Auto sync browser size"]
|
|
1246
|
+
}),
|
|
1247
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1248
|
+
/* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
|
|
1249
|
+
value: activeViewportPresetId(control.viewport),
|
|
1250
|
+
onValueChange: (value) => {
|
|
1251
|
+
const preset = VIEWPORT_PRESETS.find((item) => item.id === value);
|
|
1252
|
+
if (preset) control.setViewport(createViewportPreset(preset.width, preset.height, control.viewport.deviceScaleFactor));
|
|
1253
|
+
},
|
|
1254
|
+
children: VIEWPORT_PRESETS.map((preset) => /* @__PURE__ */ jsxs(DropdownMenuRadioItem, {
|
|
1255
|
+
value: preset.id,
|
|
1256
|
+
disabled: !connected,
|
|
1257
|
+
children: [/* @__PURE__ */ jsx(Monitor, {}), preset.label]
|
|
1258
|
+
}, preset.id))
|
|
1259
|
+
}),
|
|
1260
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1261
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Scale" }),
|
|
1262
|
+
/* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
|
|
1263
|
+
value: activeViewportScaleId(control.viewport.deviceScaleFactor),
|
|
1264
|
+
onValueChange: (value) => {
|
|
1265
|
+
const nextScale = VIEWPORT_DEVICE_SCALE_FACTORS.find((item) => formatViewportScale(item) === value);
|
|
1266
|
+
if (nextScale) control.setViewport(createViewportPreset(control.viewport.width, control.viewport.height, nextScale));
|
|
1267
|
+
},
|
|
1268
|
+
children: VIEWPORT_DEVICE_SCALE_FACTORS.map((deviceScaleFactor) => /* @__PURE__ */ jsxs(DropdownMenuRadioItem, {
|
|
1269
|
+
value: formatViewportScale(deviceScaleFactor),
|
|
1270
|
+
disabled: !connected,
|
|
1271
|
+
children: [
|
|
1272
|
+
/* @__PURE__ */ jsx(Monitor, {}),
|
|
1273
|
+
formatViewportScale(deviceScaleFactor),
|
|
1274
|
+
"x"
|
|
1275
|
+
]
|
|
1276
|
+
}, deviceScaleFactor))
|
|
1277
|
+
}),
|
|
1278
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1279
|
+
/* @__PURE__ */ jsx(CustomViewportSettings, {
|
|
1280
|
+
viewport: control.viewport,
|
|
1281
|
+
connected,
|
|
1282
|
+
onApply: control.setViewport
|
|
1283
|
+
})
|
|
1284
|
+
]
|
|
1285
|
+
})] });
|
|
1286
|
+
}
|
|
1287
|
+
function CustomViewportSettings({ viewport, connected, onApply }) {
|
|
1288
|
+
const [width, setWidth] = useState(String(viewport.width));
|
|
1289
|
+
const [height, setHeight] = useState(String(viewport.height));
|
|
1290
|
+
const [deviceScaleFactor, setDeviceScaleFactor] = useState(formatViewportScale(viewport.deviceScaleFactor));
|
|
1291
|
+
useEffect(() => {
|
|
1292
|
+
setWidth(String(viewport.width));
|
|
1293
|
+
setHeight(String(viewport.height));
|
|
1294
|
+
setDeviceScaleFactor(formatViewportScale(viewport.deviceScaleFactor));
|
|
1295
|
+
}, [viewport]);
|
|
1296
|
+
const nextViewport = parseViewportSettings({
|
|
1297
|
+
width,
|
|
1298
|
+
height,
|
|
1299
|
+
deviceScaleFactor
|
|
1300
|
+
});
|
|
1301
|
+
const unchanged = nextViewport ? viewport.width === nextViewport.width && viewport.height === nextViewport.height && viewport.deviceScaleFactor === nextViewport.deviceScaleFactor : false;
|
|
1302
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1303
|
+
className: "grid gap-2 px-2 py-1.5",
|
|
1304
|
+
onClick: (event) => event.stopPropagation(),
|
|
1305
|
+
onKeyDown: (event) => event.stopPropagation(),
|
|
1306
|
+
children: [
|
|
1307
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, {
|
|
1308
|
+
className: "px-0",
|
|
1309
|
+
children: "Custom"
|
|
1310
|
+
}),
|
|
1311
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1312
|
+
className: "grid grid-cols-3 gap-2",
|
|
1313
|
+
children: [
|
|
1314
|
+
/* @__PURE__ */ jsx(StreamNumberField, {
|
|
1315
|
+
label: "Width",
|
|
1316
|
+
value: width,
|
|
1317
|
+
onChange: setWidth
|
|
1318
|
+
}),
|
|
1319
|
+
/* @__PURE__ */ jsx(StreamNumberField, {
|
|
1320
|
+
label: "Height",
|
|
1321
|
+
value: height,
|
|
1322
|
+
onChange: setHeight
|
|
1323
|
+
}),
|
|
1324
|
+
/* @__PURE__ */ jsx(ViewportScaleField, {
|
|
1325
|
+
label: "Scale",
|
|
1326
|
+
value: deviceScaleFactor,
|
|
1327
|
+
onChange: setDeviceScaleFactor
|
|
1328
|
+
})
|
|
1329
|
+
]
|
|
1330
|
+
}),
|
|
1331
|
+
/* @__PURE__ */ jsx(Button$1, {
|
|
1332
|
+
type: "button",
|
|
1333
|
+
size: "sm",
|
|
1334
|
+
className: "h-7",
|
|
1335
|
+
disabled: !connected || !nextViewport || unchanged,
|
|
1336
|
+
onClick: () => {
|
|
1337
|
+
if (nextViewport) onApply(nextViewport);
|
|
1338
|
+
},
|
|
1339
|
+
children: "Apply"
|
|
1340
|
+
})
|
|
1341
|
+
]
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
function CustomStreamSettings({ settings, disabled, onApply }) {
|
|
1345
|
+
const [fps, setFps] = useState(String(settings.fps));
|
|
1346
|
+
const [bitrateKbps, setBitrateKbps] = useState(String(settings.bitrateKbps));
|
|
1347
|
+
useEffect(() => {
|
|
1348
|
+
setFps(String(settings.fps));
|
|
1349
|
+
setBitrateKbps(String(settings.bitrateKbps));
|
|
1350
|
+
}, [settings]);
|
|
1351
|
+
const nextSettings = parseStreamSettings({
|
|
1352
|
+
fps,
|
|
1353
|
+
bitrateKbps
|
|
1354
|
+
});
|
|
1355
|
+
const unchanged = settings.fps === nextSettings?.fps && settings.bitrateKbps === nextSettings?.bitrateKbps;
|
|
1356
|
+
return /* @__PURE__ */ jsxs(FieldGroup, {
|
|
1357
|
+
className: "gap-2 px-2 py-1.5",
|
|
1358
|
+
onClick: (event) => event.stopPropagation(),
|
|
1359
|
+
onKeyDown: (event) => event.stopPropagation(),
|
|
1360
|
+
children: [
|
|
1361
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, {
|
|
1362
|
+
className: "px-0",
|
|
1363
|
+
children: "Custom"
|
|
1364
|
+
}),
|
|
1365
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1366
|
+
className: "grid grid-cols-2 gap-2",
|
|
1367
|
+
children: [/* @__PURE__ */ jsx(StreamNumberField, {
|
|
1368
|
+
label: "FPS",
|
|
1369
|
+
value: fps,
|
|
1370
|
+
onChange: setFps
|
|
1371
|
+
}), /* @__PURE__ */ jsx(StreamNumberField, {
|
|
1372
|
+
label: "Max Kbps",
|
|
1373
|
+
value: bitrateKbps,
|
|
1374
|
+
onChange: setBitrateKbps
|
|
1375
|
+
})]
|
|
1376
|
+
}),
|
|
1377
|
+
/* @__PURE__ */ jsx(Button$1, {
|
|
1378
|
+
type: "button",
|
|
1379
|
+
size: "sm",
|
|
1380
|
+
className: "h-7 w-full",
|
|
1381
|
+
disabled: disabled || !nextSettings || unchanged,
|
|
1382
|
+
onClick: () => {
|
|
1383
|
+
if (nextSettings) onApply({
|
|
1384
|
+
...settings,
|
|
1385
|
+
...nextSettings
|
|
1386
|
+
});
|
|
1387
|
+
},
|
|
1388
|
+
children: "Apply"
|
|
1389
|
+
})
|
|
1390
|
+
]
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
function StreamNumberField({ label, value, onChange }) {
|
|
1394
|
+
const id = useId();
|
|
1395
|
+
return /* @__PURE__ */ jsxs(Field, {
|
|
1396
|
+
className: "gap-1",
|
|
1397
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
1398
|
+
htmlFor: id,
|
|
1399
|
+
children: label
|
|
1400
|
+
}), /* @__PURE__ */ jsx(Input$1, {
|
|
1401
|
+
id,
|
|
1402
|
+
type: "text",
|
|
1403
|
+
inputMode: "numeric",
|
|
1404
|
+
value,
|
|
1405
|
+
onChange: (event) => onChange(digitsOnly(event.currentTarget.value)),
|
|
1406
|
+
onFocus: (event) => event.currentTarget.select(),
|
|
1407
|
+
className: "h-7"
|
|
1408
|
+
})]
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
function ViewportScaleField({ label, value, onChange }) {
|
|
1412
|
+
const id = useId();
|
|
1413
|
+
return /* @__PURE__ */ jsxs(Field, {
|
|
1414
|
+
className: "gap-1",
|
|
1415
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
1416
|
+
htmlFor: id,
|
|
1417
|
+
children: label
|
|
1418
|
+
}), /* @__PURE__ */ jsx(Input$1, {
|
|
1419
|
+
id,
|
|
1420
|
+
type: "text",
|
|
1421
|
+
inputMode: "decimal",
|
|
1422
|
+
value,
|
|
1423
|
+
onChange: (event) => onChange(decimalNumber(event.currentTarget.value)),
|
|
1424
|
+
onFocus: (event) => event.currentTarget.select(),
|
|
1425
|
+
className: "h-7"
|
|
1426
|
+
})]
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
function parseViewportSettings({ width, height, deviceScaleFactor }) {
|
|
1430
|
+
if (!width || !height || !deviceScaleFactor) return null;
|
|
1431
|
+
const parsedWidth = Number(width);
|
|
1432
|
+
const parsedHeight = Number(height);
|
|
1433
|
+
const parsedScale = Number(deviceScaleFactor);
|
|
1434
|
+
if (!Number.isFinite(parsedWidth) || !Number.isFinite(parsedHeight) || !Number.isFinite(parsedScale)) return null;
|
|
1435
|
+
const nextWidth = clampInteger(parsedWidth, VIEWPORT_LIMITS.width.min, VIEWPORT_LIMITS.width.max);
|
|
1436
|
+
const nextHeight = clampInteger(parsedHeight, VIEWPORT_LIMITS.height.min, VIEWPORT_LIMITS.height.max);
|
|
1437
|
+
const maxScale = Math.min(VIEWPORT_LIMITS.deviceScaleFactor.max, VIEWPORT_LIMITS.width.max / nextWidth, VIEWPORT_LIMITS.height.max / nextHeight);
|
|
1438
|
+
return createViewportPreset(nextWidth, nextHeight, clampDecimal(parsedScale, VIEWPORT_LIMITS.deviceScaleFactor.min, maxScale));
|
|
1439
|
+
}
|
|
1440
|
+
function parseStreamSettings({ fps, bitrateKbps }) {
|
|
1441
|
+
if (!fps || !bitrateKbps) return null;
|
|
1442
|
+
return {
|
|
1443
|
+
fps: clampInteger(Number(fps), STREAM_LIMITS.fps.min, STREAM_LIMITS.fps.max),
|
|
1444
|
+
bitrateKbps: clampInteger(Number(bitrateKbps), STREAM_LIMITS.bitrateKbps.min, STREAM_LIMITS.bitrateKbps.max)
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
function digitsOnly(value) {
|
|
1448
|
+
return value.replace(/\D/g, "");
|
|
1449
|
+
}
|
|
1450
|
+
function decimalNumber(value) {
|
|
1451
|
+
const [integer = "", ...fraction] = value.replace(/[^\d.]/g, "").split(".");
|
|
1452
|
+
return fraction.length ? `${integer}.${fraction.join("")}` : integer;
|
|
1453
|
+
}
|
|
1454
|
+
function clampInteger(value, min, max) {
|
|
1455
|
+
return Math.min(Math.max(Math.round(value), min), max);
|
|
1456
|
+
}
|
|
1457
|
+
function clampDecimal(value, min, max) {
|
|
1458
|
+
return Math.min(Math.max(Math.round(value * 100) / 100, min), max);
|
|
1459
|
+
}
|
|
1460
|
+
function activeViewportPresetId(viewport) {
|
|
1461
|
+
return VIEWPORT_PRESETS.find((item) => viewport.width === item.width && viewport.height === item.height)?.id ?? "";
|
|
1462
|
+
}
|
|
1463
|
+
function activeViewportScaleId(deviceScaleFactor) {
|
|
1464
|
+
const preset = VIEWPORT_DEVICE_SCALE_FACTORS.find((item) => item === deviceScaleFactor);
|
|
1465
|
+
return preset ? formatViewportScale(preset) : "";
|
|
1466
|
+
}
|
|
1467
|
+
function activeStreamPresetId(settings) {
|
|
1468
|
+
if (!settings) return "";
|
|
1469
|
+
return STREAM_PRESETS.find((item) => settings.fps === item.settings.fps && settings.bitrateKbps === item.settings.bitrateKbps)?.id ?? "";
|
|
1470
|
+
}
|
|
1471
|
+
function formatElapsed(startedAt, now) {
|
|
1472
|
+
const elapsed = Math.max(0, Math.floor((now - Date.parse(startedAt)) / 1e3));
|
|
1473
|
+
const hours = Math.floor(elapsed / 3600);
|
|
1474
|
+
const minutes = Math.floor(elapsed % 3600 / 60);
|
|
1475
|
+
const seconds = elapsed % 60;
|
|
1476
|
+
return hours > 0 ? `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}` : `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
1477
|
+
}
|
|
1478
|
+
//#endregion
|
|
1479
|
+
//#region ../ui/src/components/avatar.tsx
|
|
1480
|
+
function Avatar$1({ className, size = "default", ...props }) {
|
|
1481
|
+
return /* @__PURE__ */ jsx(Avatar.Root, {
|
|
1482
|
+
"data-slot": "avatar",
|
|
1483
|
+
"data-size": size,
|
|
1484
|
+
className: cn("group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten", className),
|
|
1485
|
+
...props
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
function AvatarImage({ className, ...props }) {
|
|
1489
|
+
return /* @__PURE__ */ jsx(Avatar.Image, {
|
|
1490
|
+
"data-slot": "avatar-image",
|
|
1491
|
+
className: cn("aspect-square size-full rounded-full object-cover", className),
|
|
1492
|
+
...props
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
function AvatarFallback({ className, ...props }) {
|
|
1496
|
+
return /* @__PURE__ */ jsx(Avatar.Fallback, {
|
|
1497
|
+
"data-slot": "avatar-fallback",
|
|
1498
|
+
className: cn("flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs", className),
|
|
1499
|
+
...props
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
function AvatarBadge({ className, ...props }) {
|
|
1503
|
+
return /* @__PURE__ */ jsx("span", {
|
|
1504
|
+
"data-slot": "avatar-badge",
|
|
1505
|
+
className: cn("absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none", "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden", "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", className),
|
|
1506
|
+
...props
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
//#endregion
|
|
1510
|
+
//#region ../ui/src/components/popover.tsx
|
|
1511
|
+
function Popover$1({ ...props }) {
|
|
1512
|
+
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
1513
|
+
"data-slot": "popover",
|
|
1514
|
+
...props
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
function PopoverTrigger({ ...props }) {
|
|
1518
|
+
return /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
1519
|
+
"data-slot": "popover-trigger",
|
|
1520
|
+
...props
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
function PopoverContent({ className, align = "center", alignOffset = 0, side = "bottom", sideOffset = 4, ...props }) {
|
|
1524
|
+
return /* @__PURE__ */ jsx(Popover.Portal, {
|
|
1525
|
+
container: usePortalContainer(),
|
|
1526
|
+
children: /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
1527
|
+
align,
|
|
1528
|
+
alignOffset,
|
|
1529
|
+
side,
|
|
1530
|
+
sideOffset,
|
|
1531
|
+
className: "isolate z-50",
|
|
1532
|
+
children: /* @__PURE__ */ jsx(Popover.Popup, {
|
|
1533
|
+
"data-slot": "popover-content",
|
|
1534
|
+
className: cn("z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
|
|
1535
|
+
...props
|
|
1536
|
+
})
|
|
1537
|
+
})
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
function PopoverTitle({ className, ...props }) {
|
|
1541
|
+
return /* @__PURE__ */ jsx(Popover.Title, {
|
|
1542
|
+
"data-slot": "popover-title",
|
|
1543
|
+
className: cn("font-medium", className),
|
|
1544
|
+
...props
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
//#endregion
|
|
1548
|
+
//#region src/components/collaboration-presence.tsx
|
|
1549
|
+
const visibleParticipantCount = 5;
|
|
1550
|
+
function CollaborationPresence({ collaboration }) {
|
|
1551
|
+
const participants = [...collaboration.participants].sort((left, right) => {
|
|
1552
|
+
if (left.clientId === collaboration.clientId) return -1;
|
|
1553
|
+
if (right.clientId === collaboration.clientId) return 1;
|
|
1554
|
+
if (left.holdingInput !== right.holdingInput) return left.holdingInput ? -1 : 1;
|
|
1555
|
+
return left.name.localeCompare(right.name);
|
|
1556
|
+
});
|
|
1557
|
+
const visible = participants.slice(0, visibleParticipantCount);
|
|
1558
|
+
const overflow = participants.length - visible.length;
|
|
1559
|
+
if (visible.length === 0) return null;
|
|
1560
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1561
|
+
className: "group flex shrink-0 items-center px-2",
|
|
1562
|
+
"aria-label": "Session participants",
|
|
1563
|
+
children: [overflow > 0 ? /* @__PURE__ */ jsx("div", {
|
|
1564
|
+
className: "relative transition-[margin] duration-200",
|
|
1565
|
+
style: { zIndex: visible.length + 1 },
|
|
1566
|
+
children: /* @__PURE__ */ jsx(OverflowParticipants, {
|
|
1567
|
+
overflow,
|
|
1568
|
+
participants,
|
|
1569
|
+
collaboration
|
|
1570
|
+
})
|
|
1571
|
+
}) : null, visible.map((participant, index) => /* @__PURE__ */ jsx("div", {
|
|
1572
|
+
className: cn("relative transition-[margin] duration-200", (index > 0 || overflow > 0) && "-ml-4 group-hover:ml-0 group-focus-within:ml-0"),
|
|
1573
|
+
style: { zIndex: visible.length - index },
|
|
1574
|
+
children: /* @__PURE__ */ jsx(ParticipantButton, {
|
|
1575
|
+
participant,
|
|
1576
|
+
collaboration
|
|
1577
|
+
})
|
|
1578
|
+
}, participant.clientId))]
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
function OverflowParticipants({ overflow, participants, collaboration }) {
|
|
1582
|
+
return /* @__PURE__ */ jsxs(Popover$1, { children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
|
|
1583
|
+
render: /* @__PURE__ */ jsx(Button$1, {
|
|
1584
|
+
type: "button",
|
|
1585
|
+
variant: "ghost",
|
|
1586
|
+
size: "icon-sm",
|
|
1587
|
+
className: "shrink-0 rounded-full border bg-background text-[0.65rem] text-muted-foreground",
|
|
1588
|
+
"aria-label": `${overflow} more participants`
|
|
1589
|
+
}),
|
|
1590
|
+
children: ["+", overflow]
|
|
1591
|
+
}), /* @__PURE__ */ jsxs(PopoverContent, {
|
|
1592
|
+
align: "end",
|
|
1593
|
+
className: "w-auto gap-2",
|
|
1594
|
+
children: [/* @__PURE__ */ jsx(PopoverTitle, {
|
|
1595
|
+
className: "text-xs text-muted-foreground",
|
|
1596
|
+
children: "Everyone"
|
|
1597
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1598
|
+
className: "flex flex-wrap items-center gap-1",
|
|
1599
|
+
children: participants.map((participant) => /* @__PURE__ */ jsx(ParticipantButton, {
|
|
1600
|
+
participant,
|
|
1601
|
+
collaboration
|
|
1602
|
+
}, participant.clientId))
|
|
1603
|
+
})]
|
|
1604
|
+
})] });
|
|
1605
|
+
}
|
|
1606
|
+
function ParticipantButton({ participant, collaboration }) {
|
|
1607
|
+
const local = participant.clientId === collaboration.clientId;
|
|
1608
|
+
const followable = participant.role !== "automation";
|
|
1609
|
+
const following = participant.clientId === collaboration.followingClientId;
|
|
1610
|
+
const label = local ? `${participant.name} (you)` : !followable ? participant.name : following ? `${participant.name} · click to stop following` : `${participant.name} · click to follow`;
|
|
1611
|
+
return /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
1612
|
+
render: local || !followable ? /* @__PURE__ */ jsx("span", {
|
|
1613
|
+
className: "inline-flex size-7 shrink-0 items-center justify-center rounded-full",
|
|
1614
|
+
"aria-label": label
|
|
1615
|
+
}) : /* @__PURE__ */ jsx(Button$1, {
|
|
1616
|
+
type: "button",
|
|
1617
|
+
variant: following ? "secondary" : "ghost",
|
|
1618
|
+
size: "icon-sm",
|
|
1619
|
+
className: "shrink-0 rounded-full p-0",
|
|
1620
|
+
"aria-label": label,
|
|
1621
|
+
"aria-pressed": following,
|
|
1622
|
+
onClick: () => collaboration.follow(following ? null : participant.clientId)
|
|
1623
|
+
}),
|
|
1624
|
+
children: /* @__PURE__ */ jsx(ParticipantAvatar, {
|
|
1625
|
+
participant,
|
|
1626
|
+
following
|
|
1627
|
+
})
|
|
1628
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
1629
|
+
side: "bottom",
|
|
1630
|
+
children: label
|
|
1631
|
+
})] });
|
|
1632
|
+
}
|
|
1633
|
+
function ParticipantAvatar({ participant, following }) {
|
|
1634
|
+
const controlClaimed = participant.holdingInput && participant.leaseMode === "explicit";
|
|
1635
|
+
return /* @__PURE__ */ jsxs(Avatar$1, {
|
|
1636
|
+
size: "sm",
|
|
1637
|
+
className: cn("shrink-0 ring-2 ring-background", following && "ring-ring"),
|
|
1638
|
+
children: [
|
|
1639
|
+
/* @__PURE__ */ jsx(AvatarImage, {
|
|
1640
|
+
src: gravatarURL(participant.avatarHash),
|
|
1641
|
+
alt: ""
|
|
1642
|
+
}),
|
|
1643
|
+
/* @__PURE__ */ jsx(AvatarFallback, { children: initials(participant.name) }),
|
|
1644
|
+
participant.holdingInput ? /* @__PURE__ */ jsx(AvatarBadge, {
|
|
1645
|
+
className: cn("bg-emerald-500", controlClaimed && "group-data-[size=sm]/avatar:size-3 group-data-[size=sm]/avatar:[&>svg]:!block group-data-[size=sm]/avatar:[&>svg]:size-2"),
|
|
1646
|
+
title: controlClaimed ? "Control claimed" : "Has input control",
|
|
1647
|
+
children: controlClaimed ? /* @__PURE__ */ jsx(Lock, {}) : /* @__PURE__ */ jsx(Radio, {})
|
|
1648
|
+
}) : null
|
|
1649
|
+
]
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
function gravatarURL(hash) {
|
|
1653
|
+
return `https://www.gravatar.com/avatar/${encodeURIComponent(hash)}?d=identicon&s=64`;
|
|
1654
|
+
}
|
|
1655
|
+
function initials(name) {
|
|
1656
|
+
return name.split(/\s+/).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("");
|
|
1657
|
+
}
|
|
1658
|
+
//#endregion
|
|
1659
|
+
//#region src/components/browser-toolbar.tsx
|
|
1660
|
+
function BrowserToolbar({ control, leading, features, collaborationRole, cdpUrl, shareUrls, localCursorEnabled, onLocalCursorChange, paintingEnabled, onPaintingEnabledChange, devToolsOpen, devToolsTargetIds, devToolsDock, onDevToolsOpenChange, onDevToolsDockChange, onSessionDetails }) {
|
|
1661
|
+
const [urlDraft, setUrlDraft] = useState(null);
|
|
1662
|
+
const displayUrl = control.activeTarget?.url ?? "";
|
|
1663
|
+
const busy = control.phase === "connecting";
|
|
1664
|
+
const connected = control.phase === "connected";
|
|
1665
|
+
const browserMutationEnabled = connected && collaborationRole !== "viewer";
|
|
1666
|
+
const drawingAvailable = connected && control.collaboration.phase === "connected" && Boolean(control.activeTargetId);
|
|
1667
|
+
const loading = control.activeTarget?.loading ?? false;
|
|
1668
|
+
const runningRecordings = control.recordings.filter((recording) => recording.status === "starting" || recording.status === "running");
|
|
1669
|
+
const hasRunningRecordings = runningRecordings.length > 0;
|
|
1670
|
+
const recordingTargetIds = new Set(runningRecordings.map((recording) => recording.targetId));
|
|
1671
|
+
const [recordingNow, setRecordingNow] = useState(Date.now());
|
|
1672
|
+
useFork(() => hasRunningRecordings ? Effect.repeat(Effect.sync(() => setRecordingNow(Date.now())), Schedule.spaced(1e3)) : void 0, [hasRunningRecordings]);
|
|
1673
|
+
function handleNavigate(value) {
|
|
1674
|
+
const nextUrl = value.trim();
|
|
1675
|
+
if (!nextUrl) return;
|
|
1676
|
+
control.navigate(normalizeUrl(nextUrl));
|
|
1677
|
+
setUrlDraft(null);
|
|
1678
|
+
}
|
|
1679
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1680
|
+
className: "flex min-w-0 flex-col bg-background",
|
|
1681
|
+
children: [leading || features.tabs || features.presence ? /* @__PURE__ */ jsxs("div", {
|
|
1682
|
+
"data-workbench-titlebar": true,
|
|
1683
|
+
className: "flex min-w-0 shrink-0 items-stretch border-b bg-muted/35",
|
|
1684
|
+
children: [
|
|
1685
|
+
leading,
|
|
1686
|
+
features.tabs ? /* @__PURE__ */ jsx(BrowserTabStrip, {
|
|
1687
|
+
targets: control.targets,
|
|
1688
|
+
activeTargetId: control.activeTargetId,
|
|
1689
|
+
recordingTargetIds,
|
|
1690
|
+
devToolsTargetIds,
|
|
1691
|
+
disabled: !connected,
|
|
1692
|
+
mutationDisabled: !browserMutationEnabled,
|
|
1693
|
+
onActivate: control.activateTarget,
|
|
1694
|
+
onCreate: () => control.createTarget("about:blank"),
|
|
1695
|
+
onDuplicate: control.duplicateTarget,
|
|
1696
|
+
onClose: control.closeTarget,
|
|
1697
|
+
onReload: control.reload,
|
|
1698
|
+
onReorder: control.reorderTargets
|
|
1699
|
+
}) : /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1" }),
|
|
1700
|
+
features.presence ? /* @__PURE__ */ jsx(CollaborationPresence, { collaboration: control.collaboration }) : null
|
|
1701
|
+
]
|
|
1702
|
+
}) : null, features.navigation || features.addressBar || features.drawing || features.devTools || features.menus ? /* @__PURE__ */ jsxs("div", {
|
|
1703
|
+
className: "flex h-9 items-center gap-1 px-1.5",
|
|
1704
|
+
children: [
|
|
1705
|
+
features.navigation ? /* @__PURE__ */ jsxs("div", {
|
|
1706
|
+
className: "flex shrink-0 items-center gap-0.5",
|
|
1707
|
+
children: [
|
|
1708
|
+
/* @__PURE__ */ jsx(ToolbarButton, {
|
|
1709
|
+
label: "Back",
|
|
1710
|
+
disabled: !browserMutationEnabled,
|
|
1711
|
+
onClick: () => control.historyBack(),
|
|
1712
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, {})
|
|
1713
|
+
}),
|
|
1714
|
+
/* @__PURE__ */ jsx(ToolbarButton, {
|
|
1715
|
+
label: "Forward",
|
|
1716
|
+
disabled: !browserMutationEnabled,
|
|
1717
|
+
onClick: () => control.historyForward(),
|
|
1718
|
+
children: /* @__PURE__ */ jsx(ArrowRight, {})
|
|
1719
|
+
}),
|
|
1720
|
+
/* @__PURE__ */ jsx(ToolbarButton, {
|
|
1721
|
+
label: loading ? "Stop loading" : "Reload",
|
|
1722
|
+
disabled: !browserMutationEnabled,
|
|
1723
|
+
onClick: () => {
|
|
1724
|
+
if (loading) control.stopLoading();
|
|
1725
|
+
else if (control.activeTargetId) control.reload(control.activeTargetId);
|
|
1726
|
+
},
|
|
1727
|
+
children: loading ? /* @__PURE__ */ jsx(Square, {}) : /* @__PURE__ */ jsx(RefreshCw, {})
|
|
1728
|
+
})
|
|
1729
|
+
]
|
|
1730
|
+
}) : null,
|
|
1731
|
+
features.addressBar ? /* @__PURE__ */ jsx(InputGroup, {
|
|
1732
|
+
className: "h-7 border-transparent bg-transparent transition-colors hover:border-input/50 hover:bg-muted/35 has-[[data-slot=input-group-control]:focus-visible]:border-input/70 has-[[data-slot=input-group-control]:focus-visible]:bg-background has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/20 dark:hover:bg-input/20",
|
|
1733
|
+
children: /* @__PURE__ */ jsx(InputGroupInput, {
|
|
1734
|
+
value: urlDraft ?? displayUrl,
|
|
1735
|
+
onChange: (event) => setUrlDraft(event.target.value),
|
|
1736
|
+
onFocus: (event) => event.currentTarget.select(),
|
|
1737
|
+
onKeyDown: (event) => {
|
|
1738
|
+
if (event.key === "Enter") {
|
|
1739
|
+
event.preventDefault();
|
|
1740
|
+
handleNavigate(event.currentTarget.value);
|
|
1741
|
+
}
|
|
1742
|
+
},
|
|
1743
|
+
placeholder: "URL",
|
|
1744
|
+
className: "h-7 px-2 font-mono text-xs text-muted-foreground transition-colors focus-visible:text-foreground",
|
|
1745
|
+
disabled: !browserMutationEnabled
|
|
1746
|
+
})
|
|
1747
|
+
}) : /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1" }),
|
|
1748
|
+
busy ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 shrink-0 animate-spin text-muted-foreground" }) : null,
|
|
1749
|
+
features.drawing ? /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
1750
|
+
render: /* @__PURE__ */ jsx(Button$1, {
|
|
1751
|
+
type: "button",
|
|
1752
|
+
variant: paintingEnabled ? "secondary" : "ghost",
|
|
1753
|
+
size: "icon-sm",
|
|
1754
|
+
className: "shrink-0",
|
|
1755
|
+
disabled: !paintingEnabled && !drawingAvailable,
|
|
1756
|
+
"aria-label": paintingEnabled ? "Stop drawing" : "Draw on this tab",
|
|
1757
|
+
"aria-pressed": paintingEnabled,
|
|
1758
|
+
onClick: () => onPaintingEnabledChange(!paintingEnabled)
|
|
1759
|
+
}),
|
|
1760
|
+
children: /* @__PURE__ */ jsx(Pencil, {})
|
|
1761
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
1762
|
+
side: "bottom",
|
|
1763
|
+
children: paintingEnabled ? "Stop drawing" : "Draw on this tab"
|
|
1764
|
+
})] }) : null,
|
|
1765
|
+
features.devTools ? /* @__PURE__ */ jsx(DevToolsButton, {
|
|
1766
|
+
open: devToolsOpen,
|
|
1767
|
+
dock: devToolsDock,
|
|
1768
|
+
available: collaborationRole === "owner" && connected && Boolean(cdpUrl && control.activeTargetId),
|
|
1769
|
+
onOpenChange: onDevToolsOpenChange,
|
|
1770
|
+
onDockChange: onDevToolsDockChange
|
|
1771
|
+
}) : null,
|
|
1772
|
+
features.menus ? /* @__PURE__ */ jsx(BrowserMenus, {
|
|
1773
|
+
control,
|
|
1774
|
+
cdpUrl,
|
|
1775
|
+
shareUrls,
|
|
1776
|
+
busy,
|
|
1777
|
+
connected,
|
|
1778
|
+
localCursorEnabled,
|
|
1779
|
+
onLocalCursorChange,
|
|
1780
|
+
onReconnect: () => control.reconnect(),
|
|
1781
|
+
onSessionDetails,
|
|
1782
|
+
now: recordingNow
|
|
1783
|
+
}) : null
|
|
1784
|
+
]
|
|
1785
|
+
}) : null]
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
function DevToolsButton({ open, dock, available, onOpenChange, onDockChange }) {
|
|
1789
|
+
const label = open ? "Close DevTools" : "Open DevTools";
|
|
1790
|
+
return /* @__PURE__ */ jsxs(ContextMenu$1, { children: [/* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
1791
|
+
render: /* @__PURE__ */ jsx(ContextMenuTrigger, { render: /* @__PURE__ */ jsx(Button$1, {
|
|
1792
|
+
type: "button",
|
|
1793
|
+
variant: open ? "secondary" : "ghost",
|
|
1794
|
+
size: "icon-sm",
|
|
1795
|
+
disabled: !open && !available,
|
|
1796
|
+
"aria-label": label,
|
|
1797
|
+
"aria-pressed": open,
|
|
1798
|
+
onClick: () => onOpenChange(!open)
|
|
1799
|
+
}) }),
|
|
1800
|
+
children: /* @__PURE__ */ jsx(Wrench, {})
|
|
1801
|
+
}), /* @__PURE__ */ jsxs(TooltipContent, {
|
|
1802
|
+
side: "bottom",
|
|
1803
|
+
children: [label, ". Right-click to choose dock side."]
|
|
1804
|
+
})] }), /* @__PURE__ */ jsx(ContextMenuContent, { children: /* @__PURE__ */ jsxs(ContextMenuGroup, { children: [/* @__PURE__ */ jsx(ContextMenuLabel, { children: "Dock side" }), /* @__PURE__ */ jsxs(ContextMenuRadioGroup, {
|
|
1805
|
+
value: dock,
|
|
1806
|
+
onValueChange: (value) => {
|
|
1807
|
+
switch (value) {
|
|
1808
|
+
case "bottom":
|
|
1809
|
+
onDockChange("bottom");
|
|
1810
|
+
break;
|
|
1811
|
+
case "right":
|
|
1812
|
+
onDockChange("right");
|
|
1813
|
+
break;
|
|
1814
|
+
}
|
|
1815
|
+
},
|
|
1816
|
+
children: [/* @__PURE__ */ jsxs(ContextMenuRadioItem, {
|
|
1817
|
+
value: "bottom",
|
|
1818
|
+
children: [/* @__PURE__ */ jsx(PanelBottom, {}), "Bottom"]
|
|
1819
|
+
}), /* @__PURE__ */ jsxs(ContextMenuRadioItem, {
|
|
1820
|
+
value: "right",
|
|
1821
|
+
children: [/* @__PURE__ */ jsx(PanelRight, {}), "Right"]
|
|
1822
|
+
})]
|
|
1823
|
+
})] }) })] });
|
|
1824
|
+
}
|
|
1825
|
+
function ToolbarButton({ label, disabled, onClick, children }) {
|
|
1826
|
+
return /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
1827
|
+
render: /* @__PURE__ */ jsx(Button$1, {
|
|
1828
|
+
type: "button",
|
|
1829
|
+
variant: "ghost",
|
|
1830
|
+
size: "icon-sm",
|
|
1831
|
+
disabled,
|
|
1832
|
+
onClick
|
|
1833
|
+
}),
|
|
1834
|
+
children
|
|
1835
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
1836
|
+
side: "bottom",
|
|
1837
|
+
children: label
|
|
1838
|
+
})] });
|
|
1839
|
+
}
|
|
1840
|
+
function normalizeUrl(value) {
|
|
1841
|
+
const trimmed = value.trim();
|
|
1842
|
+
if (isLocalHost(trimmed)) return `http://${trimmed}`;
|
|
1843
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) || /^(about|chrome|devtools|data|file):/i.test(trimmed)) return trimmed;
|
|
1844
|
+
if (isLikelyHost(trimmed)) return `https://${trimmed}`;
|
|
1845
|
+
return `https://www.google.com/search?q=${encodeURIComponent(trimmed)}`;
|
|
1846
|
+
}
|
|
1847
|
+
function isLocalHost(value) {
|
|
1848
|
+
return /^localhost(?::\d+)?(?:[/?#].*)?$/i.test(value) || /^127(?:\.\d{1,3}){3}(?::\d+)?(?:[/?#].*)?$/.test(value) || /^[a-z0-9-]+:\d+(?:[/?#].*)?$/i.test(value);
|
|
1849
|
+
}
|
|
1850
|
+
function isLikelyHost(value) {
|
|
1851
|
+
return !/\s/.test(value) && /^[a-z0-9-]+(?:\.[a-z0-9-]+)+(?:[/:?#].*)?$/i.test(value);
|
|
1852
|
+
}
|
|
1853
|
+
//#endregion
|
|
1854
|
+
//#region ../ui/src/components/badge.tsx
|
|
1855
|
+
const badgeVariants = cva("group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", {
|
|
1856
|
+
variants: { variant: {
|
|
1857
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
1858
|
+
secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
|
1859
|
+
destructive: "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
|
1860
|
+
outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
|
1861
|
+
ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
1862
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
1863
|
+
} },
|
|
1864
|
+
defaultVariants: { variant: "default" }
|
|
1865
|
+
});
|
|
1866
|
+
function Badge({ className, variant = "default", render, ...props }) {
|
|
1867
|
+
return useRender({
|
|
1868
|
+
defaultTagName: "span",
|
|
1869
|
+
props: mergeProps({ className: cn(badgeVariants({ variant }), className) }, props),
|
|
1870
|
+
render,
|
|
1871
|
+
state: {
|
|
1872
|
+
slot: "badge",
|
|
1873
|
+
variant
|
|
1874
|
+
}
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
//#endregion
|
|
1878
|
+
//#region src/notices.ts
|
|
1879
|
+
function showNotice(notice) {
|
|
1880
|
+
if (notice.level === "error") toast.error(notice.message);
|
|
1881
|
+
else toast.success(notice.message);
|
|
1882
|
+
}
|
|
1883
|
+
//#endregion
|
|
1884
|
+
//#region src/components/browser-viewport.tsx
|
|
1885
|
+
function BrowserViewport({ statusBadge = true, className, ...props }) {
|
|
1886
|
+
return /* @__PURE__ */ jsx(SessionViewport, {
|
|
1887
|
+
...props,
|
|
1888
|
+
className: className ?? "bg-background",
|
|
1889
|
+
onNotice: showNotice,
|
|
1890
|
+
renderOverlay: (overlay) => /* @__PURE__ */ jsx(ViewportOverlay, {
|
|
1891
|
+
overlay,
|
|
1892
|
+
statusBadge
|
|
1893
|
+
})
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
function ViewportOverlay({ overlay, statusBadge }) {
|
|
1897
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1898
|
+
overlay.placeholder ? /* @__PURE__ */ jsx("div", {
|
|
1899
|
+
className: "pointer-events-none absolute inset-0 flex items-center justify-center",
|
|
1900
|
+
children: /* @__PURE__ */ jsx(ViewportPlaceholder, { placeholder: overlay.placeholder })
|
|
1901
|
+
}) : null,
|
|
1902
|
+
statusBadge ? /* @__PURE__ */ jsx("div", {
|
|
1903
|
+
className: "pointer-events-none absolute right-2 bottom-2 flex items-center gap-1.5",
|
|
1904
|
+
children: /* @__PURE__ */ jsx(StatusBadge, { status: overlay.status })
|
|
1905
|
+
}) : null,
|
|
1906
|
+
overlay.followedCursor ? /* @__PURE__ */ jsxs("div", {
|
|
1907
|
+
className: "pointer-events-none absolute z-30 flex translate-x-[-2px] translate-y-[-2px] items-start text-primary drop-shadow-sm",
|
|
1908
|
+
style: {
|
|
1909
|
+
left: overlay.followedCursor.x,
|
|
1910
|
+
top: overlay.followedCursor.y
|
|
1911
|
+
},
|
|
1912
|
+
children: [/* @__PURE__ */ jsx(MousePointer2, { className: "size-5 fill-primary stroke-background stroke-[1.5]" }), /* @__PURE__ */ jsx("span", {
|
|
1913
|
+
className: "mt-4 -ml-1 rounded bg-primary px-1.5 py-0.5 text-[10px] leading-none font-medium whitespace-nowrap text-primary-foreground",
|
|
1914
|
+
children: overlay.followedCursor.name
|
|
1915
|
+
})]
|
|
1916
|
+
}) : null,
|
|
1917
|
+
overlay.cursorHint ? /* @__PURE__ */ jsx("div", {
|
|
1918
|
+
className: "pointer-events-none absolute z-20 max-w-64 translate-x-3 translate-y-3 rounded-md border bg-popover px-2 py-1 text-xs text-popover-foreground shadow-md",
|
|
1919
|
+
style: {
|
|
1920
|
+
left: overlay.cursorHint.x,
|
|
1921
|
+
top: overlay.cursorHint.y
|
|
1922
|
+
},
|
|
1923
|
+
children: overlay.cursorHint.text
|
|
1924
|
+
}) : null,
|
|
1925
|
+
overlay.collaborationError ? /* @__PURE__ */ jsx("div", {
|
|
1926
|
+
className: "pointer-events-none absolute bottom-10 left-2 max-w-[80%] rounded-md border border-amber-500/40 bg-background/90 px-2 py-1 text-xs text-amber-800 dark:text-amber-300",
|
|
1927
|
+
children: overlay.collaborationError
|
|
1928
|
+
}) : null
|
|
1929
|
+
] });
|
|
1930
|
+
}
|
|
1931
|
+
const placeholderLabels = {
|
|
1932
|
+
connecting: "Connecting",
|
|
1933
|
+
"connecting-media": "Connecting media",
|
|
1934
|
+
switching: "Switching target",
|
|
1935
|
+
disconnected: "Disconnected",
|
|
1936
|
+
waiting: "Waiting for frame"
|
|
1937
|
+
};
|
|
1938
|
+
function ViewportPlaceholder({ placeholder }) {
|
|
1939
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1940
|
+
className: "flex flex-col items-center gap-2 text-sm text-muted-foreground",
|
|
1941
|
+
children: [placeholder === "disconnected" ? /* @__PURE__ */ jsx(Unplug, { className: "size-5" }) : /* @__PURE__ */ jsx(Loader2, { className: "size-5 animate-spin" }), placeholderLabels[placeholder]]
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
function StatusBadge({ status }) {
|
|
1945
|
+
if (status === "webrtc" || status === "websocket") return /* @__PURE__ */ jsx(Badge, {
|
|
1946
|
+
variant: "secondary",
|
|
1947
|
+
className: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
|
|
1948
|
+
children: status
|
|
1949
|
+
});
|
|
1950
|
+
return /* @__PURE__ */ jsx(Badge, {
|
|
1951
|
+
variant: "outline",
|
|
1952
|
+
children: "offline"
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
//#endregion
|
|
1956
|
+
//#region src/components/browser-devtools-pane.tsx
|
|
1957
|
+
function BrowserDevToolsPane({ cdpUrl, targetId }) {
|
|
1958
|
+
const src = useMemo(() => {
|
|
1959
|
+
if (!cdpUrl || !targetId) return null;
|
|
1960
|
+
const cdp = new URL(cdpUrl);
|
|
1961
|
+
const cdpPath = cdp.pathname.replace(/\/$/, "");
|
|
1962
|
+
const frame = new URL(`${cdpPath}/devtools/devtools_app.html`, cdp.origin);
|
|
1963
|
+
const socket = `${cdp.host}${cdpPath}/devtools/page/${encodeURIComponent(targetId)}`;
|
|
1964
|
+
frame.searchParams.set(cdp.protocol === "https:" ? "wss" : "ws", socket);
|
|
1965
|
+
return frame.toString();
|
|
1966
|
+
}, [cdpUrl, targetId]);
|
|
1967
|
+
if (!src) return /* @__PURE__ */ jsx(Empty, {
|
|
1968
|
+
className: "h-full rounded-none border-none",
|
|
1969
|
+
children: /* @__PURE__ */ jsxs(EmptyHeader, { children: [
|
|
1970
|
+
/* @__PURE__ */ jsx(EmptyMedia, {
|
|
1971
|
+
variant: "icon",
|
|
1972
|
+
children: /* @__PURE__ */ jsx(Wrench, {})
|
|
1973
|
+
}),
|
|
1974
|
+
/* @__PURE__ */ jsx(EmptyTitle, { children: "DevTools unavailable" }),
|
|
1975
|
+
/* @__PURE__ */ jsx(EmptyDescription, { children: "Connect to a browser target to inspect it." })
|
|
1976
|
+
] })
|
|
1977
|
+
});
|
|
1978
|
+
return /* @__PURE__ */ jsx("iframe", {
|
|
1979
|
+
className: "h-full w-full border-0 bg-background",
|
|
1980
|
+
src,
|
|
1981
|
+
title: "Browser DevTools",
|
|
1982
|
+
allow: "clipboard-read; clipboard-write",
|
|
1983
|
+
referrerPolicy: "no-referrer"
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
//#endregion
|
|
1987
|
+
//#region ../ui/src/components/resizable.tsx
|
|
1988
|
+
function ResizablePanelGroup({ className, ...props }) {
|
|
1989
|
+
return /* @__PURE__ */ jsx(ResizablePrimitive.Group, {
|
|
1990
|
+
"data-slot": "resizable-panel-group",
|
|
1991
|
+
className: cn("flex h-full w-full aria-[orientation=vertical]:flex-col", className),
|
|
1992
|
+
...props
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
function ResizablePanel({ ...props }) {
|
|
1996
|
+
return /* @__PURE__ */ jsx(ResizablePrimitive.Panel, {
|
|
1997
|
+
"data-slot": "resizable-panel",
|
|
1998
|
+
...props
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
function ResizableHandle({ withHandle, className, ...props }) {
|
|
2002
|
+
return /* @__PURE__ */ jsx(ResizablePrimitive.Separator, {
|
|
2003
|
+
"data-slot": "resizable-handle",
|
|
2004
|
+
className: cn("relative flex w-px items-center justify-center bg-border ring-offset-background after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90", className),
|
|
2005
|
+
...props,
|
|
2006
|
+
children: withHandle && /* @__PURE__ */ jsx("div", { className: "z-10 flex h-6 w-1 shrink-0 rounded-lg bg-border" })
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
//#endregion
|
|
2010
|
+
//#region src/components/browser-control-pane.tsx
|
|
2011
|
+
const LOCAL_CURSOR_STORAGE_KEY = "aperture.workbench.localCursorEnabled";
|
|
2012
|
+
function BrowserControlPane({ control, leading, features: featureOverrides, collaborationRole, cdpUrl, shareUrls, onSessionDetails }) {
|
|
2013
|
+
const features = resolveSessionFeatures(featureOverrides);
|
|
2014
|
+
const [localCursorEnabled, setLocalCursorEnabled] = useState(true);
|
|
2015
|
+
const [paintingEnabled, setPaintingEnabled] = useState(false);
|
|
2016
|
+
const [devToolsTargetIds, setDevToolsTargetIds] = useState(() => /* @__PURE__ */ new Set());
|
|
2017
|
+
const [devToolsDock, setDevToolsDock] = useState("bottom");
|
|
2018
|
+
const devToolsPanelRef = usePanelRef();
|
|
2019
|
+
const activeTargetId = control.activeTargetId;
|
|
2020
|
+
const devToolsOpen = features.devTools && activeTargetId !== null && devToolsTargetIds.has(activeTargetId);
|
|
2021
|
+
useEffect(() => {
|
|
2022
|
+
try {
|
|
2023
|
+
if (window.localStorage.getItem(LOCAL_CURSOR_STORAGE_KEY) === "false") setLocalCursorEnabled(false);
|
|
2024
|
+
} catch {}
|
|
2025
|
+
}, []);
|
|
2026
|
+
useEffect(() => {
|
|
2027
|
+
const targetIds = new Set(control.targets.map((target) => target.id));
|
|
2028
|
+
setDevToolsTargetIds((current) => {
|
|
2029
|
+
let changed = false;
|
|
2030
|
+
const next = /* @__PURE__ */ new Set();
|
|
2031
|
+
for (const targetId of current) if (targetIds.has(targetId)) next.add(targetId);
|
|
2032
|
+
else changed = true;
|
|
2033
|
+
return changed ? next : current;
|
|
2034
|
+
});
|
|
2035
|
+
}, [control.targets]);
|
|
2036
|
+
useLayoutEffect(() => {
|
|
2037
|
+
if (devToolsOpen) devToolsPanelRef.current?.expand();
|
|
2038
|
+
else devToolsPanelRef.current?.collapse();
|
|
2039
|
+
}, [devToolsOpen, devToolsPanelRef]);
|
|
2040
|
+
function handleDevToolsOpenChange(open) {
|
|
2041
|
+
if (!activeTargetId) return;
|
|
2042
|
+
setDevToolsTargetIds((current) => {
|
|
2043
|
+
const next = new Set(current);
|
|
2044
|
+
if (open) next.add(activeTargetId);
|
|
2045
|
+
else next.delete(activeTargetId);
|
|
2046
|
+
return next;
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
function handleLocalCursorChange(enabled) {
|
|
2050
|
+
setLocalCursorEnabled(enabled);
|
|
2051
|
+
try {
|
|
2052
|
+
window.localStorage.setItem(LOCAL_CURSOR_STORAGE_KEY, String(enabled));
|
|
2053
|
+
} catch {}
|
|
2054
|
+
}
|
|
2055
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2056
|
+
className: "flex h-full min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
|
|
2057
|
+
children: [/* @__PURE__ */ jsx(BrowserToolbar, {
|
|
2058
|
+
control,
|
|
2059
|
+
leading,
|
|
2060
|
+
features,
|
|
2061
|
+
collaborationRole,
|
|
2062
|
+
cdpUrl,
|
|
2063
|
+
shareUrls,
|
|
2064
|
+
localCursorEnabled,
|
|
2065
|
+
onLocalCursorChange: handleLocalCursorChange,
|
|
2066
|
+
paintingEnabled,
|
|
2067
|
+
onPaintingEnabledChange: setPaintingEnabled,
|
|
2068
|
+
devToolsOpen,
|
|
2069
|
+
devToolsTargetIds,
|
|
2070
|
+
devToolsDock,
|
|
2071
|
+
onDevToolsOpenChange: handleDevToolsOpenChange,
|
|
2072
|
+
onDevToolsDockChange: setDevToolsDock,
|
|
2073
|
+
onSessionDetails
|
|
2074
|
+
}), /* @__PURE__ */ jsxs(ResizablePanelGroup, {
|
|
2075
|
+
orientation: devToolsDock === "bottom" ? "vertical" : "horizontal",
|
|
2076
|
+
className: "min-h-0 min-w-0 flex-1 has-[[data-separator=active]]:[&_iframe]:pointer-events-none",
|
|
2077
|
+
children: [
|
|
2078
|
+
/* @__PURE__ */ jsx(ResizablePanel, {
|
|
2079
|
+
className: "flex min-h-0 min-w-0",
|
|
2080
|
+
defaultSize: "60%",
|
|
2081
|
+
minSize: "20%",
|
|
2082
|
+
children: /* @__PURE__ */ jsx(BrowserViewport, {
|
|
2083
|
+
control,
|
|
2084
|
+
viewport: control.viewport,
|
|
2085
|
+
localCursorEnabled,
|
|
2086
|
+
paintingEnabled: features.drawing && paintingEnabled,
|
|
2087
|
+
onPaintingEnabledChange: setPaintingEnabled,
|
|
2088
|
+
statusBadge: features.statusBadge
|
|
2089
|
+
})
|
|
2090
|
+
}),
|
|
2091
|
+
/* @__PURE__ */ jsx(ResizableHandle, {
|
|
2092
|
+
withHandle: true,
|
|
2093
|
+
disabled: !devToolsOpen,
|
|
2094
|
+
hidden: !devToolsOpen
|
|
2095
|
+
}),
|
|
2096
|
+
/* @__PURE__ */ jsx(ResizablePanel, {
|
|
2097
|
+
panelRef: devToolsPanelRef,
|
|
2098
|
+
className: "flex min-h-0 min-w-0",
|
|
2099
|
+
collapsible: true,
|
|
2100
|
+
defaultSize: "40%",
|
|
2101
|
+
minSize: "20%",
|
|
2102
|
+
children: Array.from(features.devTools ? devToolsTargetIds : [], (targetId) => /* @__PURE__ */ jsx("div", {
|
|
2103
|
+
className: "h-full min-h-0 w-full min-w-0",
|
|
2104
|
+
hidden: targetId !== activeTargetId,
|
|
2105
|
+
children: /* @__PURE__ */ jsx(BrowserDevToolsPane, {
|
|
2106
|
+
cdpUrl,
|
|
2107
|
+
targetId
|
|
2108
|
+
})
|
|
2109
|
+
}, targetId))
|
|
2110
|
+
})
|
|
2111
|
+
]
|
|
2112
|
+
})]
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
//#endregion
|
|
2116
|
+
//#region src/aperture-session.tsx
|
|
2117
|
+
function ApertureSession({ baseUrl, theme = "system", className, ...props }) {
|
|
2118
|
+
const [root, setRoot] = useState(null);
|
|
2119
|
+
const dark = useDarkTheme(theme);
|
|
2120
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2121
|
+
ref: setRoot,
|
|
2122
|
+
className: cn("aperture-root relative h-full w-full", dark && "dark", className),
|
|
2123
|
+
children: root ? /* @__PURE__ */ jsx(ApertureProvider, {
|
|
2124
|
+
baseUrl,
|
|
2125
|
+
children: /* @__PURE__ */ jsx(PortalContainerProvider, {
|
|
2126
|
+
container: root,
|
|
2127
|
+
children: /* @__PURE__ */ jsxs(TooltipProvider, { children: [/* @__PURE__ */ jsx(SharedSession, { ...props }), props.features?.toaster !== false ? /* @__PURE__ */ jsx(Toaster$1, {
|
|
2128
|
+
theme: dark ? "dark" : "light",
|
|
2129
|
+
richColors: true,
|
|
2130
|
+
closeButton: true,
|
|
2131
|
+
position: "bottom-center"
|
|
2132
|
+
}) : null] })
|
|
2133
|
+
})
|
|
2134
|
+
}) : null
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
function useDarkTheme(theme) {
|
|
2138
|
+
const [systemDark, setSystemDark] = useState(false);
|
|
2139
|
+
useEffect(() => {
|
|
2140
|
+
if (theme !== "system") return;
|
|
2141
|
+
const query = window.matchMedia("(prefers-color-scheme: dark)");
|
|
2142
|
+
const update = () => setSystemDark(query.matches);
|
|
2143
|
+
update();
|
|
2144
|
+
query.addEventListener("change", update);
|
|
2145
|
+
return () => query.removeEventListener("change", update);
|
|
2146
|
+
}, [theme]);
|
|
2147
|
+
return theme === "dark" || theme === "system" && systemDark;
|
|
2148
|
+
}
|
|
2149
|
+
function SharedSession({ token, features, leading }) {
|
|
2150
|
+
const { status, share, control } = useSharedSession({
|
|
2151
|
+
token,
|
|
2152
|
+
onNotice: showNotice
|
|
2153
|
+
});
|
|
2154
|
+
switch (status) {
|
|
2155
|
+
case "invalid": return /* @__PURE__ */ jsx(SessionState, {
|
|
2156
|
+
icon: /* @__PURE__ */ jsx(Link2Off, {}),
|
|
2157
|
+
title: "Invalid share link",
|
|
2158
|
+
description: "This link does not contain a valid session capability."
|
|
2159
|
+
});
|
|
2160
|
+
case "loading": return /* @__PURE__ */ jsx(SessionState, {
|
|
2161
|
+
icon: /* @__PURE__ */ jsx(Loader2, { className: "animate-spin" }),
|
|
2162
|
+
title: "Opening shared session"
|
|
2163
|
+
});
|
|
2164
|
+
case "expired": return /* @__PURE__ */ jsx(SessionState, {
|
|
2165
|
+
icon: /* @__PURE__ */ jsx(Link2Off, {}),
|
|
2166
|
+
title: "Share link expired",
|
|
2167
|
+
description: "This session capability has expired. Ask the session owner for a new link."
|
|
2168
|
+
});
|
|
2169
|
+
case "unavailable": return /* @__PURE__ */ jsx(SessionState, {
|
|
2170
|
+
icon: /* @__PURE__ */ jsx(Link2Off, {}),
|
|
2171
|
+
title: "Shared session unavailable",
|
|
2172
|
+
description: "This link is invalid, revoked, or the shared session is no longer available."
|
|
2173
|
+
});
|
|
2174
|
+
case "ready": return /* @__PURE__ */ jsx("div", {
|
|
2175
|
+
className: "flex h-full min-h-0 flex-1 flex-col overflow-hidden bg-background",
|
|
2176
|
+
children: /* @__PURE__ */ jsx(BrowserControlPane, {
|
|
2177
|
+
control,
|
|
2178
|
+
collaborationRole: share?.role ?? "viewer",
|
|
2179
|
+
cdpUrl: null,
|
|
2180
|
+
shareUrls: null,
|
|
2181
|
+
leading,
|
|
2182
|
+
features: {
|
|
2183
|
+
devTools: false,
|
|
2184
|
+
...features
|
|
2185
|
+
}
|
|
2186
|
+
})
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
function devToolsUrl(origin, cdpUrl, sessionToken) {
|
|
2191
|
+
const sourceUrl = new URL(cdpUrl, origin);
|
|
2192
|
+
const url = new URL(origin);
|
|
2193
|
+
url.pathname = `${sourceUrl.pathname.replace(/\/$/, "")}/${encodeURIComponent(sessionToken)}`;
|
|
2194
|
+
return url.toString();
|
|
2195
|
+
}
|
|
2196
|
+
function SessionState({ icon, title, description }) {
|
|
2197
|
+
return /* @__PURE__ */ jsx(Empty, {
|
|
2198
|
+
className: "h-full border-none",
|
|
2199
|
+
children: /* @__PURE__ */ jsxs(EmptyHeader, { children: [
|
|
2200
|
+
/* @__PURE__ */ jsx(EmptyMedia, {
|
|
2201
|
+
variant: "icon",
|
|
2202
|
+
children: icon
|
|
2203
|
+
}),
|
|
2204
|
+
/* @__PURE__ */ jsx(EmptyTitle, { children: title }),
|
|
2205
|
+
description ? /* @__PURE__ */ jsx(EmptyDescription, { children: description }) : null
|
|
2206
|
+
] })
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
//#endregion
|
|
2210
|
+
export { ApertureProvider, ApertureSession, BrowserControlPane, BrowserViewport, ClipboardError, LiveSessionError, RuntimeProvider, RuntimeProviderMissingError, SessionViewport, SharedSession, collaborationPaintLifetimeMs, copyText, copyTextWithToast, devToolsUrl, makeApertureRuntime, parseShareToken, resolveSessionFeatures, showNotice, useBaseUrl, useBrowserControl, useEffectCallback, useFork, useLiveSession, useRunApi, useRuntime, useSharedSession };
|
|
2211
|
+
|
|
2212
|
+
//# sourceMappingURL=index.js.map
|