versioncam 0.5.3 → 0.7.1
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/CHANGELOG.md +103 -0
- package/README.md +108 -6
- package/dist/cli/commands/check.js +25 -0
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/doctor.js +54 -1
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/preview.js +4 -2
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/cli/commands/record.d.ts +6 -0
- package/dist/cli/commands/record.js +24 -3
- package/dist/cli/commands/record.js.map +1 -1
- package/dist/cli/commands/stability.js +10 -3
- package/dist/cli/commands/stability.js.map +1 -1
- package/dist/cli/usage.d.ts +1 -1
- package/dist/cli/usage.js +2 -0
- package/dist/cli/usage.js.map +1 -1
- package/dist/config.d.ts +45 -4
- package/dist/config.js +134 -8
- package/dist/config.js.map +1 -1
- package/dist/core/cursor.d.ts +22 -0
- package/dist/core/cursor.js +6 -0
- package/dist/core/cursor.js.map +1 -1
- package/dist/core/motion-defaults.d.ts +8 -0
- package/dist/core/motion-defaults.js +8 -0
- package/dist/core/motion-defaults.js.map +1 -1
- package/dist/core/timeline.d.ts +24 -0
- package/dist/core/timeline.js.map +1 -1
- package/dist/driver/clip.d.ts +6 -0
- package/dist/driver/clip.js +33 -2
- package/dist/driver/clip.js.map +1 -1
- package/dist/driver/file-window.d.ts +66 -0
- package/dist/driver/file-window.js +245 -0
- package/dist/driver/file-window.js.map +1 -0
- package/dist/driver/page-hooks.d.ts +23 -1
- package/dist/driver/page-hooks.js +36 -1
- package/dist/driver/page-hooks.js.map +1 -1
- package/dist/driver/replay.d.ts +263 -0
- package/dist/driver/replay.js +558 -0
- package/dist/driver/replay.js.map +1 -0
- package/dist/driver/session.d.ts +70 -4
- package/dist/driver/session.js +267 -29
- package/dist/driver/session.js.map +1 -1
- package/dist/driver/settle.js +6 -0
- package/dist/driver/settle.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js.map +1 -1
- package/dist/page/assets/index-CnRDAiUz.js +2 -0
- package/dist/page/index.html +1 -1
- package/dist/publish/protocol.d.ts +12 -1
- package/dist/publish/protocol.js.map +1 -1
- package/dist/render/render.d.ts +7 -1
- package/dist/render/render.js +51 -17
- package/dist/render/render.js.map +1 -1
- package/dist/render/sequence.d.ts +10 -5
- package/dist/render/sequence.js +44 -29
- package/dist/render/sequence.js.map +1 -1
- package/dsl.md +68 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/versioncam/authoring.md +15 -1
- package/plugin/skills/versioncam/onboarding.md +9 -3
- package/dist/page/assets/index-BtUbUEdN.js +0 -2
package/dist/driver/clip.js
CHANGED
|
@@ -2,7 +2,8 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { isTransitions } from "../config.js";
|
|
5
|
-
import { MUTATION_COUNTER_SCRIPT, TRANSITION_CLOCK_SCRIPT, freezeCssScript, } from "./page-hooks.js";
|
|
5
|
+
import { INSTANT_SCROLL_SCRIPT, MUTATION_COUNTER_SCRIPT, TRANSITION_CLOCK_SCRIPT, freezeCssScript, } from "./page-hooks.js";
|
|
6
|
+
import { ClipReplay } from "./replay.js";
|
|
6
7
|
import { writeFailure, writeSettles, writeTargets } from "./reports.js";
|
|
7
8
|
import { RecordingSession } from "./session.js";
|
|
8
9
|
/**
|
|
@@ -43,6 +44,11 @@ export async function runClip(browser, config, definition, runOptions = {}) {
|
|
|
43
44
|
// clip asking for 60 would quietly cost four times what the flag promises.
|
|
44
45
|
const fps = draft ? 15 : (options.fps ?? config.fps);
|
|
45
46
|
const capture = { ...config.capture, ...options.capture };
|
|
47
|
+
// Made first, because it refuses a clip told to replay a file that does
|
|
48
|
+
// not exist, and nothing is open yet to close.
|
|
49
|
+
const replay = config.replay
|
|
50
|
+
? new ClipReplay(config.replay, id, RECORDER_VERSION, runOptions.replay)
|
|
51
|
+
: null;
|
|
46
52
|
const context = await browser.newContext({
|
|
47
53
|
viewport,
|
|
48
54
|
deviceScaleFactor: dpr,
|
|
@@ -54,6 +60,11 @@ export async function runClip(browser, config, definition, runOptions = {}) {
|
|
|
54
60
|
locale: config.clock.locale,
|
|
55
61
|
storageState: config.auth?.storageState,
|
|
56
62
|
});
|
|
63
|
+
// Installed on the context before the page exists, so the page's first
|
|
64
|
+
// request is caught. A clip's own `page.route` mocks are tried before it,
|
|
65
|
+
// as Playwright tries a page's routes before its context's: what a clip
|
|
66
|
+
// answers itself never reaches the replay.
|
|
67
|
+
await replay?.install(context);
|
|
57
68
|
const page = await context.newPage();
|
|
58
69
|
// A clip's own `capture` is merged over the config's unchecked, so its
|
|
59
70
|
// switch is checked here, where a typo would otherwise play by default.
|
|
@@ -63,8 +74,12 @@ export async function runClip(browser, config, definition, runOptions = {}) {
|
|
|
63
74
|
}
|
|
64
75
|
await page.addInitScript(MUTATION_COUNTER_SCRIPT);
|
|
65
76
|
await page.addInitScript(freezeCssScript(transitions));
|
|
77
|
+
await page.addInitScript(INSTANT_SCROLL_SCRIPT);
|
|
66
78
|
if (transitions === "play")
|
|
67
79
|
await page.addInitScript(TRANSITION_CLOCK_SCRIPT);
|
|
80
|
+
const pacing = replay?.pacingScript();
|
|
81
|
+
if (pacing)
|
|
82
|
+
await page.addInitScript(pacing);
|
|
68
83
|
// Paused, not just installed. `clock.install()` leaves Playwright's fake
|
|
69
84
|
// clock running on real time between the driver's own `runFor` calls, so
|
|
70
85
|
// every screenshot, every probe and every stall of the machine reached the
|
|
@@ -101,9 +116,24 @@ export async function runClip(browser, config, definition, runOptions = {}) {
|
|
|
101
116
|
capture,
|
|
102
117
|
draft,
|
|
103
118
|
config,
|
|
119
|
+
replay,
|
|
104
120
|
});
|
|
105
121
|
try {
|
|
106
|
-
|
|
122
|
+
try {
|
|
123
|
+
await body(session.forClipBody());
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
// A request the replay could not answer failed in the page, and the
|
|
127
|
+
// page then failed the clip somewhere else: a locator that never
|
|
128
|
+
// appeared, a settle that ran out. The missing response is the cause.
|
|
129
|
+
const missing = replay?.failure();
|
|
130
|
+
if (missing)
|
|
131
|
+
error.message += `\n\n${missing}`;
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
const missing = replay?.failure();
|
|
135
|
+
if (missing)
|
|
136
|
+
throw new Error(missing);
|
|
107
137
|
if (documentLoads > 1) {
|
|
108
138
|
throw new Error(`The document reloaded ${documentLoads - 1} time(s) during "${id}". ` +
|
|
109
139
|
"The usual cause is editing a file while recording: a dev server " +
|
|
@@ -113,6 +143,7 @@ export async function runClip(browser, config, definition, runOptions = {}) {
|
|
|
113
143
|
const timeline = session.finish();
|
|
114
144
|
writeSettles(dir, session.settles);
|
|
115
145
|
writeTargets(dir, session.evidence().targets);
|
|
146
|
+
replay?.write();
|
|
116
147
|
return { timeline, dir, cost: session.cost, settles: session.settles };
|
|
117
148
|
}
|
|
118
149
|
catch (error) {
|
package/dist/driver/clip.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clip.js","sourceRoot":"","sources":["../../src/driver/clip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAI7C,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAExE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AA+BhD;;;;;;GAMG;AACH,MAAM,UAAU,IAAI,CAClB,EAAU,EACV,OAAoB,EACpB,IAAc;IAEd,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,oBAAoB,CACpD,CAAC,OAAO,CAAC;AAEV,qEAAqE;AACrE,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE;YAC3D,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;SACpC,CAAC,CAAC,IAAI,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;
|
|
1
|
+
{"version":3,"file":"clip.js","sourceRoot":"","sources":["../../src/driver/clip.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A file window drawn over the page, to drag files from.
|
|
3
|
+
*
|
|
4
|
+
* Uploading is half of what a data app does, and the browser's own file
|
|
5
|
+
* dialog is outside the page: nothing can film it, and a file that simply
|
|
6
|
+
* appears in an upload list reads as a cut. The first app this recorded
|
|
7
|
+
* solved it with a small window drawn in the page, listing the files, from
|
|
8
|
+
* which the cursor drags them onto the app's own dropzone; nothing in it was
|
|
9
|
+
* that app's, so it is here. It looks like a Finder window, because the
|
|
10
|
+
* clips it sits in show a Mac's cursor.
|
|
11
|
+
*
|
|
12
|
+
* It is staged, and a clip that uses it should say so where its clips are
|
|
13
|
+
* shown. What is real is the drop: letting go over the page sends the
|
|
14
|
+
* element under the pointer the `dragenter`, `dragover` and `drop` events a
|
|
15
|
+
* real drag sends, carrying `File` objects with the names, types and
|
|
16
|
+
* contents given, so the app takes them through its own dropzone, its own
|
|
17
|
+
* accept rules and its own list, as it takes a person's.
|
|
18
|
+
*
|
|
19
|
+
* Selection works as in Finder: a click selects one row, a click with Shift
|
|
20
|
+
* extends to it, a click with Meta or Control adds or removes it, and a
|
|
21
|
+
* press on a selected row drags the whole selection. Its motion runs on
|
|
22
|
+
* `requestAnimationFrame`, which the recorder's clock drives, so it opens
|
|
23
|
+
* and closes at the same pace on every run.
|
|
24
|
+
*/
|
|
25
|
+
export type StagedFile = {
|
|
26
|
+
/** What the window lists, and the dropped `File`'s name. */
|
|
27
|
+
name: string;
|
|
28
|
+
/** The dropped file's contents. Default empty. */
|
|
29
|
+
content?: string;
|
|
30
|
+
/** Its MIME type. Default from the extension. */
|
|
31
|
+
type?: string;
|
|
32
|
+
/** The Size column. Default from the contents. */
|
|
33
|
+
size?: string;
|
|
34
|
+
};
|
|
35
|
+
export type FileWindowOptions = {
|
|
36
|
+
/** The title bar. Default "Files". */
|
|
37
|
+
title?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Where the window's top left corner sits, as fractions of the viewport.
|
|
40
|
+
* Default `{ x: 0.52, y: 0.42 }`, to the right of centre, where a sidebar's
|
|
41
|
+
* dropzone on the left stays in view.
|
|
42
|
+
*/
|
|
43
|
+
at?: {
|
|
44
|
+
x: number;
|
|
45
|
+
y: number;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/** A row as the page draws it: every column worked out here, not there. */
|
|
49
|
+
export type FileRow = {
|
|
50
|
+
name: string;
|
|
51
|
+
content: string;
|
|
52
|
+
type: string;
|
|
53
|
+
size: string;
|
|
54
|
+
kind: string;
|
|
55
|
+
};
|
|
56
|
+
export declare function fileRows(files: readonly StagedFile[]): FileRow[];
|
|
57
|
+
/**
|
|
58
|
+
* Runs in the page: draws the window and wires its selection and its drag.
|
|
59
|
+
* Self-contained, as everything handed to `page.evaluate` must be.
|
|
60
|
+
*/
|
|
61
|
+
export declare function drawFileWindow(args: {
|
|
62
|
+
rows: FileRow[];
|
|
63
|
+
title: string;
|
|
64
|
+
left: number;
|
|
65
|
+
top: number;
|
|
66
|
+
}): void;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A file window drawn over the page, to drag files from.
|
|
3
|
+
*
|
|
4
|
+
* Uploading is half of what a data app does, and the browser's own file
|
|
5
|
+
* dialog is outside the page: nothing can film it, and a file that simply
|
|
6
|
+
* appears in an upload list reads as a cut. The first app this recorded
|
|
7
|
+
* solved it with a small window drawn in the page, listing the files, from
|
|
8
|
+
* which the cursor drags them onto the app's own dropzone; nothing in it was
|
|
9
|
+
* that app's, so it is here. It looks like a Finder window, because the
|
|
10
|
+
* clips it sits in show a Mac's cursor.
|
|
11
|
+
*
|
|
12
|
+
* It is staged, and a clip that uses it should say so where its clips are
|
|
13
|
+
* shown. What is real is the drop: letting go over the page sends the
|
|
14
|
+
* element under the pointer the `dragenter`, `dragover` and `drop` events a
|
|
15
|
+
* real drag sends, carrying `File` objects with the names, types and
|
|
16
|
+
* contents given, so the app takes them through its own dropzone, its own
|
|
17
|
+
* accept rules and its own list, as it takes a person's.
|
|
18
|
+
*
|
|
19
|
+
* Selection works as in Finder: a click selects one row, a click with Shift
|
|
20
|
+
* extends to it, a click with Meta or Control adds or removes it, and a
|
|
21
|
+
* press on a selected row drags the whole selection. Its motion runs on
|
|
22
|
+
* `requestAnimationFrame`, which the recorder's clock drives, so it opens
|
|
23
|
+
* and closes at the same pace on every run.
|
|
24
|
+
*/
|
|
25
|
+
/** The extension's kind, as Finder words it, and its MIME type. */
|
|
26
|
+
const KINDS = {
|
|
27
|
+
csv: { kind: "CSV document", type: "text/csv" },
|
|
28
|
+
tsv: { kind: "TSV document", type: "text/tab-separated-values" },
|
|
29
|
+
json: { kind: "JSON document", type: "application/json" },
|
|
30
|
+
txt: { kind: "Plain Text", type: "text/plain" },
|
|
31
|
+
fasta: { kind: "FASTA document", type: "text/plain" },
|
|
32
|
+
fa: { kind: "FASTA document", type: "text/plain" },
|
|
33
|
+
png: { kind: "PNG image", type: "image/png" },
|
|
34
|
+
jpg: { kind: "JPEG image", type: "image/jpeg" },
|
|
35
|
+
pdf: { kind: "PDF document", type: "application/pdf" },
|
|
36
|
+
xlsx: {
|
|
37
|
+
kind: "Microsoft Excel workbook",
|
|
38
|
+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
export function fileRows(files) {
|
|
42
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
43
|
+
throw new TypeError("fileWindow: list at least one file, as { name }.");
|
|
44
|
+
}
|
|
45
|
+
return files.map((file, i) => {
|
|
46
|
+
if (typeof file?.name !== "string" || file.name.length === 0) {
|
|
47
|
+
throw new TypeError(`fileWindow: file ${i} has no name.`);
|
|
48
|
+
}
|
|
49
|
+
const ext = file.name.split(".").pop()?.toLowerCase() ?? "";
|
|
50
|
+
const known = KINDS[ext];
|
|
51
|
+
const content = file.content ?? "";
|
|
52
|
+
const bytes = new TextEncoder().encode(content).length;
|
|
53
|
+
return {
|
|
54
|
+
name: file.name,
|
|
55
|
+
content,
|
|
56
|
+
type: file.type ?? known?.type ?? "application/octet-stream",
|
|
57
|
+
size: file.size ??
|
|
58
|
+
(bytes === 1
|
|
59
|
+
? "1 byte"
|
|
60
|
+
: bytes < 1024
|
|
61
|
+
? `${bytes} bytes`
|
|
62
|
+
: `${Math.round(bytes / 1024)} KB`),
|
|
63
|
+
kind: known?.kind ?? "Document",
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Runs in the page: draws the window and wires its selection and its drag.
|
|
69
|
+
* Self-contained, as everything handed to `page.evaluate` must be.
|
|
70
|
+
*/
|
|
71
|
+
export function drawFileWindow(args) {
|
|
72
|
+
const doc = document;
|
|
73
|
+
doc.getElementById("vc-file-window")?.remove();
|
|
74
|
+
const style = doc.createElement("style");
|
|
75
|
+
style.id = "vc-file-window-style";
|
|
76
|
+
style.textContent = `
|
|
77
|
+
#vc-file-window { position: fixed; z-index: 2147483000; width: 480px; border-radius: 10px;
|
|
78
|
+
overflow: hidden; background: #ffffff; color: #1d1d1f; opacity: 0;
|
|
79
|
+
box-shadow: 0 18px 50px rgba(15, 15, 40, 0.28), 0 0 0 1px rgba(0, 0, 0, 0.12);
|
|
80
|
+
font: 13px/1.35 -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif; }
|
|
81
|
+
#vc-file-window .bar { height: 34px; display: flex; align-items: center; gap: 8px; padding: 0 12px;
|
|
82
|
+
background: #ececec; border-bottom: 1px solid #d9d9d9; }
|
|
83
|
+
#vc-file-window .dot { width: 12px; height: 12px; border-radius: 50%; }
|
|
84
|
+
#vc-file-window .title { flex: 1; text-align: center; margin-right: 52px; font-weight: 600; color: #4a4a4a; }
|
|
85
|
+
#vc-file-window .head, #vc-file-window .row { display: grid; grid-template-columns: 1fr 80px 150px;
|
|
86
|
+
gap: 8px; align-items: center; padding: 0 14px; }
|
|
87
|
+
#vc-file-window .head { height: 26px; color: #8a8a8e; font-size: 11px; border-bottom: 1px solid #eeeeee; }
|
|
88
|
+
#vc-file-window .row { height: 30px; cursor: default; user-select: none; }
|
|
89
|
+
#vc-file-window .row:nth-child(odd) { background: #f6f6f8; }
|
|
90
|
+
#vc-file-window .row.selected { background: #2f6fde; color: #ffffff; }
|
|
91
|
+
#vc-file-window .name { display: flex; align-items: center; gap: 8px; white-space: nowrap;
|
|
92
|
+
overflow: hidden; text-overflow: ellipsis; }
|
|
93
|
+
#vc-file-window .muted { color: #8a8a8e; white-space: nowrap; }
|
|
94
|
+
#vc-file-window .row.selected .muted { color: #dbe6ff; }
|
|
95
|
+
#vc-file-window .foot { height: 24px; display: flex; align-items: center; justify-content: center;
|
|
96
|
+
color: #8a8a8e; font-size: 11px; border-top: 1px solid #eeeeee; background: #fafafa; }
|
|
97
|
+
#vc-file-ghost { position: fixed; z-index: 2147483001; pointer-events: none; display: flex;
|
|
98
|
+
align-items: center; gap: 6px; padding: 5px 9px; border-radius: 6px; background: rgba(47, 111, 222, 0.92);
|
|
99
|
+
color: #ffffff; font: 12px/1.2 -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
|
100
|
+
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.2); }
|
|
101
|
+
#vc-file-ghost .count { background: #ff3b30; border-radius: 9px; padding: 0 6px; font-weight: 600; }
|
|
102
|
+
`;
|
|
103
|
+
doc.getElementById("vc-file-window-style")?.remove();
|
|
104
|
+
doc.head.append(style);
|
|
105
|
+
const icon = (kind) => {
|
|
106
|
+
const colour = /image/i.test(kind)
|
|
107
|
+
? "#f4b400"
|
|
108
|
+
: /PDF/.test(kind)
|
|
109
|
+
? "#ea4335"
|
|
110
|
+
: "#34a853";
|
|
111
|
+
return `<svg width="14" height="17" viewBox="0 0 14 17" aria-hidden="true"><path d="M1 1h8l4 4v11H1z" fill="#fff" stroke="#9a9aa2"/><path d="M9 1v4h4" fill="none" stroke="#9a9aa2"/><rect x="3" y="9" width="8" height="5" rx="1" fill="${colour}"/></svg>`;
|
|
112
|
+
};
|
|
113
|
+
const text = (value) => {
|
|
114
|
+
const span = doc.createElement("span");
|
|
115
|
+
span.textContent = value;
|
|
116
|
+
return span.innerHTML;
|
|
117
|
+
};
|
|
118
|
+
const win = doc.createElement("div");
|
|
119
|
+
win.id = "vc-file-window";
|
|
120
|
+
win.style.left = `${args.left}px`;
|
|
121
|
+
win.style.top = `${args.top}px`;
|
|
122
|
+
win.innerHTML = `
|
|
123
|
+
<div class="bar"><span class="dot" style="background:#ff5f57"></span><span class="dot" style="background:#febc2e"></span>
|
|
124
|
+
<span class="dot" style="background:#28c840"></span><span class="title">${text(args.title)}</span></div>
|
|
125
|
+
<div class="head"><span>Name</span><span>Size</span><span>Kind</span></div>
|
|
126
|
+
<div class="rows">${args.rows
|
|
127
|
+
.map((row, i) => `<div class="row" data-vc-file="${i}"><span class="name">${icon(row.kind)}${text(row.name)}</span>` +
|
|
128
|
+
`<span class="muted">${text(row.size)}</span><span class="muted">${text(row.kind)}</span></div>`)
|
|
129
|
+
.join("")}</div>
|
|
130
|
+
<div class="foot">${args.rows.length} ${args.rows.length === 1 ? "item" : "items"}</div>`;
|
|
131
|
+
doc.body.append(win);
|
|
132
|
+
const fade = (from, to, ms, done) => {
|
|
133
|
+
const start = performance.now();
|
|
134
|
+
const step = (now) => {
|
|
135
|
+
const t = Math.min(1, (now - start) / ms);
|
|
136
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
137
|
+
const value = from + (to - from) * eased;
|
|
138
|
+
win.style.opacity = String(value);
|
|
139
|
+
win.style.transform = `translateY(${(1 - value) * 10}px)`;
|
|
140
|
+
if (t < 1)
|
|
141
|
+
requestAnimationFrame(step);
|
|
142
|
+
else
|
|
143
|
+
done?.();
|
|
144
|
+
};
|
|
145
|
+
requestAnimationFrame(step);
|
|
146
|
+
};
|
|
147
|
+
fade(0, 1, 280);
|
|
148
|
+
const rowEls = [...win.querySelectorAll(".row")];
|
|
149
|
+
const selected = new Set();
|
|
150
|
+
let anchor = 0;
|
|
151
|
+
const paint = () => rowEls.forEach((el, i) => el.classList.toggle("selected", selected.has(i)));
|
|
152
|
+
const transfer = () => {
|
|
153
|
+
const dt = new DataTransfer();
|
|
154
|
+
for (const i of [...selected].sort((a, b) => a - b)) {
|
|
155
|
+
const row = args.rows[i];
|
|
156
|
+
dt.items.add(new File([row.content], row.name, { type: row.type }));
|
|
157
|
+
}
|
|
158
|
+
return dt;
|
|
159
|
+
};
|
|
160
|
+
const fire = (target, type, x, y) => target.dispatchEvent(new DragEvent(type, {
|
|
161
|
+
bubbles: true,
|
|
162
|
+
cancelable: true,
|
|
163
|
+
composed: true,
|
|
164
|
+
clientX: x,
|
|
165
|
+
clientY: y,
|
|
166
|
+
dataTransfer: transfer(),
|
|
167
|
+
}));
|
|
168
|
+
const underPointer = (x, y) => {
|
|
169
|
+
const el = doc.elementFromPoint(x, y);
|
|
170
|
+
return el && !win.contains(el) ? el : null;
|
|
171
|
+
};
|
|
172
|
+
let pressed = null;
|
|
173
|
+
let ghost = null;
|
|
174
|
+
let over = null;
|
|
175
|
+
for (const [i, el] of rowEls.entries()) {
|
|
176
|
+
el.addEventListener("mousedown", (event) => {
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
if (event.shiftKey) {
|
|
179
|
+
selected.clear();
|
|
180
|
+
for (let k = Math.min(anchor, i); k <= Math.max(anchor, i); k += 1)
|
|
181
|
+
selected.add(k);
|
|
182
|
+
}
|
|
183
|
+
else if (event.metaKey || event.ctrlKey) {
|
|
184
|
+
if (selected.has(i))
|
|
185
|
+
selected.delete(i);
|
|
186
|
+
else
|
|
187
|
+
selected.add(i);
|
|
188
|
+
anchor = i;
|
|
189
|
+
}
|
|
190
|
+
else if (!selected.has(i)) {
|
|
191
|
+
selected.clear();
|
|
192
|
+
selected.add(i);
|
|
193
|
+
anchor = i;
|
|
194
|
+
}
|
|
195
|
+
paint();
|
|
196
|
+
pressed = selected.has(i) ? { x: event.clientX, y: event.clientY } : null;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
doc.addEventListener("mousemove", (event) => {
|
|
200
|
+
if (!pressed)
|
|
201
|
+
return;
|
|
202
|
+
if (!ghost) {
|
|
203
|
+
// A few pixels first, as a real drag waits for, so a click with a
|
|
204
|
+
// slightly unsteady hand selects and does not drag.
|
|
205
|
+
if (Math.hypot(event.clientX - pressed.x, event.clientY - pressed.y) < 4)
|
|
206
|
+
return;
|
|
207
|
+
ghost = doc.createElement("div");
|
|
208
|
+
ghost.id = "vc-file-ghost";
|
|
209
|
+
const first = args.rows[Math.min(...selected)];
|
|
210
|
+
ghost.innerHTML =
|
|
211
|
+
`${icon(first.kind)}${text(first.name)}` +
|
|
212
|
+
(selected.size > 1
|
|
213
|
+
? `<span class="count">${selected.size}</span>`
|
|
214
|
+
: "");
|
|
215
|
+
doc.body.append(ghost);
|
|
216
|
+
}
|
|
217
|
+
ghost.style.left = `${event.clientX + 12}px`;
|
|
218
|
+
ghost.style.top = `${event.clientY + 10}px`;
|
|
219
|
+
const target = underPointer(event.clientX, event.clientY);
|
|
220
|
+
if (over && target !== over)
|
|
221
|
+
fire(over, "dragleave", event.clientX, event.clientY);
|
|
222
|
+
if (target && target !== over)
|
|
223
|
+
fire(target, "dragenter", event.clientX, event.clientY);
|
|
224
|
+
if (target)
|
|
225
|
+
fire(target, "dragover", event.clientX, event.clientY);
|
|
226
|
+
over = target;
|
|
227
|
+
});
|
|
228
|
+
doc.addEventListener("mouseup", (event) => {
|
|
229
|
+
const dragging = ghost !== null;
|
|
230
|
+
pressed = null;
|
|
231
|
+
if (!dragging)
|
|
232
|
+
return;
|
|
233
|
+
ghost?.remove();
|
|
234
|
+
ghost = null;
|
|
235
|
+
const target = underPointer(event.clientX, event.clientY);
|
|
236
|
+
over = null;
|
|
237
|
+
if (!target)
|
|
238
|
+
return;
|
|
239
|
+
fire(target, "drop", event.clientX, event.clientY);
|
|
240
|
+
// The files have gone where they were dropped, and the window with them.
|
|
241
|
+
fade(1, 0, 220, () => win.remove());
|
|
242
|
+
});
|
|
243
|
+
window.__vcCloseFileWindow = () => fade(1, 0, 220, () => win.remove());
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=file-window.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-window.js","sourceRoot":"","sources":["../../src/driver/file-window.ts"],"names":[],"mappings":"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"}
|
|
@@ -80,10 +80,28 @@ export declare const SYNC_TRANSITIONS: string;
|
|
|
80
80
|
* deterministically, at exactly the rate the timeline says.
|
|
81
81
|
*
|
|
82
82
|
* `scroll-behavior: auto` is here for the same reason: a smooth-scrolling
|
|
83
|
-
* container animates outside our control.
|
|
83
|
+
* container animates outside our control. It covers what the stylesheet
|
|
84
|
+
* asks for; what a script asks for, `{ behavior: "smooth" }`, is
|
|
85
|
+
* `INSTANT_SCROLL_SCRIPT`'s.
|
|
84
86
|
*/
|
|
85
87
|
export declare function freezeCss(zeroTransitions: boolean): string;
|
|
86
88
|
export declare function freezeCssScript(transitions: Transitions): string;
|
|
89
|
+
/**
|
|
90
|
+
* Makes a script's smooth scroll instant, as the freeze stylesheet makes a
|
|
91
|
+
* stylesheet's.
|
|
92
|
+
*
|
|
93
|
+
* `scroll-behavior` in CSS only decides a scroll that does not say how to
|
|
94
|
+
* scroll. An app that calls `scrollIntoView({ behavior: "smooth" })`, or
|
|
95
|
+
* `scrollTo` with it, says, and the browser animates that scroll on the
|
|
96
|
+
* compositor's real clock whatever the stylesheet holds: a chat that keeps
|
|
97
|
+
* its newest message in view does it on every message, and the frames while
|
|
98
|
+
* it moved showed the pane wherever the machine had got it to. So the five
|
|
99
|
+
* methods a script scrolls with take `"smooth"` as `"instant"`, and every
|
|
100
|
+
* other argument through untouched. The scroll still happens, at once,
|
|
101
|
+
* which is what a clip on authored time can show the same way twice;
|
|
102
|
+
* `s.scroll` is how a clip shows one move.
|
|
103
|
+
*/
|
|
104
|
+
export declare const INSTANT_SCROLL_SCRIPT = "(() => {\n const instant = (options) =>\n options && typeof options === \"object\" && options.behavior === \"smooth\"\n ? { ...options, behavior: \"instant\" }\n : options;\n const take = (owner, name) => {\n const original = owner[name];\n if (typeof original !== \"function\") return;\n owner[name] = function (...args) {\n if (args.length === 1) args[0] = instant(args[0]);\n return original.apply(this, args);\n };\n };\n for (const name of [\"scroll\", \"scrollTo\", \"scrollBy\", \"scrollIntoView\"]) {\n take(Element.prototype, name);\n }\n for (const name of [\"scroll\", \"scrollTo\", \"scrollBy\"]) take(window, name);\n})();";
|
|
87
105
|
/**
|
|
88
106
|
* What the driver learns from the page once per frame.
|
|
89
107
|
*
|
|
@@ -123,5 +141,9 @@ declare global {
|
|
|
123
141
|
__recorderTransitions?: {
|
|
124
142
|
sync(): number;
|
|
125
143
|
};
|
|
144
|
+
/** Set by the replay's pacing script (replay.ts). */
|
|
145
|
+
__recorderReplay?: {
|
|
146
|
+
pending: number;
|
|
147
|
+
};
|
|
126
148
|
}
|
|
127
149
|
}
|
|
@@ -196,7 +196,9 @@ export const SYNC_TRANSITIONS = "(window.__recorder ? window.__recorder.finishEn
|
|
|
196
196
|
* deterministically, at exactly the rate the timeline says.
|
|
197
197
|
*
|
|
198
198
|
* `scroll-behavior: auto` is here for the same reason: a smooth-scrolling
|
|
199
|
-
* container animates outside our control.
|
|
199
|
+
* container animates outside our control. It covers what the stylesheet
|
|
200
|
+
* asks for; what a script asks for, `{ behavior: "smooth" }`, is
|
|
201
|
+
* `INSTANT_SCROLL_SCRIPT`'s.
|
|
200
202
|
*/
|
|
201
203
|
export function freezeCss(zeroTransitions) {
|
|
202
204
|
const transitions = zeroTransitions
|
|
@@ -223,6 +225,39 @@ export function freezeCssScript(transitions) {
|
|
|
223
225
|
else document.addEventListener("DOMContentLoaded", inject, { once: true });
|
|
224
226
|
})();`;
|
|
225
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Makes a script's smooth scroll instant, as the freeze stylesheet makes a
|
|
230
|
+
* stylesheet's.
|
|
231
|
+
*
|
|
232
|
+
* `scroll-behavior` in CSS only decides a scroll that does not say how to
|
|
233
|
+
* scroll. An app that calls `scrollIntoView({ behavior: "smooth" })`, or
|
|
234
|
+
* `scrollTo` with it, says, and the browser animates that scroll on the
|
|
235
|
+
* compositor's real clock whatever the stylesheet holds: a chat that keeps
|
|
236
|
+
* its newest message in view does it on every message, and the frames while
|
|
237
|
+
* it moved showed the pane wherever the machine had got it to. So the five
|
|
238
|
+
* methods a script scrolls with take `"smooth"` as `"instant"`, and every
|
|
239
|
+
* other argument through untouched. The scroll still happens, at once,
|
|
240
|
+
* which is what a clip on authored time can show the same way twice;
|
|
241
|
+
* `s.scroll` is how a clip shows one move.
|
|
242
|
+
*/
|
|
243
|
+
export const INSTANT_SCROLL_SCRIPT = `(() => {
|
|
244
|
+
const instant = (options) =>
|
|
245
|
+
options && typeof options === "object" && options.behavior === "smooth"
|
|
246
|
+
? { ...options, behavior: "instant" }
|
|
247
|
+
: options;
|
|
248
|
+
const take = (owner, name) => {
|
|
249
|
+
const original = owner[name];
|
|
250
|
+
if (typeof original !== "function") return;
|
|
251
|
+
owner[name] = function (...args) {
|
|
252
|
+
if (args.length === 1) args[0] = instant(args[0]);
|
|
253
|
+
return original.apply(this, args);
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
for (const name of ["scroll", "scrollTo", "scrollBy", "scrollIntoView"]) {
|
|
257
|
+
take(Element.prototype, name);
|
|
258
|
+
}
|
|
259
|
+
for (const name of ["scroll", "scrollTo", "scrollBy"]) take(window, name);
|
|
260
|
+
})();`;
|
|
226
261
|
/**
|
|
227
262
|
* What the driver learns from the page once per frame.
|
|
228
263
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-hooks.js","sourceRoot":"","sources":["../../src/driver/page-hooks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAwFjC,CAAC;AAEP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA4BjC,CAAC;AAEP;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,8DAA8D;IAC9D,0EAA0E,CAAC;AAE7E
|
|
1
|
+
{"version":3,"file":"page-hooks.js","sourceRoot":"","sources":["../../src/driver/page-hooks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAwFjC,CAAC;AAEP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA4BjC,CAAC;AAEP;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,8DAA8D;IAC9D,0EAA0E,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,SAAS,CAAC,eAAwB;IAChD,MAAM,WAAW,GAAG,eAAe;QACjC,CAAC,CAAC,6EAA6E;QAC/E,CAAC,CAAC,EAAE,CAAC;IACP,OAAO;EACP,WAAW;;;;EAIX,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,WAAwB;IACtD,OAAO;iBACQ,WAAW,KAAK,MAAM;uBAChB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;;;;;;;;;MAStF,CAAC;AACP,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;MAiB/B,CAAC;AAEP;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAe,EAAE,CAAS,EAAE,CAAS,EAAU,EAAE,CAC3E,4BAA4B,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC"}
|