waygraph 0.13.2 → 0.13.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/dist/cli.js +39 -11
- package/dist/highlights.d.ts +25 -2
- package/dist/highlights.js +131 -43
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/examples/saucedemo/.tmp/device-demo/device-desktop.png +0 -0
- package/examples/saucedemo/.tmp/device-demo/device-mobile.png +0 -0
- package/examples/saucedemo/.tmp/device-demo/device-tablet.png +0 -0
- package/examples/saucedemo/scripts/demo-orientation-gestures-video.mjs +262 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1355,7 +1355,7 @@ async function installOverlay(page, title) {
|
|
|
1355
1355
|
cursor.innerHTML = MOUSE_SVG;
|
|
1356
1356
|
}
|
|
1357
1357
|
};
|
|
1358
|
-
const ensureChip = (preset, touch, remain) => {
|
|
1358
|
+
const ensureChip = (preset, touch, remain, orientation) => {
|
|
1359
1359
|
let badge = document.getElementById("wg-device-badge");
|
|
1360
1360
|
if (preset === "desktop" && !touch) {
|
|
1361
1361
|
if (badge) {
|
|
@@ -1375,10 +1375,17 @@ async function installOverlay(page, title) {
|
|
|
1375
1375
|
}
|
|
1376
1376
|
badge.dataset.preset = preset;
|
|
1377
1377
|
badge.dataset.touch = touch ? "1" : "0";
|
|
1378
|
+
const orient =
|
|
1379
|
+
orientation === "landscape" || orientation === "portrait"
|
|
1380
|
+
? orientation
|
|
1381
|
+
: "";
|
|
1382
|
+
if (orient) badge.dataset.orientation = orient;
|
|
1378
1383
|
const chipLabel =
|
|
1379
1384
|
preset === "desktop" && !touch
|
|
1380
1385
|
? "desktop"
|
|
1381
|
-
: preset +
|
|
1386
|
+
: preset +
|
|
1387
|
+
(orient ? " \u00b7 " + (orient === "landscape" ? "land" : "port") : "") +
|
|
1388
|
+
(remain ? " \u00b7 remain" : "");
|
|
1382
1389
|
badge.innerHTML =
|
|
1383
1390
|
'<span class="wg-dev-icon">' +
|
|
1384
1391
|
(ICONS[preset] || ICONS.desktop) +
|
|
@@ -1388,7 +1395,7 @@ async function installOverlay(page, title) {
|
|
|
1388
1395
|
void badge.offsetWidth;
|
|
1389
1396
|
badge.classList.add("wg-in");
|
|
1390
1397
|
};
|
|
1391
|
-
const showToast = (preset, touch, title, sub) => {
|
|
1398
|
+
const showToast = (preset, touch, title, sub, orientation) => {
|
|
1392
1399
|
let toast = document.getElementById("wg-device-toast");
|
|
1393
1400
|
if (toast && toast._wgTimer) {
|
|
1394
1401
|
clearTimeout(toast._wgTimer);
|
|
@@ -1422,7 +1429,7 @@ async function installOverlay(page, title) {
|
|
|
1422
1429
|
const t = document.getElementById("wg-device-toast");
|
|
1423
1430
|
if (t) t.remove();
|
|
1424
1431
|
if (preset !== "desktop" || touch) {
|
|
1425
|
-
ensureChip(preset, touch, true);
|
|
1432
|
+
ensureChip(preset, touch, true, orientation);
|
|
1426
1433
|
}
|
|
1427
1434
|
}, 380);
|
|
1428
1435
|
}, 2200);
|
|
@@ -1443,6 +1450,7 @@ async function installOverlay(page, title) {
|
|
|
1443
1450
|
false,
|
|
1444
1451
|
"Back to desktop",
|
|
1445
1452
|
vp.width + "\u00d7" + vp.height + " \u00b7 mouse",
|
|
1453
|
+
"landscape",
|
|
1446
1454
|
);
|
|
1447
1455
|
setTimeout(() => {
|
|
1448
1456
|
const badge = document.getElementById("wg-device-badge");
|
|
@@ -1455,6 +1463,7 @@ async function installOverlay(page, title) {
|
|
|
1455
1463
|
if (toast) toast.remove();
|
|
1456
1464
|
}
|
|
1457
1465
|
window.__wgDevicePreset = "desktop";
|
|
1466
|
+
window.__wgDeviceOrientation = "landscape";
|
|
1458
1467
|
return;
|
|
1459
1468
|
}
|
|
1460
1469
|
|
|
@@ -1462,27 +1471,46 @@ async function installOverlay(page, title) {
|
|
|
1462
1471
|
const preset = device.preset || "desktop";
|
|
1463
1472
|
const touch = !!device.touchMode;
|
|
1464
1473
|
const remain = payload.remain !== false && sync !== "clear";
|
|
1474
|
+
const vp = device.viewport || {};
|
|
1475
|
+
const orient =
|
|
1476
|
+
device.orientation === "landscape" || device.orientation === "portrait"
|
|
1477
|
+
? device.orientation
|
|
1478
|
+
: vp.height > vp.width
|
|
1479
|
+
? "portrait"
|
|
1480
|
+
: "landscape";
|
|
1465
1481
|
const prev = window.__wgDevicePreset || "";
|
|
1466
|
-
const
|
|
1482
|
+
const prevOrient = window.__wgDeviceOrientation || "";
|
|
1483
|
+
const presetChanged = prev !== preset || (touch && prev === "desktop");
|
|
1484
|
+
const orientChanged = prevOrient !== "" && prevOrient !== orient;
|
|
1485
|
+
const changed = presetChanged || orientChanged || prev === "";
|
|
1467
1486
|
setCursor(touch);
|
|
1468
1487
|
window.__wgDevicePreset = preset;
|
|
1488
|
+
window.__wgDeviceOrientation = orient;
|
|
1469
1489
|
|
|
1470
1490
|
const announce =
|
|
1471
1491
|
payload.announce === true ||
|
|
1472
1492
|
(payload.announce !== false && (sync === "set" || sync === "clear") && changed);
|
|
1473
1493
|
|
|
1474
|
-
const vp = device.viewport || {};
|
|
1475
1494
|
const size =
|
|
1476
|
-
(vp.width || "?") +
|
|
1477
|
-
|
|
1495
|
+
(vp.width || "?") +
|
|
1496
|
+
"\u00d7" +
|
|
1497
|
+
(vp.height || "?") +
|
|
1498
|
+
" \u00b7 " +
|
|
1499
|
+
orient +
|
|
1500
|
+
(touch ? " \u00b7 touch" : "");
|
|
1501
|
+
let titleText = {
|
|
1478
1502
|
mobile: "Now in mobile mode",
|
|
1479
1503
|
tablet: "Now in tablet mode",
|
|
1480
1504
|
desktop: "Now in desktop mode",
|
|
1481
|
-
};
|
|
1505
|
+
}[preset] || "Device updated";
|
|
1506
|
+
if (orientChanged && !presetChanged) {
|
|
1507
|
+
titleText =
|
|
1508
|
+
orient === "landscape" ? "Rotated to landscape" : "Rotated to portrait";
|
|
1509
|
+
}
|
|
1482
1510
|
if (announce) {
|
|
1483
|
-
showToast(preset, touch,
|
|
1511
|
+
showToast(preset, touch, titleText, size, orient);
|
|
1484
1512
|
} else {
|
|
1485
|
-
ensureChip(preset, touch, remain);
|
|
1513
|
+
ensureChip(preset, touch, remain, orient);
|
|
1486
1514
|
}
|
|
1487
1515
|
};
|
|
1488
1516
|
},
|
package/dist/highlights.d.ts
CHANGED
|
@@ -127,6 +127,8 @@ export type StubPhaseFixtures = {
|
|
|
127
127
|
viewport?: DeviceViewport;
|
|
128
128
|
/** Touch theater + Playwright touchscreen when true. */
|
|
129
129
|
touch?: boolean;
|
|
130
|
+
/** Portrait / landscape (swaps preset width/height). */
|
|
131
|
+
orientation?: DeviceOrientation;
|
|
130
132
|
};
|
|
131
133
|
/** Named viewport presets (0.13+). */
|
|
132
134
|
export type DevicePreset = "mobile" | "tablet" | "desktop";
|
|
@@ -149,14 +151,26 @@ export type DeviceState = {
|
|
|
149
151
|
hasTouch: boolean;
|
|
150
152
|
/** Demo gesture theater: finger cursor, tap/hold timing, prefer touchscreen. */
|
|
151
153
|
touchMode: boolean;
|
|
154
|
+
/** Viewport orientation (0.13.3+). Default derived from width/height. */
|
|
155
|
+
orientation?: DeviceOrientation;
|
|
152
156
|
};
|
|
153
|
-
/**
|
|
157
|
+
/** Portrait (tall) or landscape (wide). */
|
|
158
|
+
export type DeviceOrientation = "portrait" | "landscape";
|
|
159
|
+
/** Built-in preset sizes (CSS pixels) — stored in portrait for mobile/tablet. */
|
|
154
160
|
export declare const DEVICE_PRESETS: Record<DevicePreset, Omit<DeviceState, "touchMode"> & {
|
|
155
161
|
touchMode?: boolean;
|
|
156
162
|
}>;
|
|
157
163
|
export declare function normalizeDevicePreset(raw: unknown): DevicePreset | undefined;
|
|
164
|
+
export declare function normalizeDeviceOrientation(raw: unknown): DeviceOrientation | undefined;
|
|
165
|
+
/** Infer orientation from box (square counts as landscape). */
|
|
166
|
+
export declare function orientationFromViewport(vp: DeviceViewport): DeviceOrientation;
|
|
167
|
+
/**
|
|
168
|
+
* Swap width/height so the viewport matches the requested orientation.
|
|
169
|
+
* Preserves deviceScaleFactor. No-op when already oriented.
|
|
170
|
+
*/
|
|
171
|
+
export declare function applyOrientation(state: DeviceState, orientation: DeviceOrientation): DeviceState;
|
|
158
172
|
/** Resolve a preset name or partial state into a full DeviceState. */
|
|
159
|
-
export declare function resolveDeviceState(input: DevicePreset | DeviceState | DeviceViewport | undefined, touchOverride?: boolean): DeviceState | undefined;
|
|
173
|
+
export declare function resolveDeviceState(input: DevicePreset | DeviceState | DeviceViewport | undefined, touchOverride?: boolean, orientationOverride?: DeviceOrientation): DeviceState | undefined;
|
|
160
174
|
/**
|
|
161
175
|
* Carry-forward for device fixtures (mirror {@link applyTodoPhase}).
|
|
162
176
|
* - clear: hideDevice / clearDevice / device("desktop") with clear intent
|
|
@@ -307,6 +321,15 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
|
|
|
307
321
|
viewport(box: DeviceViewport): void;
|
|
308
322
|
/** Enable / disable touch theater + touchscreen gestures. */
|
|
309
323
|
touch(on?: boolean): void;
|
|
324
|
+
/**
|
|
325
|
+
* Rotate viewport to portrait or landscape (0.13.3+).
|
|
326
|
+
* Keeps current preset; swaps width/height. Toast + lerp like device().
|
|
327
|
+
*/
|
|
328
|
+
orientation(o: DeviceOrientation): void;
|
|
329
|
+
/** Alias for {@link StubCtx.orientation}("landscape"). */
|
|
330
|
+
landscape(): void;
|
|
331
|
+
/** Alias for {@link StubCtx.orientation}("portrait"). */
|
|
332
|
+
portrait(): void;
|
|
310
333
|
/**
|
|
311
334
|
* Reset to desktop viewport and clear touch mode. Omit device on a later
|
|
312
335
|
* block = **keep**; only hideDevice / clearDevice / device("desktop") clears.
|
package/dist/highlights.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* All fixture shapes share optional {@link FixtureDurationFields} so demo dwell
|
|
13
13
|
* before advancing is authored the same way on stubs, flow fixtures, and slides.
|
|
14
14
|
*/
|
|
15
|
-
/** Built-in preset sizes (CSS pixels). */
|
|
15
|
+
/** Built-in preset sizes (CSS pixels) — stored in portrait for mobile/tablet. */
|
|
16
16
|
export const DEVICE_PRESETS = {
|
|
17
17
|
mobile: {
|
|
18
18
|
preset: "mobile",
|
|
@@ -20,6 +20,7 @@ export const DEVICE_PRESETS = {
|
|
|
20
20
|
isMobile: true,
|
|
21
21
|
hasTouch: true,
|
|
22
22
|
touchMode: true,
|
|
23
|
+
orientation: "portrait",
|
|
23
24
|
},
|
|
24
25
|
tablet: {
|
|
25
26
|
preset: "tablet",
|
|
@@ -27,6 +28,7 @@ export const DEVICE_PRESETS = {
|
|
|
27
28
|
isMobile: true,
|
|
28
29
|
hasTouch: true,
|
|
29
30
|
touchMode: true,
|
|
31
|
+
orientation: "portrait",
|
|
30
32
|
},
|
|
31
33
|
desktop: {
|
|
32
34
|
preset: "desktop",
|
|
@@ -34,6 +36,7 @@ export const DEVICE_PRESETS = {
|
|
|
34
36
|
isMobile: false,
|
|
35
37
|
hasTouch: false,
|
|
36
38
|
touchMode: false,
|
|
39
|
+
orientation: "landscape",
|
|
37
40
|
},
|
|
38
41
|
};
|
|
39
42
|
export function normalizeDevicePreset(raw) {
|
|
@@ -48,67 +51,118 @@ export function normalizeDevicePreset(raw) {
|
|
|
48
51
|
return "desktop";
|
|
49
52
|
return undefined;
|
|
50
53
|
}
|
|
54
|
+
export function normalizeDeviceOrientation(raw) {
|
|
55
|
+
const s = String(raw ?? "")
|
|
56
|
+
.trim()
|
|
57
|
+
.toLowerCase();
|
|
58
|
+
if (s === "portrait" || s === "port" || s === "tall" || s === "p")
|
|
59
|
+
return "portrait";
|
|
60
|
+
if (s === "landscape" || s === "land" || s === "wide" || s === "l")
|
|
61
|
+
return "landscape";
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
/** Infer orientation from box (square counts as landscape). */
|
|
65
|
+
export function orientationFromViewport(vp) {
|
|
66
|
+
return vp.height > vp.width ? "portrait" : "landscape";
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Swap width/height so the viewport matches the requested orientation.
|
|
70
|
+
* Preserves deviceScaleFactor. No-op when already oriented.
|
|
71
|
+
*/
|
|
72
|
+
export function applyOrientation(state, orientation) {
|
|
73
|
+
const vp = { ...state.viewport };
|
|
74
|
+
const current = orientationFromViewport(vp);
|
|
75
|
+
if (current !== orientation) {
|
|
76
|
+
const w = vp.width;
|
|
77
|
+
vp.width = vp.height;
|
|
78
|
+
vp.height = w;
|
|
79
|
+
}
|
|
80
|
+
return { ...state, viewport: vp, orientation };
|
|
81
|
+
}
|
|
51
82
|
/** Resolve a preset name or partial state into a full DeviceState. */
|
|
52
|
-
export function resolveDeviceState(input, touchOverride) {
|
|
83
|
+
export function resolveDeviceState(input, touchOverride, orientationOverride) {
|
|
53
84
|
if (input === undefined)
|
|
54
85
|
return undefined;
|
|
86
|
+
let resolved;
|
|
55
87
|
if (typeof input === "string") {
|
|
56
88
|
const p = normalizeDevicePreset(input);
|
|
57
89
|
if (!p)
|
|
58
90
|
return undefined;
|
|
59
91
|
const base = DEVICE_PRESETS[p];
|
|
60
92
|
const touchMode = touchOverride !== undefined ? !!touchOverride : !!base.touchMode;
|
|
61
|
-
|
|
93
|
+
resolved = {
|
|
62
94
|
preset: base.preset,
|
|
63
95
|
viewport: { ...base.viewport },
|
|
64
96
|
isMobile: base.isMobile,
|
|
65
97
|
hasTouch: base.hasTouch || touchMode,
|
|
66
98
|
touchMode,
|
|
99
|
+
orientation: base.orientation || orientationFromViewport(base.viewport),
|
|
67
100
|
};
|
|
68
101
|
}
|
|
69
|
-
if ("width" in input && "height" in input && !("preset" in input)) {
|
|
102
|
+
else if ("width" in input && "height" in input && !("preset" in input)) {
|
|
70
103
|
const vp = input;
|
|
71
104
|
const touchMode = touchOverride !== undefined ? !!touchOverride : true;
|
|
72
|
-
|
|
105
|
+
const box = {
|
|
106
|
+
width: Math.max(200, Math.floor(vp.width)),
|
|
107
|
+
height: Math.max(200, Math.floor(vp.height)),
|
|
108
|
+
...(vp.deviceScaleFactor !== undefined
|
|
109
|
+
? { deviceScaleFactor: vp.deviceScaleFactor }
|
|
110
|
+
: { deviceScaleFactor: 2 }),
|
|
111
|
+
};
|
|
112
|
+
resolved = {
|
|
73
113
|
preset: "mobile",
|
|
74
|
-
viewport:
|
|
75
|
-
width: Math.max(200, Math.floor(vp.width)),
|
|
76
|
-
height: Math.max(200, Math.floor(vp.height)),
|
|
77
|
-
...(vp.deviceScaleFactor !== undefined
|
|
78
|
-
? { deviceScaleFactor: vp.deviceScaleFactor }
|
|
79
|
-
: { deviceScaleFactor: 2 }),
|
|
80
|
-
},
|
|
114
|
+
viewport: box,
|
|
81
115
|
isMobile: true,
|
|
82
116
|
hasTouch: true,
|
|
83
117
|
touchMode,
|
|
118
|
+
orientation: orientationFromViewport(box),
|
|
84
119
|
};
|
|
85
120
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
else {
|
|
122
|
+
const d = input;
|
|
123
|
+
const preset = normalizeDevicePreset(d.preset) || "desktop";
|
|
124
|
+
const base = DEVICE_PRESETS[preset];
|
|
125
|
+
const touchMode = touchOverride !== undefined
|
|
126
|
+
? !!touchOverride
|
|
127
|
+
: d.touchMode !== undefined
|
|
128
|
+
? !!d.touchMode
|
|
129
|
+
: !!base.touchMode;
|
|
130
|
+
const viewport = d.viewport
|
|
131
|
+
? {
|
|
132
|
+
width: Math.max(200, Math.floor(d.viewport.width)),
|
|
133
|
+
height: Math.max(200, Math.floor(d.viewport.height)),
|
|
134
|
+
...(d.viewport.deviceScaleFactor !== undefined
|
|
135
|
+
? { deviceScaleFactor: d.viewport.deviceScaleFactor }
|
|
136
|
+
: base.viewport.deviceScaleFactor !== undefined
|
|
137
|
+
? { deviceScaleFactor: base.viewport.deviceScaleFactor }
|
|
138
|
+
: {}),
|
|
139
|
+
}
|
|
140
|
+
: { ...base.viewport };
|
|
141
|
+
resolved = {
|
|
142
|
+
preset,
|
|
143
|
+
viewport,
|
|
144
|
+
isMobile: d.isMobile !== undefined ? !!d.isMobile : base.isMobile,
|
|
145
|
+
hasTouch: d.hasTouch !== undefined ? !!d.hasTouch : base.hasTouch || touchMode,
|
|
146
|
+
touchMode,
|
|
147
|
+
orientation: normalizeDeviceOrientation(d.orientation) ||
|
|
148
|
+
orientationFromViewport(viewport),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (!resolved)
|
|
152
|
+
return undefined;
|
|
153
|
+
const want = orientationOverride ||
|
|
154
|
+
(typeof input === "object" && input && "orientation" in input
|
|
155
|
+
? normalizeDeviceOrientation(input.orientation)
|
|
156
|
+
: undefined);
|
|
157
|
+
if (want)
|
|
158
|
+
return applyOrientation(resolved, want);
|
|
159
|
+
if (!resolved.orientation) {
|
|
160
|
+
resolved = {
|
|
161
|
+
...resolved,
|
|
162
|
+
orientation: orientationFromViewport(resolved.viewport),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return resolved;
|
|
112
166
|
}
|
|
113
167
|
/**
|
|
114
168
|
* Carry-forward for device fixtures (mirror {@link applyTodoPhase}).
|
|
@@ -734,7 +788,7 @@ function createStubCtx(bag, opts) {
|
|
|
734
788
|
device(preset) {
|
|
735
789
|
bag.deviceTouched = true;
|
|
736
790
|
bag.hideDevice = false;
|
|
737
|
-
const resolved = resolveDeviceState(preset, bag.touchOverride);
|
|
791
|
+
const resolved = resolveDeviceState(preset, bag.touchOverride, bag.orientationOverride);
|
|
738
792
|
if (resolved) {
|
|
739
793
|
bag.device = resolved;
|
|
740
794
|
if (resolved.preset === "desktop" && !resolved.touchMode) {
|
|
@@ -745,7 +799,7 @@ function createStubCtx(bag, opts) {
|
|
|
745
799
|
viewport(box) {
|
|
746
800
|
bag.deviceTouched = true;
|
|
747
801
|
bag.hideDevice = false;
|
|
748
|
-
const resolved = resolveDeviceState(box, bag.touchOverride);
|
|
802
|
+
const resolved = resolveDeviceState(box, bag.touchOverride, bag.orientationOverride);
|
|
749
803
|
if (resolved)
|
|
750
804
|
bag.device = resolved;
|
|
751
805
|
},
|
|
@@ -765,22 +819,43 @@ function createStubCtx(bag, opts) {
|
|
|
765
819
|
bag.hideDevice = false;
|
|
766
820
|
}
|
|
767
821
|
else if (enabled) {
|
|
768
|
-
const base = resolveDeviceState("desktop", true);
|
|
822
|
+
const base = resolveDeviceState("desktop", true, bag.orientationOverride);
|
|
769
823
|
if (base)
|
|
770
824
|
bag.device = { ...base, touchMode: true, hasTouch: true };
|
|
771
825
|
}
|
|
772
826
|
},
|
|
827
|
+
orientation(o) {
|
|
828
|
+
const want = normalizeDeviceOrientation(o);
|
|
829
|
+
if (!want)
|
|
830
|
+
return;
|
|
831
|
+
bag.deviceTouched = true;
|
|
832
|
+
bag.hideDevice = false;
|
|
833
|
+
bag.orientationOverride = want;
|
|
834
|
+
const base = bag.device ||
|
|
835
|
+
resolveDeviceState("mobile", bag.touchOverride) ||
|
|
836
|
+
resolveDeviceState("mobile");
|
|
837
|
+
if (base)
|
|
838
|
+
bag.device = applyOrientation(base, want);
|
|
839
|
+
},
|
|
840
|
+
landscape() {
|
|
841
|
+
this.orientation("landscape");
|
|
842
|
+
},
|
|
843
|
+
portrait() {
|
|
844
|
+
this.orientation("portrait");
|
|
845
|
+
},
|
|
773
846
|
hideDevice() {
|
|
774
847
|
bag.deviceTouched = true;
|
|
775
848
|
bag.hideDevice = true;
|
|
776
849
|
bag.device = resolveDeviceState("desktop", false);
|
|
777
850
|
bag.touchOverride = false;
|
|
851
|
+
bag.orientationOverride = undefined;
|
|
778
852
|
},
|
|
779
853
|
clearDevice() {
|
|
780
854
|
bag.deviceTouched = true;
|
|
781
855
|
bag.hideDevice = true;
|
|
782
856
|
bag.device = resolveDeviceState("desktop", false);
|
|
783
857
|
bag.touchOverride = false;
|
|
858
|
+
bag.orientationOverride = undefined;
|
|
784
859
|
},
|
|
785
860
|
zoom(n) {
|
|
786
861
|
if (Number.isFinite(n) && n > 0)
|
|
@@ -834,10 +909,23 @@ function createStubCtx(bag, opts) {
|
|
|
834
909
|
bag.deviceTouched = true;
|
|
835
910
|
bag.touchOverride = !!partial.touch;
|
|
836
911
|
}
|
|
912
|
+
if (partial.orientation !== undefined) {
|
|
913
|
+
const want = normalizeDeviceOrientation(partial.orientation);
|
|
914
|
+
if (want) {
|
|
915
|
+
bag.deviceTouched = true;
|
|
916
|
+
bag.hideDevice = false;
|
|
917
|
+
bag.orientationOverride = want;
|
|
918
|
+
const base = bag.device ||
|
|
919
|
+
resolveDeviceState("mobile", bag.touchOverride) ||
|
|
920
|
+
resolveDeviceState("mobile");
|
|
921
|
+
if (base)
|
|
922
|
+
bag.device = applyOrientation(base, want);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
837
925
|
if (partial.device !== undefined) {
|
|
838
926
|
bag.deviceTouched = true;
|
|
839
927
|
bag.hideDevice = false;
|
|
840
|
-
const resolved = resolveDeviceState(partial.device, bag.touchOverride);
|
|
928
|
+
const resolved = resolveDeviceState(partial.device, bag.touchOverride, bag.orientationOverride);
|
|
841
929
|
if (resolved) {
|
|
842
930
|
bag.device = resolved;
|
|
843
931
|
if (resolved.preset === "desktop" && !resolved.touchMode)
|
|
@@ -847,7 +935,7 @@ function createStubCtx(bag, opts) {
|
|
|
847
935
|
if (partial.viewport !== undefined) {
|
|
848
936
|
bag.deviceTouched = true;
|
|
849
937
|
bag.hideDevice = false;
|
|
850
|
-
const resolved = resolveDeviceState(partial.viewport, bag.touchOverride);
|
|
938
|
+
const resolved = resolveDeviceState(partial.viewport, bag.touchOverride, bag.orientationOverride);
|
|
851
939
|
if (resolved)
|
|
852
940
|
bag.device = resolved;
|
|
853
941
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ export { connect, checkpoint } from "./types.js";
|
|
|
3
3
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
4
4
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
5
5
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
6
|
-
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, DEVICE_PRESETS, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
7
|
-
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, } from "./highlights.js";
|
|
6
|
+
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
7
|
+
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, DeviceOrientation, } from "./highlights.js";
|
|
8
8
|
export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
9
9
|
export type { ActionPage } from "./types.js";
|
|
10
10
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { connect, checkpoint } from "./types.js";
|
|
|
2
2
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
3
3
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
4
4
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
5
|
-
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, DEVICE_PRESETS, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
5
|
+
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
6
6
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
7
7
|
export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp, } from "./blocks-select.js";
|
|
8
8
|
export { EdgeLeaseCoordinator } from "./traverse-lease.js";
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record a .webm of: portrait/landscape + click / tap / hold theater.
|
|
3
|
+
* Square capture so orientation changes are obvious in the video frame.
|
|
4
|
+
*
|
|
5
|
+
* From examples/saucedemo:
|
|
6
|
+
* node scripts/demo-orientation-gestures-video.mjs
|
|
7
|
+
*/
|
|
8
|
+
import { chromium } from "playwright";
|
|
9
|
+
import { mkdirSync, copyFileSync, readdirSync } from "node:fs";
|
|
10
|
+
import { join, dirname } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const outDir = join(__dirname, "../.tmp/device-demo");
|
|
15
|
+
mkdirSync(outDir, { recursive: true });
|
|
16
|
+
|
|
17
|
+
const CAPTURE = { width: 1080, height: 1080 };
|
|
18
|
+
|
|
19
|
+
const PRESETS = {
|
|
20
|
+
mobilePortrait: { w: 390, h: 844, preset: "mobile", orient: "portrait", touch: true },
|
|
21
|
+
mobileLandscape: { w: 844, h: 390, preset: "mobile", orient: "landscape", touch: true },
|
|
22
|
+
tabletPortrait: { w: 768, h: 1024, preset: "tablet", orient: "portrait", touch: true },
|
|
23
|
+
tabletLandscape: { w: 1024, h: 768, preset: "tablet", orient: "landscape", touch: true },
|
|
24
|
+
desktop: { w: 1280, h: 720, preset: "desktop", orient: "landscape", touch: false },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const CSS = `
|
|
28
|
+
html,body{margin:0;background:#0b1220;}
|
|
29
|
+
#wg-stage{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;
|
|
30
|
+
background:radial-gradient(ellipse at 50% 30%,#1a1030 0%,#0b1220 70%);}
|
|
31
|
+
#wg-frame{position:relative;background:#fff;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.55);
|
|
32
|
+
border:3px solid #2a1650;border-radius:18px;transition:width .5s cubic-bezier(.22,1,.36,1),
|
|
33
|
+
height .5s cubic-bezier(.22,1,.36,1),border-radius .4s ease;}
|
|
34
|
+
#wg-frame iframe{border:0;width:100%;height:100%;display:block;}
|
|
35
|
+
#wg-device-toast{position:fixed;z-index:2147483647;top:18px;right:18px;
|
|
36
|
+
display:flex;align-items:center;gap:10px;min-width:220px;max-width:340px;
|
|
37
|
+
padding:12px 14px;border-radius:14px;background:rgba(20,10,40,.96);color:#f0e8ff;
|
|
38
|
+
border:1px solid rgba(124,58,237,.55);box-shadow:0 10px 28px rgba(0,0,0,.45);
|
|
39
|
+
font:600 13px/1.35 system-ui,sans-serif;pointer-events:none;
|
|
40
|
+
opacity:0;transform:translateX(18px);transition:opacity .35s ease,transform .45s cubic-bezier(.22,1,.36,1);}
|
|
41
|
+
#wg-device-toast.wg-in{opacity:1;transform:translateX(0);}
|
|
42
|
+
#wg-device-toast[data-preset=mobile]{border-color:#3B82F6;}
|
|
43
|
+
#wg-device-toast[data-preset=tablet]{border-color:#22C55E;}
|
|
44
|
+
#wg-device-toast[data-preset=desktop]{border-color:#9CA3AF;}
|
|
45
|
+
#wg-device-toast .wg-dev-icon{width:36px;height:36px;border-radius:10px;display:flex;
|
|
46
|
+
align-items:center;justify-content:center;background:rgba(124,58,237,.28);}
|
|
47
|
+
#wg-device-toast .wg-dev-title{font:800 13px/1.2 system-ui,sans-serif;color:#fff;}
|
|
48
|
+
#wg-device-toast .wg-dev-sub{margin-top:3px;font:600 11px/1.3 system-ui,sans-serif;color:#c9a6ff;}
|
|
49
|
+
#wg-gesture-banner{position:fixed;z-index:2147483646;left:18px;bottom:18px;
|
|
50
|
+
padding:10px 14px;border-radius:12px;background:rgba(20,10,40,.94);color:#fff;
|
|
51
|
+
font:700 13px/1.3 system-ui,sans-serif;border:1px solid rgba(124,58,237,.45);}
|
|
52
|
+
#wg-cursor{position:fixed;z-index:2147483647;width:28px;height:28px;pointer-events:none;
|
|
53
|
+
left:0;top:0;opacity:0;margin:0;transition:transform .55s cubic-bezier(.22,1,.36,1),opacity .2s;}
|
|
54
|
+
#wg-click-pulse{position:fixed;z-index:2147483647;width:18px;height:18px;margin-left:-9px;
|
|
55
|
+
margin-top:-9px;border-radius:50%;pointer-events:none;opacity:0;border:2px solid #7C3AED;
|
|
56
|
+
background:rgba(124,58,237,.3);}
|
|
57
|
+
#wg-click-pulse.wg-pulse{animation:wg-pulse .55s ease-out;}
|
|
58
|
+
#wg-click-pulse[data-hold=1]{border-color:#EAB308;background:rgba(234,179,8,.35);
|
|
59
|
+
width:28px;height:28px;margin-left:-14px;margin-top:-14px;}
|
|
60
|
+
@keyframes wg-pulse{0%{opacity:.95;transform:scale(.35);}100%{opacity:0;transform:scale(2.6);}}
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const ICONS = {
|
|
64
|
+
mobile:
|
|
65
|
+
'<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="#fff" stroke-width="1.8"><rect x="7" y="2.5" width="10" height="19" rx="2.2"/><circle cx="12" cy="18.2" r="1.1" fill="#fff" stroke="none"/></svg>',
|
|
66
|
+
tablet:
|
|
67
|
+
'<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="#fff" stroke-width="1.8"><rect x="3.5" y="4" width="17" height="16" rx="2"/><circle cx="12" cy="17.2" r="1" fill="#fff" stroke="none"/></svg>',
|
|
68
|
+
desktop:
|
|
69
|
+
'<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="#fff" stroke-width="1.8"><rect x="2.5" y="3.5" width="19" height="12.5" rx="1.5"/><path d="M8 20h8M12 16v4"/></svg>',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const MOUSE =
|
|
73
|
+
"<svg viewBox='0 0 32 32' width='24' height='24'><path fill='#0C0C1A' stroke='#fff' stroke-width='1.4' d='M6 3.5l1.4 22.5 5.8-5.4 4.2 9.4 3.6-1.6-4.2-9.2H26z'/></svg>";
|
|
74
|
+
const FINGER =
|
|
75
|
+
"<svg viewBox='0 0 32 32' width='28' height='28'><ellipse cx='16' cy='22' rx='7' ry='8' fill='#0C0C1A' stroke='#fff' stroke-width='1.4'/><rect x='12' y='6' width='8' height='16' rx='4' fill='#0C0C1A' stroke='#fff' stroke-width='1.4'/></svg>";
|
|
76
|
+
|
|
77
|
+
const executablePath = process.env.CHROME_PATH || process.env.CHROMIUM_PATH || undefined;
|
|
78
|
+
|
|
79
|
+
const browser = await chromium.launch({
|
|
80
|
+
headless: false,
|
|
81
|
+
...(executablePath ? { executablePath } : {}),
|
|
82
|
+
});
|
|
83
|
+
const context = await browser.newContext({
|
|
84
|
+
hasTouch: true,
|
|
85
|
+
viewport: CAPTURE,
|
|
86
|
+
recordVideo: { dir: outDir, size: CAPTURE },
|
|
87
|
+
deviceScaleFactor: 1,
|
|
88
|
+
});
|
|
89
|
+
const page = await context.newPage();
|
|
90
|
+
|
|
91
|
+
await page.setContent(`<!doctype html><html><head><style>${CSS}</style></head>
|
|
92
|
+
<body>
|
|
93
|
+
<div id="wg-stage"><div id="wg-frame" style="width:390px;height:844px">
|
|
94
|
+
<iframe id="wg-app" src="https://www.saucedemo.com/"></iframe>
|
|
95
|
+
</div></div>
|
|
96
|
+
<div id="wg-gesture-banner">waygraph · orientation + gestures</div>
|
|
97
|
+
<div id="wg-cursor">${FINGER}</div>
|
|
98
|
+
<div id="wg-click-pulse"></div>
|
|
99
|
+
</body></html>`);
|
|
100
|
+
|
|
101
|
+
await page.waitForTimeout(2500);
|
|
102
|
+
|
|
103
|
+
async function toast(title, sub, preset) {
|
|
104
|
+
await page.evaluate(
|
|
105
|
+
({ title, sub, preset, icons }) => {
|
|
106
|
+
let t = document.getElementById("wg-device-toast");
|
|
107
|
+
if (!t) {
|
|
108
|
+
t = document.createElement("div");
|
|
109
|
+
t.id = "wg-device-toast";
|
|
110
|
+
document.body.appendChild(t);
|
|
111
|
+
}
|
|
112
|
+
t.dataset.preset = preset;
|
|
113
|
+
t.innerHTML =
|
|
114
|
+
'<span class="wg-dev-icon">' +
|
|
115
|
+
icons[preset] +
|
|
116
|
+
'</span><span><div class="wg-dev-title">' +
|
|
117
|
+
title +
|
|
118
|
+
'</div><div class="wg-dev-sub">' +
|
|
119
|
+
sub +
|
|
120
|
+
"</div></span>";
|
|
121
|
+
t.classList.remove("wg-in");
|
|
122
|
+
void t.offsetWidth;
|
|
123
|
+
t.classList.add("wg-in");
|
|
124
|
+
},
|
|
125
|
+
{ title, sub, preset, icons: ICONS },
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function setFrame(key) {
|
|
130
|
+
const p = PRESETS[key];
|
|
131
|
+
const title =
|
|
132
|
+
key.includes("Landscape") || p.orient === "landscape"
|
|
133
|
+
? p.preset === "desktop"
|
|
134
|
+
? "Back to desktop"
|
|
135
|
+
: "Rotated to landscape"
|
|
136
|
+
: key.includes("Portrait") || p.orient === "portrait"
|
|
137
|
+
? p.preset === "mobile" && key === "mobilePortrait"
|
|
138
|
+
? "Now in mobile mode"
|
|
139
|
+
: "Rotated to portrait"
|
|
140
|
+
: "Device updated";
|
|
141
|
+
// First switch: mobile portrait uses "Now in mobile"
|
|
142
|
+
const t =
|
|
143
|
+
key === "mobilePortrait"
|
|
144
|
+
? "Now in mobile mode"
|
|
145
|
+
: key === "desktop"
|
|
146
|
+
? "Back to desktop"
|
|
147
|
+
: key.includes("Landscape")
|
|
148
|
+
? "Rotated to landscape"
|
|
149
|
+
: key.includes("Portrait")
|
|
150
|
+
? key.startsWith("tablet")
|
|
151
|
+
? "Now in tablet mode"
|
|
152
|
+
: "Rotated to portrait"
|
|
153
|
+
: title;
|
|
154
|
+
|
|
155
|
+
await page.evaluate(
|
|
156
|
+
({ w, h, touch, finger, mouse }) => {
|
|
157
|
+
const frame = document.getElementById("wg-frame");
|
|
158
|
+
frame.style.width = w + "px";
|
|
159
|
+
frame.style.height = h + "px";
|
|
160
|
+
frame.style.borderRadius = touch ? (w < h ? "28px" : "18px") : "10px";
|
|
161
|
+
const c = document.getElementById("wg-cursor");
|
|
162
|
+
c.innerHTML = touch ? finger : mouse;
|
|
163
|
+
},
|
|
164
|
+
{ w: p.w, h: p.h, touch: p.touch, finger: FINGER, mouse: MOUSE },
|
|
165
|
+
);
|
|
166
|
+
await toast(t, `${p.w}×${p.h} · ${p.orient}${p.touch ? " · touch" : " · mouse"}`, p.preset);
|
|
167
|
+
await page.waitForTimeout(900);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function banner(text) {
|
|
171
|
+
await page.evaluate((text) => {
|
|
172
|
+
const b = document.getElementById("wg-gesture-banner");
|
|
173
|
+
if (b) b.textContent = text;
|
|
174
|
+
}, text);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function gestureAt(kind) {
|
|
178
|
+
// Aim at center of the device frame (login button-ish on saucedemo).
|
|
179
|
+
const box = await page.locator("#wg-frame").boundingBox();
|
|
180
|
+
if (!box) return;
|
|
181
|
+
const x = box.x + box.width * 0.5;
|
|
182
|
+
const y = box.y + box.height * 0.58;
|
|
183
|
+
await banner(
|
|
184
|
+
kind === "click"
|
|
185
|
+
? "Gesture: CLICK (mouse)"
|
|
186
|
+
: kind === "tap"
|
|
187
|
+
? "Gesture: TAP (touch)"
|
|
188
|
+
: "Gesture: HOLD (long-press)",
|
|
189
|
+
);
|
|
190
|
+
await page.evaluate(
|
|
191
|
+
({ x, y, kind, ms }) => {
|
|
192
|
+
const c = document.getElementById("wg-cursor");
|
|
193
|
+
c.style.setProperty("--wg-cursor-ms", ms + "ms");
|
|
194
|
+
c.style.opacity = "1";
|
|
195
|
+
c.style.transform = `translate(${x - 8}px,${y - 8}px)`;
|
|
196
|
+
},
|
|
197
|
+
{ x, y, kind, ms: 550 },
|
|
198
|
+
);
|
|
199
|
+
await page.waitForTimeout(600);
|
|
200
|
+
await page.evaluate(
|
|
201
|
+
({ x, y, kind }) => {
|
|
202
|
+
const p = document.getElementById("wg-click-pulse");
|
|
203
|
+
p.style.left = x + "px";
|
|
204
|
+
p.style.top = y + "px";
|
|
205
|
+
p.dataset.hold = kind === "hold" ? "1" : "0";
|
|
206
|
+
p.classList.remove("wg-pulse");
|
|
207
|
+
void p.offsetWidth;
|
|
208
|
+
p.classList.add("wg-pulse");
|
|
209
|
+
},
|
|
210
|
+
{ x, y, kind },
|
|
211
|
+
);
|
|
212
|
+
await page.waitForTimeout(kind === "hold" ? 900 : 550);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Sequence: portrait -> landscape (shows in square video) + gestures
|
|
216
|
+
await setFrame("mobilePortrait");
|
|
217
|
+
await page.waitForTimeout(1200);
|
|
218
|
+
await gestureAt("tap");
|
|
219
|
+
await page.waitForTimeout(700);
|
|
220
|
+
|
|
221
|
+
await setFrame("mobileLandscape");
|
|
222
|
+
await page.waitForTimeout(1400);
|
|
223
|
+
await gestureAt("hold");
|
|
224
|
+
await page.waitForTimeout(700);
|
|
225
|
+
|
|
226
|
+
await setFrame("tabletPortrait");
|
|
227
|
+
await page.waitForTimeout(1200);
|
|
228
|
+
await gestureAt("tap");
|
|
229
|
+
await page.waitForTimeout(600);
|
|
230
|
+
|
|
231
|
+
await setFrame("tabletLandscape");
|
|
232
|
+
await page.waitForTimeout(1400);
|
|
233
|
+
await gestureAt("click");
|
|
234
|
+
await page.waitForTimeout(800);
|
|
235
|
+
|
|
236
|
+
await setFrame("desktop");
|
|
237
|
+
await page.waitForTimeout(1500);
|
|
238
|
+
|
|
239
|
+
await banner("waygraph · orientation + gestures · done");
|
|
240
|
+
await page.waitForTimeout(1000);
|
|
241
|
+
|
|
242
|
+
const vid = page.video();
|
|
243
|
+
await context.close();
|
|
244
|
+
await browser.close();
|
|
245
|
+
|
|
246
|
+
let saved = null;
|
|
247
|
+
if (vid) {
|
|
248
|
+
const raw = await vid.path();
|
|
249
|
+
saved = join(outDir, "orientation-gestures.webm");
|
|
250
|
+
try {
|
|
251
|
+
copyFileSync(raw, saved);
|
|
252
|
+
} catch {
|
|
253
|
+
saved = raw;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
console.log("ORIENTATION_GESTURES_VIDEO", saved || outDir);
|
|
257
|
+
console.log(
|
|
258
|
+
"files",
|
|
259
|
+
readdirSync(outDir)
|
|
260
|
+
.filter((f) => f.endsWith(".webm"))
|
|
261
|
+
.join(", "),
|
|
262
|
+
);
|