@agent-native/core 0.175.2-nightly-20260826165737 → 0.175.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +1 -1
- package/corpus/templates/clips/desktop/src/app.tsx +171 -51
- package/corpus/templates/clips/desktop/src/lib/pill-completion.ts +111 -0
- package/corpus/templates/clips/desktop/src/lib/pill-session.ts +34 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +181 -79
- package/corpus/templates/clips/desktop/src/lib/rewind-recording-start.ts +28 -4
- package/corpus/templates/clips/desktop/src/main.tsx +4 -4
- package/corpus/templates/clips/desktop/src/overlays/record-pill.tsx +1320 -0
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +1 -1
- package/corpus/templates/clips/desktop/src/styles.css +20 -308
- package/corpus/templates/clips/desktop/src/tailwind.css +85 -0
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +153 -70
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +15 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +25 -7
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +3 -3
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +375 -24
- package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +22 -30
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +5 -0
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +134 -14
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +233 -4
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +8 -5
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +50 -28
- package/corpus/templates/slides/app/context/DeckContext.tsx +78 -23
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +5 -5
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/secrets/routes.d.ts +9 -9
- package/package.json +3 -3
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +0 -458
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
IconAlertTriangle,
|
|
3
|
-
IconLoader2,
|
|
4
|
-
IconPlayerPauseFilled,
|
|
5
|
-
IconPlayerPlayFilled,
|
|
6
|
-
IconRefresh,
|
|
7
|
-
IconTrash,
|
|
8
|
-
} from "@tabler/icons-react";
|
|
9
|
-
import { invoke } from "@tauri-apps/api/core";
|
|
10
|
-
import { LogicalSize } from "@tauri-apps/api/dpi";
|
|
11
|
-
import { emit, listen } from "@tauri-apps/api/event";
|
|
12
|
-
import { getCurrentWindow } from "@tauri-apps/api/window";
|
|
13
|
-
import { useEffect, useRef, useState } from "react";
|
|
14
|
-
import type { FocusEvent } from "react";
|
|
15
|
-
|
|
16
|
-
const OVERLAY_SHADOW_GUTTER = 18;
|
|
17
|
-
const TOOLBAR_CONTENT_WIDTH = 72;
|
|
18
|
-
const TOOLBAR_COLLAPSED_HEIGHT = 150;
|
|
19
|
-
// Collapsed content box (150 − 20 padding = 130) holds the centered primary
|
|
20
|
-
// zone; the expanded height must fit that fixed 130 zone + the 88px hover
|
|
21
|
-
// actions (+2 margin) + 20 vertical padding so nothing clips on hover.
|
|
22
|
-
const TOOLBAR_EXPANDED_HEIGHT = 240;
|
|
23
|
-
const TOOLBAR_WINDOW_WIDTH = TOOLBAR_CONTENT_WIDTH + OVERLAY_SHADOW_GUTTER * 2;
|
|
24
|
-
const TOOLBAR_COLLAPSED_WINDOW_HEIGHT =
|
|
25
|
-
TOOLBAR_COLLAPSED_HEIGHT + OVERLAY_SHADOW_GUTTER * 2;
|
|
26
|
-
const TOOLBAR_EXPANDED_WINDOW_HEIGHT =
|
|
27
|
-
TOOLBAR_EXPANDED_HEIGHT + OVERLAY_SHADOW_GUTTER * 2;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Floating recording toolbar — vertical pill anchored to the LEFT edge of
|
|
31
|
-
* the screen (Loom's placement). Big orange Stop at the top, elapsed time
|
|
32
|
-
* below, pause underneath. On hover, it grows downward to expose restart
|
|
33
|
-
* and cancel controls. Pure command emitter — the popover owns the
|
|
34
|
-
* MediaRecorder.
|
|
35
|
-
*
|
|
36
|
-
* IPC contract:
|
|
37
|
-
* receives → `clips:recorder-state` { paused, elapsedMs }
|
|
38
|
-
* emits → `clips:recorder-stop`, `:pause`, `:resume`, `:restart`, `:cancel`
|
|
39
|
-
*
|
|
40
|
-
* IMPORTANT: The Stop button MUST NOT close its own window. The popover's
|
|
41
|
-
* recorder listener is what drives the stop flow, and it invokes
|
|
42
|
-
* `hide_overlays` from the Rust side once the MediaRecorder has been
|
|
43
|
-
* flushed. Closing the toolbar window synchronously here races the
|
|
44
|
-
* IPC delivery: Tauri's `emit()` promise resolves when the event is
|
|
45
|
-
* queued on the wire, not when listeners have run — if we immediately
|
|
46
|
-
* `.close()` the emitting window, the popover listener can miss the
|
|
47
|
-
* event entirely (observed as: toolbar disappears, nothing else
|
|
48
|
-
* happens, user has to hit the tray icon to actually stop the
|
|
49
|
-
* recording). Let the recorder own the close.
|
|
50
|
-
*/
|
|
51
|
-
export function Toolbar() {
|
|
52
|
-
const [paused, setPaused] = useState(false);
|
|
53
|
-
const [elapsed, setElapsed] = useState(0);
|
|
54
|
-
const [pendingAction, setPendingAction] = useState<
|
|
55
|
-
"stop" | "restart" | "cancel" | null
|
|
56
|
-
>(null);
|
|
57
|
-
// Pre-record mode: the toolbar shows alongside the pre-record bubble so
|
|
58
|
-
// the user can drag both around and position them before hitting Start.
|
|
59
|
-
// Stop / Pause are disabled until the recorder actually begins, at which
|
|
60
|
-
// point `clips:toolbar-enabled` fires with `true` from the recorder.
|
|
61
|
-
const [enabled, setEnabled] = useState(false);
|
|
62
|
-
const [preparing, setPreparing] = useState(false);
|
|
63
|
-
const [popoverVisible, setPopoverVisible] = useState(true);
|
|
64
|
-
const [diskSpaceLevel, setDiskSpaceLevel] = useState<
|
|
65
|
-
"ok" | "warning" | "critical"
|
|
66
|
-
>("ok");
|
|
67
|
-
const fallbackTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
68
|
-
const expandedRef = useRef(false);
|
|
69
|
-
const pauseTransitionRef = useRef<"pause" | "resume" | null>(null);
|
|
70
|
-
const pauseTransitionTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
|
|
71
|
-
null,
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
function clearPauseTransition() {
|
|
75
|
-
pauseTransitionRef.current = null;
|
|
76
|
-
if (pauseTransitionTimerRef.current) {
|
|
77
|
-
clearTimeout(pauseTransitionTimerRef.current);
|
|
78
|
-
pauseTransitionTimerRef.current = null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
const unlistens: Array<() => void> = [];
|
|
84
|
-
const listenerPromises: Array<Promise<() => void>> = [];
|
|
85
|
-
let stopped = false;
|
|
86
|
-
// Same race-safe listen tracker as elsewhere: if this effect
|
|
87
|
-
// cleans up before `listen()` resolves, the unlisten is called
|
|
88
|
-
// immediately — otherwise the listener lingers for the life of
|
|
89
|
-
// the webview, holding the setState closures captive.
|
|
90
|
-
const trackListen = (p: Promise<() => void>) => {
|
|
91
|
-
listenerPromises.push(p);
|
|
92
|
-
p.then((u) => {
|
|
93
|
-
if (stopped) {
|
|
94
|
-
try {
|
|
95
|
-
u();
|
|
96
|
-
} catch {
|
|
97
|
-
// ignore
|
|
98
|
-
}
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
unlistens.push(u);
|
|
102
|
-
}).catch(() => {
|
|
103
|
-
// ignore
|
|
104
|
-
});
|
|
105
|
-
};
|
|
106
|
-
trackListen(
|
|
107
|
-
listen<{ paused: boolean; elapsedMs: number }>(
|
|
108
|
-
"clips:recorder-state",
|
|
109
|
-
(ev) => {
|
|
110
|
-
const nextPaused = !!ev.payload.paused;
|
|
111
|
-
const pendingTransition = pauseTransitionRef.current;
|
|
112
|
-
const transitionReached =
|
|
113
|
-
(pendingTransition === "pause" && nextPaused) ||
|
|
114
|
-
(pendingTransition === "resume" && !nextPaused);
|
|
115
|
-
// Native pause/resume can take a beat. Keep the first click's
|
|
116
|
-
// optimistic state instead of letting an in-flight timer tick briefly
|
|
117
|
-
// flip the button back and invite duplicate clicks.
|
|
118
|
-
if (!pendingTransition || transitionReached) {
|
|
119
|
-
setPaused(nextPaused);
|
|
120
|
-
}
|
|
121
|
-
if (transitionReached) clearPauseTransition();
|
|
122
|
-
setElapsed(ev.payload.elapsedMs ?? 0);
|
|
123
|
-
},
|
|
124
|
-
),
|
|
125
|
-
);
|
|
126
|
-
trackListen(
|
|
127
|
-
listen<boolean>("clips:toolbar-enabled", (ev) => {
|
|
128
|
-
setEnabled(!!ev.payload);
|
|
129
|
-
setPreparing(false);
|
|
130
|
-
setPendingAction(null);
|
|
131
|
-
if (!ev.payload) {
|
|
132
|
-
setDiskSpaceLevel("ok");
|
|
133
|
-
setPaused(false);
|
|
134
|
-
setElapsed(0);
|
|
135
|
-
}
|
|
136
|
-
}),
|
|
137
|
-
);
|
|
138
|
-
trackListen(
|
|
139
|
-
listen("clips:toolbar-sync", () => {
|
|
140
|
-
emit("clips:toolbar-ready", {}).catch(() => {});
|
|
141
|
-
}),
|
|
142
|
-
);
|
|
143
|
-
trackListen(
|
|
144
|
-
listen<boolean>("clips:toolbar-preparing", (ev) => {
|
|
145
|
-
setPreparing(!!ev.payload);
|
|
146
|
-
}),
|
|
147
|
-
);
|
|
148
|
-
trackListen(
|
|
149
|
-
listen<boolean>("clips:popover-visible", (ev) => {
|
|
150
|
-
setPopoverVisible(!!ev.payload);
|
|
151
|
-
}),
|
|
152
|
-
);
|
|
153
|
-
trackListen(
|
|
154
|
-
listen<{ freeMb: number }>("clips:disk-space-warning", () => {
|
|
155
|
-
setDiskSpaceLevel((prev) =>
|
|
156
|
-
prev === "critical" ? "critical" : "warning",
|
|
157
|
-
);
|
|
158
|
-
}),
|
|
159
|
-
);
|
|
160
|
-
trackListen(
|
|
161
|
-
listen<{ freeMb: number }>("clips:disk-space-critical", () => {
|
|
162
|
-
setDiskSpaceLevel("critical");
|
|
163
|
-
}),
|
|
164
|
-
);
|
|
165
|
-
trackListen(
|
|
166
|
-
listen<{ freeMb: number }>("clips:disk-space-ok", () => {
|
|
167
|
-
setDiskSpaceLevel("ok");
|
|
168
|
-
}),
|
|
169
|
-
);
|
|
170
|
-
void Promise.allSettled(listenerPromises).then(() => {
|
|
171
|
-
if (!stopped) emit("clips:toolbar-ready", {}).catch(() => {});
|
|
172
|
-
});
|
|
173
|
-
return () => {
|
|
174
|
-
stopped = true;
|
|
175
|
-
unlistens.forEach((u) => {
|
|
176
|
-
try {
|
|
177
|
-
u();
|
|
178
|
-
} catch {
|
|
179
|
-
// ignore
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
unlistens.length = 0;
|
|
183
|
-
if (fallbackTimer.current) {
|
|
184
|
-
clearTimeout(fallbackTimer.current);
|
|
185
|
-
fallbackTimer.current = null;
|
|
186
|
-
}
|
|
187
|
-
clearPauseTransition();
|
|
188
|
-
};
|
|
189
|
-
}, []);
|
|
190
|
-
|
|
191
|
-
function scheduleCloseFallback(action: string) {
|
|
192
|
-
fallbackTimer.current = setTimeout(() => {
|
|
193
|
-
console.warn(
|
|
194
|
-
`[clips-toolbar] recorder did not close toolbar within 3s after ${action} — self-closing`,
|
|
195
|
-
);
|
|
196
|
-
getCurrentWindow()
|
|
197
|
-
.close()
|
|
198
|
-
.catch(() => {});
|
|
199
|
-
}, 3_000);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function resizeToolbarWindow(expanded: boolean) {
|
|
203
|
-
if (expandedRef.current === expanded) return;
|
|
204
|
-
expandedRef.current = expanded;
|
|
205
|
-
const height = expanded
|
|
206
|
-
? TOOLBAR_EXPANDED_WINDOW_HEIGHT
|
|
207
|
-
: TOOLBAR_COLLAPSED_WINDOW_HEIGHT;
|
|
208
|
-
getCurrentWindow()
|
|
209
|
-
.setSize(new LogicalSize(TOOLBAR_WINDOW_WIDTH, height))
|
|
210
|
-
.catch((err) => {
|
|
211
|
-
console.warn("[clips-toolbar] resize failed", err);
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function stop() {
|
|
216
|
-
if (pendingAction || !enabled) return;
|
|
217
|
-
setPendingAction("stop");
|
|
218
|
-
console.log("[clips-toolbar] stop clicked — emitting clips:recorder-stop");
|
|
219
|
-
emit("clips:recorder-stop")
|
|
220
|
-
.then(() => scheduleCloseFallback("stop"))
|
|
221
|
-
.catch((err) => {
|
|
222
|
-
console.error("[clips-toolbar] emit clips:recorder-stop failed:", err);
|
|
223
|
-
setPendingAction(null);
|
|
224
|
-
});
|
|
225
|
-
// Defensive fallback: the recorder normally closes us via
|
|
226
|
-
// `hide_overlays` within a second or two. If for any reason the
|
|
227
|
-
// popover listener never fires (popover window closed, listener
|
|
228
|
-
// torn down mid-emit, etc.), self-close after 3s so the user isn't
|
|
229
|
-
// left with a zombie pill floating over their screen. The recorder
|
|
230
|
-
// closing us first is a no-op on the already-closed window.
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const isPreparing = preparing || (!enabled && !popoverVisible);
|
|
234
|
-
function togglePause() {
|
|
235
|
-
if (!enabled || pendingAction) return;
|
|
236
|
-
const transition = paused ? "resume" : "pause";
|
|
237
|
-
clearPauseTransition();
|
|
238
|
-
pauseTransitionRef.current = transition;
|
|
239
|
-
setPaused(transition === "pause");
|
|
240
|
-
pauseTransitionTimerRef.current = setTimeout(clearPauseTransition, 3_000);
|
|
241
|
-
emit(`clips:recorder-${transition}`).catch((err) => {
|
|
242
|
-
console.error(
|
|
243
|
-
`[clips-toolbar] emit clips:recorder-${transition} failed:`,
|
|
244
|
-
err,
|
|
245
|
-
);
|
|
246
|
-
clearPauseTransition();
|
|
247
|
-
setPaused(paused);
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
function activatePauseFromPointer(e: React.PointerEvent<HTMLButtonElement>) {
|
|
251
|
-
if (e.button !== 0) return;
|
|
252
|
-
// The native toolbar resizes as the pointer enters it. Dispatch on
|
|
253
|
-
// pointer-down so that resize/focus changes cannot cancel the subsequent
|
|
254
|
-
// click, while the click handler below remains available to keyboards.
|
|
255
|
-
e.preventDefault();
|
|
256
|
-
togglePause();
|
|
257
|
-
}
|
|
258
|
-
function restart() {
|
|
259
|
-
if (pendingAction || !enabled) return;
|
|
260
|
-
setPendingAction("restart");
|
|
261
|
-
console.log(
|
|
262
|
-
"[clips-toolbar] restart clicked — emitting clips:recorder-restart",
|
|
263
|
-
);
|
|
264
|
-
emit("clips:recorder-restart")
|
|
265
|
-
.then(() => scheduleCloseFallback("restart"))
|
|
266
|
-
.catch((err) => {
|
|
267
|
-
console.error(
|
|
268
|
-
"[clips-toolbar] emit clips:recorder-restart failed:",
|
|
269
|
-
err,
|
|
270
|
-
);
|
|
271
|
-
setPendingAction(null);
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
function cancel() {
|
|
275
|
-
if (pendingAction) return;
|
|
276
|
-
setPendingAction("cancel");
|
|
277
|
-
console.log(
|
|
278
|
-
"[clips-toolbar] cancel clicked — emitting clips:recorder-cancel",
|
|
279
|
-
);
|
|
280
|
-
emit("clips:recorder-cancel")
|
|
281
|
-
.then(() => scheduleCloseFallback("cancel"))
|
|
282
|
-
.catch((err) => {
|
|
283
|
-
console.error(
|
|
284
|
-
"[clips-toolbar] emit clips:recorder-cancel failed:",
|
|
285
|
-
err,
|
|
286
|
-
);
|
|
287
|
-
setPendingAction(null);
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Same explicit-drag pattern the bubble uses — `data-tauri-drag-region`
|
|
292
|
-
// has been unreliable across iterations so we call `startDragging()`
|
|
293
|
-
// directly on mousedown. Interactive controls are marked `data-no-drag`
|
|
294
|
-
// so their clicks reach onClick instead of starting a drag.
|
|
295
|
-
const handleToolbarMouseDown = (e: React.MouseEvent) => {
|
|
296
|
-
if (e.button !== 0) return;
|
|
297
|
-
const target = e.target as HTMLElement;
|
|
298
|
-
if (target.closest("[data-no-drag]")) return;
|
|
299
|
-
getCurrentWindow()
|
|
300
|
-
.startDragging()
|
|
301
|
-
.catch((err) => {
|
|
302
|
-
console.warn("[clips-toolbar] startDragging failed", err);
|
|
303
|
-
});
|
|
304
|
-
};
|
|
305
|
-
const handleToolbarBlur = (e: FocusEvent<HTMLDivElement>) => {
|
|
306
|
-
if (e.currentTarget.contains(e.relatedTarget as Node | null)) return;
|
|
307
|
-
resizeToolbarWindow(false);
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const pendingActionLabel =
|
|
311
|
-
pendingAction === "restart"
|
|
312
|
-
? "Restarting..."
|
|
313
|
-
: pendingAction === "cancel"
|
|
314
|
-
? "Cancelling..."
|
|
315
|
-
: "Stopping...";
|
|
316
|
-
|
|
317
|
-
return (
|
|
318
|
-
<div
|
|
319
|
-
className={`toolbar-v ${paused ? "toolbar-v-paused" : ""} ${enabled ? "" : "toolbar-v-disabled"} ${diskSpaceLevel !== "ok" ? `toolbar-v-disk-${diskSpaceLevel}` : ""}`}
|
|
320
|
-
onMouseDown={handleToolbarMouseDown}
|
|
321
|
-
onMouseEnter={() => resizeToolbarWindow(true)}
|
|
322
|
-
onMouseLeave={() => resizeToolbarWindow(false)}
|
|
323
|
-
onFocusCapture={() => resizeToolbarWindow(true)}
|
|
324
|
-
onBlurCapture={handleToolbarBlur}
|
|
325
|
-
>
|
|
326
|
-
{/* Primary controls live in a fixed-height zone so they stay pinned
|
|
327
|
-
to the same vertical position whether or not the pill is hovered.
|
|
328
|
-
Centering happens INSIDE this zone (not on the pill), so the
|
|
329
|
-
collapsed→expanded `justify-content` change can't nudge the Stop
|
|
330
|
-
button up — only the hover actions below grow into the new space. */}
|
|
331
|
-
<div className="toolbar-v-primary">
|
|
332
|
-
<button
|
|
333
|
-
className="toolbar-v-stop"
|
|
334
|
-
onClick={stop}
|
|
335
|
-
disabled={!!pendingAction || !enabled}
|
|
336
|
-
aria-label={
|
|
337
|
-
pendingAction === "stop" ? "Stopping recording" : "Stop recording"
|
|
338
|
-
}
|
|
339
|
-
title={
|
|
340
|
-
pendingAction === "stop"
|
|
341
|
-
? pendingActionLabel
|
|
342
|
-
: enabled
|
|
343
|
-
? "Stop recording"
|
|
344
|
-
: "Recording not started yet"
|
|
345
|
-
}
|
|
346
|
-
data-no-drag
|
|
347
|
-
>
|
|
348
|
-
{pendingAction === "stop" || isPreparing ? (
|
|
349
|
-
<IconLoader2 className="toolbar-v-spinner" size={18} />
|
|
350
|
-
) : (
|
|
351
|
-
<span className="toolbar-v-stop-square" />
|
|
352
|
-
)}
|
|
353
|
-
</button>
|
|
354
|
-
<button
|
|
355
|
-
type="button"
|
|
356
|
-
className={`toolbar-v-time ${isPreparing ? "toolbar-v-time-preparing" : ""}`}
|
|
357
|
-
onClick={() => invoke("show_popover").catch(() => {})}
|
|
358
|
-
aria-label="Open Clips"
|
|
359
|
-
title="Open Clips"
|
|
360
|
-
data-no-drag
|
|
361
|
-
>
|
|
362
|
-
{isPreparing ? "Preparing…" : formatTime(elapsed)}
|
|
363
|
-
</button>
|
|
364
|
-
{diskSpaceLevel !== "ok" && (
|
|
365
|
-
<div
|
|
366
|
-
className={`toolbar-v-disk-indicator toolbar-v-disk-indicator-${diskSpaceLevel}`}
|
|
367
|
-
title={
|
|
368
|
-
diskSpaceLevel === "critical"
|
|
369
|
-
? "Disk almost full. Stop recording now to avoid losing your clip."
|
|
370
|
-
: "Low disk space. Save your recording soon."
|
|
371
|
-
}
|
|
372
|
-
data-no-drag
|
|
373
|
-
>
|
|
374
|
-
<IconAlertTriangle size={12} />
|
|
375
|
-
</div>
|
|
376
|
-
)}
|
|
377
|
-
<button
|
|
378
|
-
className="toolbar-v-pause"
|
|
379
|
-
onPointerDown={activatePauseFromPointer}
|
|
380
|
-
onClick={(e) => {
|
|
381
|
-
if (e.detail === 0) togglePause();
|
|
382
|
-
}}
|
|
383
|
-
disabled={!enabled || !!pendingAction}
|
|
384
|
-
aria-label={paused ? "Resume" : "Pause"}
|
|
385
|
-
title={
|
|
386
|
-
pendingAction
|
|
387
|
-
? pendingActionLabel
|
|
388
|
-
: enabled
|
|
389
|
-
? paused
|
|
390
|
-
? "Resume"
|
|
391
|
-
: "Pause"
|
|
392
|
-
: "Recording not started yet"
|
|
393
|
-
}
|
|
394
|
-
data-no-drag
|
|
395
|
-
>
|
|
396
|
-
{paused ? (
|
|
397
|
-
<IconPlayerPlayFilled size={18} />
|
|
398
|
-
) : (
|
|
399
|
-
<IconPlayerPauseFilled size={18} />
|
|
400
|
-
)}
|
|
401
|
-
</button>
|
|
402
|
-
</div>
|
|
403
|
-
<div
|
|
404
|
-
className="toolbar-v-hover-actions"
|
|
405
|
-
role="group"
|
|
406
|
-
aria-label="Recording actions"
|
|
407
|
-
>
|
|
408
|
-
<button
|
|
409
|
-
className="toolbar-v-action"
|
|
410
|
-
onClick={restart}
|
|
411
|
-
disabled={!enabled || !!pendingAction}
|
|
412
|
-
aria-label="Restart recording"
|
|
413
|
-
title={
|
|
414
|
-
pendingAction === "restart"
|
|
415
|
-
? pendingActionLabel
|
|
416
|
-
: enabled
|
|
417
|
-
? "Restart"
|
|
418
|
-
: "Recording not started yet"
|
|
419
|
-
}
|
|
420
|
-
data-no-drag
|
|
421
|
-
>
|
|
422
|
-
{pendingAction === "restart" ? (
|
|
423
|
-
<IconLoader2 className="toolbar-v-spinner" size={18} />
|
|
424
|
-
) : (
|
|
425
|
-
<IconRefresh size={24} stroke={1.9} />
|
|
426
|
-
)}
|
|
427
|
-
</button>
|
|
428
|
-
<button
|
|
429
|
-
className="toolbar-v-action toolbar-v-action-danger"
|
|
430
|
-
onClick={cancel}
|
|
431
|
-
disabled={!!pendingAction}
|
|
432
|
-
aria-label={enabled ? "Cancel recording" : "Cancel recording setup"}
|
|
433
|
-
title={
|
|
434
|
-
pendingAction === "cancel"
|
|
435
|
-
? pendingActionLabel
|
|
436
|
-
: enabled
|
|
437
|
-
? "Cancel"
|
|
438
|
-
: "Cancel recording setup"
|
|
439
|
-
}
|
|
440
|
-
data-no-drag
|
|
441
|
-
>
|
|
442
|
-
{pendingAction === "cancel" ? (
|
|
443
|
-
<IconLoader2 className="toolbar-v-spinner" size={18} />
|
|
444
|
-
) : (
|
|
445
|
-
<IconTrash size={24} stroke={1.9} />
|
|
446
|
-
)}
|
|
447
|
-
</button>
|
|
448
|
-
</div>
|
|
449
|
-
</div>
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function formatTime(ms: number): string {
|
|
454
|
-
const total = Math.max(0, Math.floor(ms / 1000));
|
|
455
|
-
const m = Math.floor(total / 60);
|
|
456
|
-
const s = total % 60;
|
|
457
|
-
return `${m}:${s.toString().padStart(2, "0")}`;
|
|
458
|
-
}
|