@mcp-b/react-components 0.40.0 → 0.42.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/dist/{AgentChat-CVO7xyLC.d.ts → AgentChat-DVr2giwD.d.ts} +1 -1
- package/dist/{AgentToolRunsContext-BCVqCtJ1.d.ts → AgentToolRunsContext-daY67hFM.d.ts} +12 -0
- package/dist/{CodeBlock-CWk1FVtm.js → CodeBlock-BjELA-_s.js} +1 -1
- package/dist/{FileTree-xqHYBw4g.js → FileTree-DMYRdT6R.js} +1 -1
- package/dist/McpConnectorForm-MRm1r59e.d.ts +39 -0
- package/dist/{Message-DBsHHhcM.d.ts → Message-D_vCVEGB.d.ts} +20 -1
- package/dist/{ThinkChatActivity-D6-_5Phw.d.ts → ThinkChatActivity-CsDql-ER.d.ts} +4 -1
- package/dist/{ThinkExecutionRun-lYYgyeE1.d.ts → ThinkExecutionRun-k8bpyXqt.d.ts} +3 -3
- package/dist/{WorkspaceFileView-Lab3om3F.js → WorkspaceFileView-DwdXiKku.js} +17 -22
- package/dist/{WorkspaceMessageResponse-Cy28WGSz.d.ts → WorkspaceMessageResponse-D66ty4FO.d.ts} +1 -1
- package/dist/components/agents-sdk/AgentActivity.js +4 -3
- package/dist/components/agents-sdk/AgentChat.d.ts +1 -1
- package/dist/components/agents-sdk/AgentChat.js +42 -18
- package/dist/components/agents-sdk/AgentToolRunsContext.d.ts +1 -1
- package/dist/components/agents-sdk/AgentToolRunsContext.js +12 -0
- package/dist/components/agents-sdk/BrowserLiveScreen.d.ts +135 -0
- package/dist/components/agents-sdk/BrowserLiveScreen.js +637 -0
- package/dist/components/agents-sdk/BrowserLiveView.d.ts +2 -2
- package/dist/components/agents-sdk/BrowserLiveView.js +6 -9
- package/dist/components/agents-sdk/BrowserReplay.js +1 -1
- package/dist/components/agents-sdk/BrowserRunPanel.d.ts +1 -1
- package/dist/components/agents-sdk/BrowserRunPanel.js +8 -13
- package/dist/components/agents-sdk/ManagerOffice.d.ts +168 -0
- package/dist/components/agents-sdk/ManagerOffice.js +698 -0
- package/dist/components/agents-sdk/McpConnectionRequest.js +3 -3
- package/dist/components/agents-sdk/McpConnectorForm.d.ts +1 -38
- package/dist/components/agents-sdk/McpPluginCatalog.d.ts +8 -2
- package/dist/components/agents-sdk/McpPluginCatalog.js +82 -62
- package/dist/components/agents-sdk/McpServerPicker.d.ts +1 -1
- package/dist/components/agents-sdk/McpServerPicker.js +2 -2
- package/dist/components/agents-sdk/ModelSelector.js +1 -1
- package/dist/components/agents-sdk/PromptInputCommands.js +1 -1
- package/dist/components/agents-sdk/SubagentRunDetails.d.ts +1 -9
- package/dist/components/agents-sdk/SubagentRunDetails.js +1 -9
- package/dist/components/agents-sdk/SubagentRunIndicator.js +1 -1
- package/dist/components/agents-sdk/SubagentRunSelector.d.ts +4 -2
- package/dist/components/agents-sdk/SubagentRunSelector.js +7 -6
- package/dist/components/agents-sdk/ThinkChat.d.ts +4 -4
- package/dist/components/agents-sdk/ThinkChatActivity.d.ts +1 -1
- package/dist/components/agents-sdk/ThinkChatActivity.js +18 -4
- package/dist/components/agents-sdk/ThinkChatToolPanels.js +7 -3
- package/dist/components/agents-sdk/ThinkExecutionDetail.d.ts +1 -1
- package/dist/components/agents-sdk/ThinkExecutionDetail.js +45 -43
- package/dist/components/agents-sdk/ThinkExecutionRun.d.ts +2 -2
- package/dist/components/agents-sdk/ThinkExecutionRun.js +5 -6
- package/dist/components/agents-sdk/ThinkExecutionStatus.d.ts +1 -1
- package/dist/components/agents-sdk/ThinkExecutionStatus.js +8 -12
- package/dist/components/agents-sdk/ThinkExecutionWorkbench.d.ts +8 -1
- package/dist/components/agents-sdk/ThinkExecutionWorkbench.js +43 -37
- package/dist/components/agents-sdk/ThinkFullApp.d.ts +42 -78
- package/dist/components/agents-sdk/ThinkFullApp.js +121 -202
- package/dist/components/agents-sdk/ThinkFullAppViewSwitch.d.ts +1 -1
- package/dist/components/agents-sdk/ThinkRoutine.d.ts +1 -1
- package/dist/components/agents-sdk/ThinkRoutineBrowser.js +1 -1
- package/dist/components/agents-sdk/ThinkRoutineDetail.js +4 -15
- package/dist/components/agents-sdk/ThinkRoutineForm.js +1 -1
- package/dist/components/agents-sdk/ThinkRoutineLibrary.d.ts +7 -6
- package/dist/components/agents-sdk/ThinkRoutineLibrary.js +112 -140
- package/dist/components/agents-sdk/ThinkRoutineRuns.d.ts +1 -1
- package/dist/components/agents-sdk/ThinkRoutineRuns.js +3 -10
- package/dist/components/agents-sdk/ThinkThreadPicker.js +2 -1
- package/dist/components/agents-sdk/WorkspaceBrowser.js +1 -1
- package/dist/components/agents-sdk/WorkspaceExplorer.js +2 -1
- package/dist/components/agents-sdk/WorkspaceFileView.js +1 -1
- package/dist/components/agents-sdk/WorkspaceHeader.js +2 -1
- package/dist/components/agents-sdk/WorkspaceMessageResponse.d.ts +1 -1
- package/dist/components/agents-sdk/WorkspaceMountManager.js +11 -11
- package/dist/components/ai-sdk/Activity.js +1 -1
- package/dist/components/ai-sdk/AgentAttention.d.ts +1 -1
- package/dist/components/ai-sdk/AgentChatStartScreen.js +1 -1
- package/dist/components/ai-sdk/Approval.js +1 -1
- package/dist/components/ai-sdk/Artifact.js +1 -1
- package/dist/components/ai-sdk/ChainOfThought.d.ts +1 -1
- package/dist/components/ai-sdk/ChainOfThought.js +1 -1
- package/dist/components/ai-sdk/Checkpoint.js +1 -1
- package/dist/components/ai-sdk/CodeBlock.js +1 -1
- package/dist/components/ai-sdk/Conversation.js +1 -1
- package/dist/components/ai-sdk/Goal.js +1 -1
- package/dist/components/ai-sdk/MediaPreviewCard.js +1 -1
- package/dist/components/ai-sdk/Message.d.ts +2 -2
- package/dist/components/ai-sdk/Message.js +25 -3
- package/dist/components/ai-sdk/MessageScroller.js +3 -2
- package/dist/components/ai-sdk/MessageScrollerVirtual.js +2 -1
- package/dist/components/ai-sdk/Plan.js +1 -1
- package/dist/components/ai-sdk/PromptInput.js +21 -15
- package/dist/components/ai-sdk/Queue.d.ts +4 -4
- package/dist/components/ai-sdk/Queue.js +6 -15
- package/dist/components/ai-sdk/Reasoning.js +1 -1
- package/dist/components/ai-sdk/Sources.js +1 -1
- package/dist/components/ai-sdk/Task.d.ts +2 -2
- package/dist/components/ai-sdk/Task.js +2 -2
- package/dist/components/extension/ChatGptSignInScreen.js +2 -1
- package/dist/components/extension/CodexModelSelector.js +1 -1
- package/dist/components/extension/ConversationTabPicker.js +1 -1
- package/dist/components/foundations/Avatar.d.ts +1 -10
- package/dist/components/foundations/Avatar.js +1 -10
- package/dist/components/foundations/Checkbox.js +1 -1
- package/dist/components/foundations/CheckboxGroup.js +1 -0
- package/dist/components/foundations/ContextMenu.js +1 -1
- package/dist/components/foundations/LinkButton.d.ts +12 -10
- package/dist/components/foundations/LinkButton.js +15 -11
- package/dist/components/foundations/Menubar.js +1 -1
- package/dist/components/foundations/SearchField.js +1 -1
- package/dist/components/foundations/Select.js +1 -1
- package/dist/components/foundations/Stepper.js +1 -1
- package/dist/components/foundations/Toast.js +1 -1
- package/dist/components/foundations/ToggleGroup.d.ts +79 -1
- package/dist/components/foundations/icons.d.ts +2 -2
- package/dist/components/foundations/icons.js +2 -2
- package/dist/components/general-purpose/AppFrame.js +1 -0
- package/dist/components/general-purpose/AppTopBar.d.ts +6 -0
- package/dist/components/general-purpose/AppTopBar.js +2 -1
- package/dist/components/general-purpose/Commit.d.ts +2 -2
- package/dist/components/general-purpose/Commit.js +5 -13
- package/dist/components/general-purpose/EnvironmentVariables.d.ts +2 -2
- package/dist/components/general-purpose/EnvironmentVariables.js +4 -7
- package/dist/components/general-purpose/FileTree.js +1 -1
- package/dist/components/general-purpose/PricingBillingToggle.js +4 -9
- package/dist/components/general-purpose/PricingPlanCard.js +15 -14
- package/dist/components/general-purpose/ResizableSidePanel.d.ts +8 -0
- package/dist/components/general-purpose/ResizableSidePanel.js +3 -2
- package/dist/components/general-purpose/ResponsiveSidebarShell.d.ts +105 -2
- package/dist/components/general-purpose/ResponsiveSidebarShell.js +112 -56
- package/dist/components/general-purpose/SetupChecklist.js +1 -1
- package/dist/components/general-purpose/Terminal.d.ts +1 -1
- package/dist/components/general-purpose/Terminal.js +4 -12
- package/dist/components/general-purpose/TestResults.d.ts +1 -1
- package/dist/components/general-purpose/TestResults.js +8 -21
- package/dist/components/general-purpose/ThemeModeControl.js +1 -1
- package/dist/components/general-purpose/WebPreview.d.ts +4 -1
- package/dist/components/general-purpose/WebPreview.js +6 -14
- package/dist/components/nanites/AgentConnection.js +2 -2
- package/dist/components/nanites/AgentToolDetail.d.ts +1 -1
- package/dist/components/nanites/AgentToolDetail.js +6 -5
- package/dist/components/nanites/NaniteScene.js +11 -11
- package/dist/{copy-button-Bc7jsYv_.js → copy-button-lys9N_aI.js} +1 -1
- package/dist/{icons-DwgNir1Q.d.ts → icons-0corjfxR.d.ts} +1 -16
- package/dist/{icons-MmIXciMO.js → icons-CVFSeTJn.js} +2 -5
- package/dist/styles/accordion.css +1 -0
- package/dist/styles/action-guide.css +8 -7
- package/dist/styles/activity-header.css +13 -39
- package/dist/styles/activity.css +40 -55
- package/dist/styles/agent-attention.css +19 -19
- package/dist/styles/agent-chat.css +94 -42
- package/dist/styles/agent-connection.css +10 -4
- package/dist/styles/agent-tool-detail.css +5 -15
- package/dist/styles/alert-dialog.css +4 -4
- package/dist/styles/app-frame.css +1 -3
- package/dist/styles/app-icon.css +12 -18
- package/dist/styles/app-top-bar.css +31 -9
- package/dist/styles/approval.css +1 -0
- package/dist/styles/artifact.css +6 -6
- package/dist/styles/attachment-preview.css +5 -4
- package/dist/styles/avatar.css +6 -22
- package/dist/styles/base.css +2 -8
- package/dist/styles/browser-live-screen.css +38 -0
- package/dist/styles/browser-live-view.css +12 -4
- package/dist/styles/browser-replay.css +6 -6
- package/dist/styles/browser-run-panel.css +21 -18
- package/dist/styles/button.css +15 -6
- package/dist/styles/card.css +5 -5
- package/dist/styles/chatgpt-sign-in-screen.css +6 -6
- package/dist/styles/code-block.css +14 -13
- package/dist/styles/codex-model-selector.css +2 -0
- package/dist/styles/collapsible.css +1 -0
- package/dist/styles/commit.css +30 -7
- package/dist/styles/context.css +9 -9
- package/dist/styles/conversation-tab-picker.css +1 -1
- package/dist/styles/conversation.css +6 -8
- package/dist/styles/dialog.css +4 -4
- package/dist/styles/empty-state.css +3 -3
- package/dist/styles/environment-variables.css +12 -9
- package/dist/styles/field.css +2 -1
- package/dist/styles/fieldset.css +2 -2
- package/dist/styles/index.css +8 -7
- package/dist/styles/manager-office.css +480 -0
- package/dist/styles/mcp-plugin-catalog.css +44 -39
- package/dist/styles/mcp-server-picker.css +4 -4
- package/dist/styles/message-scroller.css +9 -11
- package/dist/styles/message.css +74 -28
- package/dist/styles/nanite-scene.css +9 -9
- package/dist/styles/navigation-menu.css +1 -0
- package/dist/styles/plan.css +13 -12
- package/dist/styles/pricing-page.css +37 -30
- package/dist/styles/prompt-input.css +26 -18
- package/dist/styles/queue.css +1 -0
- package/dist/styles/reasoning.css +1 -9
- package/dist/styles/recipes.css +29 -58
- package/dist/styles/responsive-sidebar-shell.css +107 -29
- package/dist/styles/separator.css +5 -5
- package/dist/styles/sources.css +4 -4
- package/dist/styles/stepper.css +1 -0
- package/dist/styles/streamdown.css +31 -21
- package/dist/styles/subagent-run.css +20 -3
- package/dist/styles/tabs.css +1 -0
- package/dist/styles/task.css +5 -17
- package/dist/styles/terminal.css +29 -10
- package/dist/styles/test-results.css +16 -7
- package/dist/styles/think-execution-status.css +10 -26
- package/dist/styles/think-execution-workbench.css +98 -108
- package/dist/styles/think-full-app.css +75 -225
- package/dist/styles/think-routine-library.css +26 -95
- package/dist/styles/think-thread-picker.css +17 -12
- package/dist/styles/transition-surface.css +7 -8
- package/dist/styles/utilities.css +75 -0
- package/dist/styles/web-preview.css +46 -22
- package/dist/styles/workspace-explorer.css +22 -44
- package/dist/styles/workspace-mount-manager.css +24 -2
- package/dist/utils/favicon.d.ts +6 -1
- package/dist/utils/favicon.js +18 -1
- package/package.json +37 -35
- package/dist/ResponsiveSidebarShell-DWjULskY.d.ts +0 -68
- package/dist/ToggleGroup-_pQMyHGM.d.ts +0 -80
- package/dist/components/agents-sdk/AgentActivityPreview.d.ts +0 -46
- package/dist/components/agents-sdk/AgentActivityPreview.js +0 -93
- package/dist/components/agents-sdk/ThinkThreadShell.d.ts +0 -38
- package/dist/components/agents-sdk/ThinkThreadShell.js +0 -27
- package/dist/components/foundations/Badge.d.ts +0 -62
- package/dist/components/foundations/Badge.js +0 -55
- package/dist/components/foundations/StatusPill.d.ts +0 -43
- package/dist/components/foundations/StatusPill.js +0 -35
- package/dist/components/general-purpose/Shell.d.ts +0 -149
- package/dist/components/general-purpose/Shell.js +0 -116
- package/dist/styles/agent-activity-preview-overlay.css +0 -169
- package/dist/styles/agent-activity-preview.css +0 -209
- package/dist/styles/badge.css +0 -120
- package/dist/styles/shell.css +0 -122
- package/dist/styles/status-pill.css +0 -39
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import { E as KeyboardIcon, H as RefreshIcon, r as ArrowLeftIcon } from "../../icons-CVFSeTJn.js";
|
|
2
|
+
import { Button } from "../foundations/Button.js";
|
|
3
|
+
import { WebPreviewNavigationButton } from "../general-purpose/WebPreview.js";
|
|
4
|
+
import { BrowserLiveView } from "./BrowserLiveView.js";
|
|
5
|
+
import { BrowserRunPanel } from "./BrowserRunPanel.js";
|
|
6
|
+
import { useCallback, useEffect, useEffectEvent, useRef, useState } from "react";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/components/agents-sdk/BrowserLiveScreen.tsx
|
|
9
|
+
const BROWSER_LIVE_SCREEN_NEGOTIATION_TIMEOUT_MS = 1e4;
|
|
10
|
+
/**
|
|
11
|
+
* A complete interactive viewer for an employee's claimed browser tabs.
|
|
12
|
+
*
|
|
13
|
+
* The host supplies only signaling transport, ICE configuration, and claimed
|
|
14
|
+
* tab summaries. This component owns the peer connection, remote-input
|
|
15
|
+
* mapping, reconnect lifecycle, synthesized browser chrome, and cleanup.
|
|
16
|
+
*/
|
|
17
|
+
function BrowserLiveScreen({ sendSignal: sendHostSignal, subscribeSignals, protocol, getIceServers, label = "Employee browser", controlLabel = "Control employee browser", tabs = [], requestedTabId }) {
|
|
18
|
+
const video = useRef(null);
|
|
19
|
+
const track = useRef(null);
|
|
20
|
+
const attachVideo = useCallback((element) => {
|
|
21
|
+
video.current = element;
|
|
22
|
+
if (element) element.srcObject = track.current;
|
|
23
|
+
}, []);
|
|
24
|
+
const showTrack = useCallback((stream) => {
|
|
25
|
+
track.current = stream;
|
|
26
|
+
if (video.current) video.current.srcObject = stream;
|
|
27
|
+
}, []);
|
|
28
|
+
const channel = useRef(null);
|
|
29
|
+
const geometry = useRef(null);
|
|
30
|
+
const [phase, setPhase] = useState("connecting");
|
|
31
|
+
const [failure, setFailure] = useState(null);
|
|
32
|
+
const sendSignal = useEffectEvent((viewerId, payload) => sendHostSignal({
|
|
33
|
+
viewerId,
|
|
34
|
+
payload
|
|
35
|
+
}));
|
|
36
|
+
const loadIceServers = useEffectEvent(async () => await getIceServers?.() ?? []);
|
|
37
|
+
const subscribe = useEffectEvent((listener) => subscribeSignals(listener));
|
|
38
|
+
const parseStatus = useEffectEvent((value) => protocol.parseStatus(value));
|
|
39
|
+
const serializeInput = useEffectEvent((value) => protocol.serializeInput(value));
|
|
40
|
+
const serializeCommand = useEffectEvent((value) => protocol.serializeCommand(value));
|
|
41
|
+
const selectRequestedTab = useEffectEvent((data) => {
|
|
42
|
+
if (requestedTabId === void 0 || data?.readyState !== "open") return;
|
|
43
|
+
data.send(serializeCommand({
|
|
44
|
+
kind: "selectTab",
|
|
45
|
+
tabId: requestedTabId
|
|
46
|
+
}));
|
|
47
|
+
});
|
|
48
|
+
const dataChannelLabel = protocol.dataChannelLabel;
|
|
49
|
+
const input = useRef(null);
|
|
50
|
+
const handleWheel = useEffectEvent((event) => {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
const point = toPagePoint(event);
|
|
53
|
+
if (!point) return;
|
|
54
|
+
sendInput([{
|
|
55
|
+
kind: "mouse",
|
|
56
|
+
type: "mouseWheel",
|
|
57
|
+
...point,
|
|
58
|
+
deltaX: event.deltaX,
|
|
59
|
+
deltaY: event.deltaY,
|
|
60
|
+
modifiers: modifiers(event)
|
|
61
|
+
}]);
|
|
62
|
+
});
|
|
63
|
+
const attachInput = useCallback((element) => {
|
|
64
|
+
input.current?.removeEventListener("wheel", handleWheel);
|
|
65
|
+
input.current = element;
|
|
66
|
+
element?.addEventListener("wheel", handleWheel, { passive: false });
|
|
67
|
+
}, []);
|
|
68
|
+
const [keyboardOpen, setKeyboardOpen] = useState(false);
|
|
69
|
+
const touch = useRef(null);
|
|
70
|
+
const mousePress = useRef(null);
|
|
71
|
+
const [streamed, setStreamed] = useState(null);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
let disposed = false;
|
|
74
|
+
let connection;
|
|
75
|
+
let viewerId = "";
|
|
76
|
+
let attempt = 0;
|
|
77
|
+
let pendingCandidates = [];
|
|
78
|
+
let retryTimer;
|
|
79
|
+
let negotiationTimer;
|
|
80
|
+
let signaling = Promise.resolve();
|
|
81
|
+
function clearRetry() {
|
|
82
|
+
if (retryTimer !== void 0) clearTimeout(retryTimer);
|
|
83
|
+
retryTimer = void 0;
|
|
84
|
+
}
|
|
85
|
+
function clearNegotiationTimeout() {
|
|
86
|
+
if (negotiationTimer !== void 0) clearTimeout(negotiationTimer);
|
|
87
|
+
negotiationTimer = void 0;
|
|
88
|
+
}
|
|
89
|
+
function closeCurrent(notify) {
|
|
90
|
+
const closing = connection;
|
|
91
|
+
const closingViewerId = viewerId;
|
|
92
|
+
connection = void 0;
|
|
93
|
+
channel.current = null;
|
|
94
|
+
geometry.current = null;
|
|
95
|
+
if (!disposed) setStreamed(null);
|
|
96
|
+
showTrack(null);
|
|
97
|
+
pendingCandidates = [];
|
|
98
|
+
signaling = Promise.resolve();
|
|
99
|
+
clearNegotiationTimeout();
|
|
100
|
+
closing?.close();
|
|
101
|
+
if (notify && closingViewerId) sendSignal(closingViewerId, { kind: "close" }).catch(() => void 0);
|
|
102
|
+
}
|
|
103
|
+
function scheduleReconnect(error) {
|
|
104
|
+
if (disposed) return;
|
|
105
|
+
clearNegotiationTimeout();
|
|
106
|
+
setPhase("reconnecting");
|
|
107
|
+
if (error !== void 0) setFailure(error instanceof Error ? error.message : "Live view connection failed.");
|
|
108
|
+
retryTimer ??= setTimeout(() => {
|
|
109
|
+
retryTimer = void 0;
|
|
110
|
+
restart();
|
|
111
|
+
}, RECONNECT_DELAY_MS);
|
|
112
|
+
}
|
|
113
|
+
const unsubscribe = subscribe((signal) => {
|
|
114
|
+
if (signal.viewerId !== viewerId) return;
|
|
115
|
+
const payload = signal.payload;
|
|
116
|
+
const signalViewerId = signal.viewerId;
|
|
117
|
+
signaling = signaling.then(async () => {
|
|
118
|
+
const peer = connection;
|
|
119
|
+
if (!peer || signalViewerId !== viewerId) return;
|
|
120
|
+
if (payload.kind === "answer") {
|
|
121
|
+
await peer.setRemoteDescription({
|
|
122
|
+
type: "answer",
|
|
123
|
+
sdp: payload.sdp
|
|
124
|
+
});
|
|
125
|
+
if (peer !== connection || signalViewerId !== viewerId) return;
|
|
126
|
+
for (const candidate of pendingCandidates.splice(0)) await peer.addIceCandidate(candidate);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (payload.kind === "candidate") {
|
|
130
|
+
if (!peer.remoteDescription) {
|
|
131
|
+
pendingCandidates.push(payload.candidate);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
await peer.addIceCandidate(payload.candidate);
|
|
135
|
+
}
|
|
136
|
+
}).catch((error) => {
|
|
137
|
+
if (signalViewerId === viewerId) scheduleReconnect(error);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
async function connect() {
|
|
141
|
+
const currentAttempt = ++attempt;
|
|
142
|
+
try {
|
|
143
|
+
await connectAs(currentAttempt);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (currentAttempt === attempt) scheduleReconnect(error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function connectAs(currentAttempt) {
|
|
149
|
+
const currentViewerId = crypto.randomUUID();
|
|
150
|
+
viewerId = currentViewerId;
|
|
151
|
+
setFailure(null);
|
|
152
|
+
clearNegotiationTimeout();
|
|
153
|
+
negotiationTimer = setTimeout(() => {
|
|
154
|
+
if (currentAttempt === attempt) scheduleReconnect(/* @__PURE__ */ new Error("The employee machine did not answer the live view."));
|
|
155
|
+
}, BROWSER_LIVE_SCREEN_NEGOTIATION_TIMEOUT_MS);
|
|
156
|
+
const iceServers = await loadIceServers();
|
|
157
|
+
if (disposed || currentAttempt !== attempt) return;
|
|
158
|
+
const peer = new RTCPeerConnection({ iceServers });
|
|
159
|
+
connection = peer;
|
|
160
|
+
pendingCandidates = [];
|
|
161
|
+
peer.ontrack = (event) => {
|
|
162
|
+
if (peer !== connection) return;
|
|
163
|
+
showTrack(event.streams[0] ?? new MediaStream([event.track]));
|
|
164
|
+
};
|
|
165
|
+
peer.onicecandidate = (event) => {
|
|
166
|
+
if (event.candidate && peer === connection) sendSignal(currentViewerId, {
|
|
167
|
+
kind: "candidate",
|
|
168
|
+
candidate: event.candidate.toJSON()
|
|
169
|
+
}).catch((error) => {
|
|
170
|
+
if (peer === connection && currentViewerId === viewerId) scheduleReconnect(error);
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
peer.onconnectionstatechange = () => {
|
|
174
|
+
if (peer !== connection) return;
|
|
175
|
+
if (peer.connectionState === "connected") {
|
|
176
|
+
clearRetry();
|
|
177
|
+
clearNegotiationTimeout();
|
|
178
|
+
setFailure(null);
|
|
179
|
+
setPhase((current) => current === "reconnecting" ? geometry.current ? "streaming" : "idle" : current);
|
|
180
|
+
}
|
|
181
|
+
if (peer.connectionState === "failed" || peer.connectionState === "disconnected") scheduleReconnect();
|
|
182
|
+
};
|
|
183
|
+
const data = peer.createDataChannel(dataChannelLabel);
|
|
184
|
+
channel.current = data;
|
|
185
|
+
data.onopen = () => selectRequestedTab(data);
|
|
186
|
+
selectRequestedTab(data);
|
|
187
|
+
data.onmessage = (event) => {
|
|
188
|
+
const status = parseStatus(event.data);
|
|
189
|
+
if (!status) return;
|
|
190
|
+
if (status.kind === "idle") {
|
|
191
|
+
geometry.current = null;
|
|
192
|
+
setFailure(null);
|
|
193
|
+
setPhase("idle");
|
|
194
|
+
setStreamed(null);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (status.kind === "streaming") {
|
|
198
|
+
geometry.current = status;
|
|
199
|
+
setFailure(null);
|
|
200
|
+
setPhase("streaming");
|
|
201
|
+
setStreamed(status);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
setFailure(status.message);
|
|
205
|
+
};
|
|
206
|
+
peer.addTransceiver("video", { direction: "recvonly" });
|
|
207
|
+
const offer = await peer.createOffer();
|
|
208
|
+
await peer.setLocalDescription(offer);
|
|
209
|
+
if (disposed || peer !== connection) return;
|
|
210
|
+
await sendSignal(currentViewerId, {
|
|
211
|
+
kind: "offer",
|
|
212
|
+
sdp: offer.sdp ?? ""
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function restart() {
|
|
216
|
+
clearRetry();
|
|
217
|
+
closeCurrent(true);
|
|
218
|
+
if (disposed) return;
|
|
219
|
+
connect();
|
|
220
|
+
}
|
|
221
|
+
connect();
|
|
222
|
+
return () => {
|
|
223
|
+
disposed = true;
|
|
224
|
+
attempt += 1;
|
|
225
|
+
clearRetry();
|
|
226
|
+
unsubscribe();
|
|
227
|
+
closeCurrent(true);
|
|
228
|
+
};
|
|
229
|
+
}, [dataChannelLabel]);
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
selectRequestedTab(channel.current);
|
|
232
|
+
}, [requestedTabId]);
|
|
233
|
+
function sendInput(events) {
|
|
234
|
+
const open = channel.current;
|
|
235
|
+
const status = geometry.current;
|
|
236
|
+
if (open?.readyState !== "open" || !status || events.length === 0) return;
|
|
237
|
+
open.send(serializeInput({
|
|
238
|
+
tabId: status.tabId,
|
|
239
|
+
streamGeneration: status.streamGeneration,
|
|
240
|
+
events
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* A browser-chrome command needs only the channel, not a live stream:
|
|
245
|
+
* picking a tab is exactly what a viewer does while nothing is on screen.
|
|
246
|
+
*/
|
|
247
|
+
function sendCommand(command) {
|
|
248
|
+
const open = channel.current;
|
|
249
|
+
if (open?.readyState !== "open") return;
|
|
250
|
+
open.send(serializeCommand(command));
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* How the displayed picture maps to page CSS pixels, computed from the
|
|
254
|
+
* video's current on-screen box. The machine never rescales: it sends the
|
|
255
|
+
* geometry with each change and the viewer, which is the side that knows how
|
|
256
|
+
* big it drew the picture, does the arithmetic.
|
|
257
|
+
*
|
|
258
|
+
* A touch gesture freezes one of these at its first contact, because the
|
|
259
|
+
* soft keyboard opening or closing mid-gesture moves the video and would
|
|
260
|
+
* otherwise put the release somewhere the press never was.
|
|
261
|
+
*/
|
|
262
|
+
function currentViewGeometry() {
|
|
263
|
+
const element = video.current;
|
|
264
|
+
const status = geometry.current;
|
|
265
|
+
if (!element || !status) return null;
|
|
266
|
+
const bounds = element.getBoundingClientRect();
|
|
267
|
+
const sourceWidth = element.videoWidth || status.metadata.deviceWidth;
|
|
268
|
+
const sourceHeight = element.videoHeight || status.metadata.deviceHeight;
|
|
269
|
+
if (bounds.width === 0 || bounds.height === 0 || sourceWidth <= 0 || sourceHeight <= 0) return null;
|
|
270
|
+
const containScale = Math.min(bounds.width / sourceWidth, bounds.height / sourceHeight);
|
|
271
|
+
const renderedWidth = sourceWidth * containScale;
|
|
272
|
+
const renderedHeight = sourceHeight * containScale;
|
|
273
|
+
return {
|
|
274
|
+
renderedLeft: bounds.left + (bounds.width - renderedWidth) / 2,
|
|
275
|
+
renderedTop: bounds.top + (bounds.height - renderedHeight) / 2,
|
|
276
|
+
renderedWidth,
|
|
277
|
+
renderedHeight,
|
|
278
|
+
zoomX: renderedWidth / status.metadata.deviceWidth,
|
|
279
|
+
zoomY: renderedHeight / status.metadata.deviceHeight,
|
|
280
|
+
offsetTop: status.metadata.offsetTop
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function toPagePoint(event) {
|
|
284
|
+
const view = currentViewGeometry();
|
|
285
|
+
return view ? pointWithin(view, event) : null;
|
|
286
|
+
}
|
|
287
|
+
function beginTouch(event) {
|
|
288
|
+
if (touch.current) return;
|
|
289
|
+
const view = currentViewGeometry();
|
|
290
|
+
const startPage = view ? pointWithin(view, event) : null;
|
|
291
|
+
if (!view || !startPage) return;
|
|
292
|
+
const client = {
|
|
293
|
+
x: event.clientX,
|
|
294
|
+
y: event.clientY
|
|
295
|
+
};
|
|
296
|
+
const gesture = {
|
|
297
|
+
pointerId: event.pointerId,
|
|
298
|
+
mode: "pending",
|
|
299
|
+
view,
|
|
300
|
+
startClient: client,
|
|
301
|
+
startPage,
|
|
302
|
+
lastClient: client,
|
|
303
|
+
lastPage: startPage,
|
|
304
|
+
holdTimer: setTimeout(() => {
|
|
305
|
+
gesture.mode = "drag";
|
|
306
|
+
sendInput([move(startPage, 0), press(startPage)]);
|
|
307
|
+
}, TOUCH_HOLD_MS)
|
|
308
|
+
};
|
|
309
|
+
touch.current = gesture;
|
|
310
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
311
|
+
}
|
|
312
|
+
function moveTouch(event) {
|
|
313
|
+
const gesture = touch.current;
|
|
314
|
+
if (!gesture || event.pointerId !== gesture.pointerId) return;
|
|
315
|
+
const client = {
|
|
316
|
+
x: event.clientX,
|
|
317
|
+
y: event.clientY
|
|
318
|
+
};
|
|
319
|
+
if (gesture.mode === "pending") {
|
|
320
|
+
if (Math.hypot(client.x - gesture.startClient.x, client.y - gesture.startClient.y) <= TOUCH_TAP_SLOP_PX) return;
|
|
321
|
+
clearTimeout(gesture.holdTimer);
|
|
322
|
+
gesture.mode = "scroll";
|
|
323
|
+
}
|
|
324
|
+
if (gesture.mode === "scroll") {
|
|
325
|
+
const deltaX = (gesture.lastClient.x - client.x) / gesture.view.zoomX;
|
|
326
|
+
const deltaY = (gesture.lastClient.y - client.y) / gesture.view.zoomY;
|
|
327
|
+
gesture.lastClient = client;
|
|
328
|
+
if (deltaX !== 0 || deltaY !== 0) sendInput([{
|
|
329
|
+
kind: "mouse",
|
|
330
|
+
type: "mouseWheel",
|
|
331
|
+
...gesture.startPage,
|
|
332
|
+
deltaX,
|
|
333
|
+
deltaY
|
|
334
|
+
}]);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const point = pointWithin(gesture.view, event);
|
|
338
|
+
if (!point) return;
|
|
339
|
+
gesture.lastPage = point;
|
|
340
|
+
sendInput([move(point, 1)]);
|
|
341
|
+
}
|
|
342
|
+
function endTouch(event, cancelled) {
|
|
343
|
+
const gesture = touch.current;
|
|
344
|
+
if (!gesture || event.pointerId !== gesture.pointerId) return;
|
|
345
|
+
touch.current = null;
|
|
346
|
+
clearTimeout(gesture.holdTimer);
|
|
347
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
348
|
+
if (gesture.mode === "pending") {
|
|
349
|
+
if (cancelled) return;
|
|
350
|
+
sendInput([
|
|
351
|
+
move(gesture.startPage, 0),
|
|
352
|
+
press(gesture.startPage),
|
|
353
|
+
release(gesture.startPage)
|
|
354
|
+
]);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (gesture.mode === "drag") sendInput([release(gesture.lastPage)]);
|
|
358
|
+
}
|
|
359
|
+
const notice = failure ?? (phase === "streaming" ? void 0 : PHASE_TEXT[phase]);
|
|
360
|
+
const streamedTab = streamed ? tabs.find((tab) => tab.tabId === streamed.tabId) : void 0;
|
|
361
|
+
const displayUrl = streamed?.pageUrl ?? streamedTab?.url ?? "";
|
|
362
|
+
const view = /* @__PURE__ */ jsx(BrowserLiveView, {
|
|
363
|
+
expandable: false,
|
|
364
|
+
fill: true,
|
|
365
|
+
phase,
|
|
366
|
+
statusLabel: notice,
|
|
367
|
+
renderMedia: (_placement, statusId) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
368
|
+
/* @__PURE__ */ jsx("video", {
|
|
369
|
+
ref: attachVideo,
|
|
370
|
+
autoPlay: true,
|
|
371
|
+
playsInline: true,
|
|
372
|
+
muted: true,
|
|
373
|
+
"aria-hidden": "true"
|
|
374
|
+
}),
|
|
375
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
376
|
+
"aria-describedby": notice ? statusId : void 0,
|
|
377
|
+
ref: attachInput,
|
|
378
|
+
"aria-label": controlLabel,
|
|
379
|
+
autoCapitalize: "none",
|
|
380
|
+
autoComplete: "off",
|
|
381
|
+
className: "browser-live-screen__input",
|
|
382
|
+
inputMode: keyboardOpen ? "text" : "none",
|
|
383
|
+
spellCheck: false,
|
|
384
|
+
onBeforeInput: (event) => {
|
|
385
|
+
const nativeInput = event.nativeEvent;
|
|
386
|
+
if (nativeInput.isComposing) return;
|
|
387
|
+
if (nativeInput.inputType === "insertLineBreak" || nativeInput.inputType === "insertParagraph") {
|
|
388
|
+
event.preventDefault();
|
|
389
|
+
event.currentTarget.value = "";
|
|
390
|
+
sendInput([{
|
|
391
|
+
kind: "key",
|
|
392
|
+
type: "keyDown",
|
|
393
|
+
key: "Enter",
|
|
394
|
+
code: "Enter",
|
|
395
|
+
windowsVirtualKeyCode: 13,
|
|
396
|
+
text: "\r",
|
|
397
|
+
modifiers: 0
|
|
398
|
+
}, {
|
|
399
|
+
kind: "key",
|
|
400
|
+
type: "keyUp",
|
|
401
|
+
key: "Enter",
|
|
402
|
+
code: "Enter",
|
|
403
|
+
windowsVirtualKeyCode: 13,
|
|
404
|
+
modifiers: 0
|
|
405
|
+
}]);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (!nativeInput.data) return;
|
|
409
|
+
event.preventDefault();
|
|
410
|
+
event.currentTarget.value = "";
|
|
411
|
+
sendInput([{
|
|
412
|
+
kind: "text",
|
|
413
|
+
text: nativeInput.data
|
|
414
|
+
}]);
|
|
415
|
+
},
|
|
416
|
+
onInput: (event) => {
|
|
417
|
+
if ("isComposing" in event.nativeEvent && event.nativeEvent.isComposing) return;
|
|
418
|
+
const text = event.currentTarget.value;
|
|
419
|
+
event.currentTarget.value = "";
|
|
420
|
+
if (text) sendInput([{
|
|
421
|
+
kind: "text",
|
|
422
|
+
text
|
|
423
|
+
}]);
|
|
424
|
+
},
|
|
425
|
+
onKeyDown: (event) => {
|
|
426
|
+
if (event.nativeEvent.isComposing) return;
|
|
427
|
+
if (event.key === "Enter") event.preventDefault();
|
|
428
|
+
sendInput([{
|
|
429
|
+
kind: "key",
|
|
430
|
+
type: "keyDown",
|
|
431
|
+
key: event.key,
|
|
432
|
+
code: event.code,
|
|
433
|
+
windowsVirtualKeyCode: event.keyCode,
|
|
434
|
+
...event.key === "Enter" ? { text: "\r" } : {},
|
|
435
|
+
modifiers: modifiers(event)
|
|
436
|
+
}]);
|
|
437
|
+
},
|
|
438
|
+
onKeyUp: (event) => {
|
|
439
|
+
if (event.nativeEvent.isComposing) return;
|
|
440
|
+
sendInput([{
|
|
441
|
+
kind: "key",
|
|
442
|
+
type: "keyUp",
|
|
443
|
+
key: event.key,
|
|
444
|
+
code: event.code,
|
|
445
|
+
windowsVirtualKeyCode: event.keyCode,
|
|
446
|
+
modifiers: modifiers(event)
|
|
447
|
+
}]);
|
|
448
|
+
},
|
|
449
|
+
onPaste: (event) => {
|
|
450
|
+
event.preventDefault();
|
|
451
|
+
const text = event.clipboardData.getData("text");
|
|
452
|
+
if (text) sendInput([{
|
|
453
|
+
kind: "text",
|
|
454
|
+
text
|
|
455
|
+
}]);
|
|
456
|
+
},
|
|
457
|
+
onContextMenu: (event) => {
|
|
458
|
+
event.preventDefault();
|
|
459
|
+
},
|
|
460
|
+
onPointerDown: (event) => {
|
|
461
|
+
if (event.pointerType === "touch") {
|
|
462
|
+
beginTouch(event);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const point = toPagePoint(event);
|
|
466
|
+
if (point) {
|
|
467
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
468
|
+
mousePress.current = point;
|
|
469
|
+
sendInput([press(point)]);
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
onPointerMove: (event) => {
|
|
473
|
+
if (event.pointerType === "touch") {
|
|
474
|
+
moveTouch(event);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const point = toPagePoint(event);
|
|
478
|
+
if (point) {
|
|
479
|
+
if (mousePress.current) mousePress.current = point;
|
|
480
|
+
sendInput([move(point, event.buttons)]);
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
onPointerUp: (event) => {
|
|
484
|
+
if (event.pointerType === "touch") {
|
|
485
|
+
endTouch(event, false);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const point = toPagePoint(event) ?? mousePress.current;
|
|
489
|
+
mousePress.current = null;
|
|
490
|
+
if (point) sendInput([release(point)]);
|
|
491
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
492
|
+
},
|
|
493
|
+
onPointerCancel: (event) => {
|
|
494
|
+
if (event.pointerType === "touch") {
|
|
495
|
+
endTouch(event, true);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const point = toPagePoint(event) ?? mousePress.current;
|
|
499
|
+
mousePress.current = null;
|
|
500
|
+
if (point) sendInput([release(point)]);
|
|
501
|
+
}
|
|
502
|
+
}),
|
|
503
|
+
/* @__PURE__ */ jsx(Button, {
|
|
504
|
+
"aria-label": keyboardOpen ? "Hide keyboard" : "Show keyboard",
|
|
505
|
+
"aria-pressed": keyboardOpen,
|
|
506
|
+
className: "browser-live-screen__keyboard",
|
|
507
|
+
color: "neutral",
|
|
508
|
+
iconOnly: true,
|
|
509
|
+
size: "sm",
|
|
510
|
+
variant: "ghost",
|
|
511
|
+
onClick: () => {
|
|
512
|
+
const element = input.current;
|
|
513
|
+
const open = !keyboardOpen;
|
|
514
|
+
setKeyboardOpen(open);
|
|
515
|
+
if (!element) return;
|
|
516
|
+
element.inputMode = open ? "text" : "none";
|
|
517
|
+
if (open) element.focus();
|
|
518
|
+
else element.blur();
|
|
519
|
+
},
|
|
520
|
+
children: /* @__PURE__ */ jsx(KeyboardIcon, {})
|
|
521
|
+
})
|
|
522
|
+
] })
|
|
523
|
+
});
|
|
524
|
+
return /* @__PURE__ */ jsx(BrowserRunPanel, {
|
|
525
|
+
label,
|
|
526
|
+
tabs: tabs.map((tab) => ({
|
|
527
|
+
id: String(tab.tabId),
|
|
528
|
+
title: tab.title || tab.url || `Tab ${String(tab.tabId)}`,
|
|
529
|
+
...tab.url === void 0 ? {} : { url: tab.url }
|
|
530
|
+
})),
|
|
531
|
+
selectedTabId: streamed ? String(streamed.tabId) : null,
|
|
532
|
+
onSelectTab: (id) => sendCommand({
|
|
533
|
+
kind: "selectTab",
|
|
534
|
+
tabId: Number(id)
|
|
535
|
+
}),
|
|
536
|
+
url: displayUrl,
|
|
537
|
+
onOpenUrl: (next) => {
|
|
538
|
+
if (!streamed) return;
|
|
539
|
+
const url = normalizeOpenUrl(next);
|
|
540
|
+
if (url) sendCommand({
|
|
541
|
+
kind: "open",
|
|
542
|
+
tabId: streamed.tabId,
|
|
543
|
+
url
|
|
544
|
+
});
|
|
545
|
+
},
|
|
546
|
+
urlLabel: "Streamed page URL",
|
|
547
|
+
urlPlaceholder: streamed ? "Open a page…" : "Nothing is streaming",
|
|
548
|
+
urlDisabled: !streamed,
|
|
549
|
+
navigation: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(WebPreviewNavigationButton, {
|
|
550
|
+
label: "Back",
|
|
551
|
+
disabled: !streamed,
|
|
552
|
+
onClick: () => streamed && sendCommand({
|
|
553
|
+
kind: "navigate",
|
|
554
|
+
tabId: streamed.tabId,
|
|
555
|
+
action: "back"
|
|
556
|
+
}),
|
|
557
|
+
children: /* @__PURE__ */ jsx(ArrowLeftIcon, {})
|
|
558
|
+
}), /* @__PURE__ */ jsx(WebPreviewNavigationButton, {
|
|
559
|
+
label: "Reload",
|
|
560
|
+
disabled: !streamed,
|
|
561
|
+
onClick: () => streamed && sendCommand({
|
|
562
|
+
kind: "navigate",
|
|
563
|
+
tabId: streamed.tabId,
|
|
564
|
+
action: "reload"
|
|
565
|
+
}),
|
|
566
|
+
children: /* @__PURE__ */ jsx(RefreshIcon, {})
|
|
567
|
+
})] }),
|
|
568
|
+
children: view
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* What a thumb types is rarely a URL: absent scheme becomes https, and
|
|
573
|
+
* anything that still is not an http(s) page is refused here, mirroring the
|
|
574
|
+
* machine's own validation rather than surprising it.
|
|
575
|
+
*/
|
|
576
|
+
function normalizeOpenUrl(raw) {
|
|
577
|
+
const value = raw.trim();
|
|
578
|
+
if (!value) return null;
|
|
579
|
+
for (const candidate of [value, `https://${value}`]) {
|
|
580
|
+
let url;
|
|
581
|
+
try {
|
|
582
|
+
url = new URL(candidate);
|
|
583
|
+
} catch {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (url.protocol === "http:" || url.protocol === "https:") return url.href;
|
|
587
|
+
}
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
const press = (p) => ({
|
|
591
|
+
kind: "mouse",
|
|
592
|
+
type: "mousePressed",
|
|
593
|
+
...p,
|
|
594
|
+
button: "left",
|
|
595
|
+
buttons: 1,
|
|
596
|
+
clickCount: 1
|
|
597
|
+
});
|
|
598
|
+
const release = (p) => ({
|
|
599
|
+
kind: "mouse",
|
|
600
|
+
type: "mouseReleased",
|
|
601
|
+
...p,
|
|
602
|
+
button: "left",
|
|
603
|
+
buttons: 0,
|
|
604
|
+
clickCount: 1
|
|
605
|
+
});
|
|
606
|
+
const move = (p, buttons) => ({
|
|
607
|
+
kind: "mouse",
|
|
608
|
+
type: "mouseMoved",
|
|
609
|
+
...p,
|
|
610
|
+
buttons
|
|
611
|
+
});
|
|
612
|
+
/** A client point in the picture's own page coordinates, or null if outside it. */
|
|
613
|
+
function pointWithin(view, event) {
|
|
614
|
+
const renderedX = event.clientX - view.renderedLeft;
|
|
615
|
+
const renderedY = event.clientY - view.renderedTop;
|
|
616
|
+
if (renderedX < 0 || renderedY < 0 || renderedX > view.renderedWidth || renderedY > view.renderedHeight) return null;
|
|
617
|
+
return {
|
|
618
|
+
x: renderedX / view.zoomX,
|
|
619
|
+
y: renderedY / view.zoomY - view.offsetTop
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
/** CDP's modifier bitmask: alt, ctrl, meta, shift. */
|
|
623
|
+
function modifiers(event) {
|
|
624
|
+
return Number(event.altKey) | Number(event.ctrlKey) << 1 | Number(event.metaKey) << 2 | Number(event.shiftKey) << 3;
|
|
625
|
+
}
|
|
626
|
+
/** Finger travel that still reads as a tap, in the viewer's own pixels. */
|
|
627
|
+
const TOUCH_TAP_SLOP_PX = 8;
|
|
628
|
+
/** A hold this long turns the touch into a mouse drag instead of a scroll. */
|
|
629
|
+
const TOUCH_HOLD_MS = 450;
|
|
630
|
+
const RECONNECT_DELAY_MS = 1e3;
|
|
631
|
+
const PHASE_TEXT = {
|
|
632
|
+
connecting: "Connecting to the employee browser…",
|
|
633
|
+
idle: "Nothing is on screen. The employee browser appears here while it works.",
|
|
634
|
+
reconnecting: "Reconnecting…"
|
|
635
|
+
};
|
|
636
|
+
//#endregion
|
|
637
|
+
export { BrowserLiveScreen };
|
|
@@ -47,7 +47,7 @@ type BrowserLiveViewProps = BrowserLiveViewCommonProps & ({
|
|
|
47
47
|
* expects. This component owns the frame around them. An overlay drawn
|
|
48
48
|
* here covers the picture and stays under the frame's own chrome, so a
|
|
49
49
|
* control surface can take every pointer the picture gets without
|
|
50
|
-
* swallowing the
|
|
50
|
+
* swallowing the live status or the expand control.
|
|
51
51
|
*
|
|
52
52
|
* `statusId` names whatever element currently states the phase, for a
|
|
53
53
|
* control surface to point `aria-describedby` at.
|
|
@@ -64,7 +64,7 @@ type BrowserLiveViewProps = BrowserLiveViewCommonProps & ({
|
|
|
64
64
|
* `BrowserLiveViewUrl` — and it embeds the hosted viewer, expiring the frame
|
|
65
65
|
* on its own once `expiresInMs` passes. Give it `renderMedia` and a `phase`
|
|
66
66
|
* instead and the host draws the picture on its own transport while this keeps
|
|
67
|
-
* the frame around it. The
|
|
67
|
+
* the frame around it. The live status, connecting overlay, ended
|
|
68
68
|
* state, and the fullscreen expand are the same either way, because to the
|
|
69
69
|
* person watching it is the same thing.
|
|
70
70
|
*
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { t as cx } from "../../class-names-BiMDVpUo.js";
|
|
2
|
-
import {
|
|
2
|
+
import { K as SpinnerIcon, O as MaximizeIcon, _ as DotIcon } from "../../icons-CVFSeTJn.js";
|
|
3
3
|
import { Button } from "../foundations/Button.js";
|
|
4
4
|
import { Dialog } from "../foundations/Dialog.js";
|
|
5
|
-
import { StatusPill } from "../foundations/StatusPill.js";
|
|
6
5
|
import { TransitionSurface } from "../foundations/TransitionSurface.js";
|
|
7
6
|
import * as React from "react";
|
|
8
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -34,7 +33,7 @@ function EndedState({ title, detail }) {
|
|
|
34
33
|
* `BrowserLiveViewUrl` — and it embeds the hosted viewer, expiring the frame
|
|
35
34
|
* on its own once `expiresInMs` passes. Give it `renderMedia` and a `phase`
|
|
36
35
|
* instead and the host draws the picture on its own transport while this keeps
|
|
37
|
-
* the frame around it. The
|
|
36
|
+
* the frame around it. The live status, connecting overlay, ended
|
|
38
37
|
* state, and the fullscreen expand are the same either way, because to the
|
|
39
38
|
* person watching it is the same thing.
|
|
40
39
|
*
|
|
@@ -105,12 +104,10 @@ function BrowserLiveView(props) {
|
|
|
105
104
|
detail: pageTitle
|
|
106
105
|
});
|
|
107
106
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
108
|
-
live && placement === "inline" ? /* @__PURE__ */
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
className: "browser-live-view__badge",
|
|
113
|
-
children: "Live"
|
|
107
|
+
live && placement === "inline" ? /* @__PURE__ */ jsxs("span", {
|
|
108
|
+
className: "browser-live-view__status",
|
|
109
|
+
role: "status",
|
|
110
|
+
children: [/* @__PURE__ */ jsx(DotIcon, {}), "Live"]
|
|
114
111
|
}) : null,
|
|
115
112
|
source ? placement === "dialog" ? /* @__PURE__ */ jsx("iframe", {
|
|
116
113
|
src: source.url,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as cx } from "../../class-names-BiMDVpUo.js";
|
|
2
|
-
import {
|
|
2
|
+
import { L as PauseIcon, z as PlayIcon } from "../../icons-CVFSeTJn.js";
|
|
3
3
|
import { IconButton } from "../foundations/IconButton.js";
|
|
4
4
|
import { r as formatClock } from "../../format-DB2mX9DA.js";
|
|
5
5
|
import { EmptyState } from "../foundations/EmptyState.js";
|