Improve videos
This commit is contained in:
parent
4012e4e047
commit
d3418c67cc
11 changed files with 988 additions and 869 deletions
|
|
@ -101,7 +101,10 @@ async function runCue(
|
|||
videoTimeMs: cursor.ms + leadInMs,
|
||||
durationMs: measuredAudioMs,
|
||||
});
|
||||
await showCaption(ctx.page, cue.text, cue.captionPlacement);
|
||||
// The spoken line is never rendered; only an explicit short caption is.
|
||||
if (cue.caption) {
|
||||
await showCaption(ctx.page, cue.caption, cue.captionPlacement);
|
||||
}
|
||||
|
||||
const during = cue.during ?? [];
|
||||
const declaredSum = during.reduce((s, a) => s + a.durationMs, 0);
|
||||
|
|
@ -126,7 +129,9 @@ async function runCue(
|
|||
cursor.ms += duringElapsed;
|
||||
}
|
||||
|
||||
await hideCaption(ctx.page);
|
||||
if (cue.caption) {
|
||||
await hideCaption(ctx.page);
|
||||
}
|
||||
|
||||
for (const step of cue.tail ?? []) {
|
||||
cursor.ms += await runStep(ctx, step);
|
||||
|
|
@ -185,6 +190,12 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise<void> {
|
|||
}
|
||||
case 'click': {
|
||||
const selectionVersion = ctx.dashboard.getSelectionStatsVersion();
|
||||
// Hexagon targets are projected from the latest map response; make
|
||||
// sure that response corresponds to the settled viewport (a zoom in
|
||||
// the previous cue may still have postcode fetches in flight).
|
||||
if (step.target.kind === 'hexagon') {
|
||||
await ctx.dashboard.waitForApiIdle(3000);
|
||||
}
|
||||
const candidates =
|
||||
step.target.kind === 'hexagon' && step.waitForSelectionReady
|
||||
? await ctx.dashboard.visibleHexagonTargets(4)
|
||||
|
|
@ -228,7 +239,7 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise<void> {
|
|||
const mapVersion = ctx.dashboard.getMapDataVersion();
|
||||
const delta = step.direction === 'out' ? -MAP_ZOOM_WHEEL_DELTA : MAP_ZOOM_WHEEL_DELTA;
|
||||
const handled = await ctx.page.evaluate(
|
||||
async ({ x, y, steps, durationMs, direction }) => {
|
||||
async ({ x, y, steps, durationMs, direction, center }) => {
|
||||
const root = document.querySelector('.maplibregl-map') as HTMLElement | null;
|
||||
const fiberKey = root
|
||||
? Object.getOwnPropertyNames(root).find((key) => key.startsWith('__reactFiber$'))
|
||||
|
|
@ -264,6 +275,12 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise<void> {
|
|||
zoom: number,
|
||||
options: { around?: unknown; duration?: number; essential?: boolean }
|
||||
) => void;
|
||||
easeTo?: (options: {
|
||||
center?: unknown;
|
||||
zoom?: number;
|
||||
duration?: number;
|
||||
essential?: boolean;
|
||||
}) => void;
|
||||
};
|
||||
if (
|
||||
typeof mapApi.getCanvas !== 'function' ||
|
||||
|
|
@ -281,7 +298,16 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise<void> {
|
|||
const minZoom = mapApi.getMinZoom?.() ?? 0;
|
||||
const maxZoom = mapApi.getMaxZoom?.() ?? 22;
|
||||
const targetZoom = Math.max(minZoom, Math.min(maxZoom, mapApi.getZoom() + zoomDelta));
|
||||
mapApi.zoomTo(targetZoom, { around, duration: durationMs, essential: true });
|
||||
if (center && typeof mapApi.easeTo === 'function') {
|
||||
mapApi.easeTo({
|
||||
center: around,
|
||||
zoom: targetZoom,
|
||||
duration: durationMs,
|
||||
essential: true,
|
||||
});
|
||||
} else {
|
||||
mapApi.zoomTo(targetZoom, { around, duration: durationMs, essential: true });
|
||||
}
|
||||
await new Promise((resolve) => window.setTimeout(resolve, durationMs));
|
||||
return true;
|
||||
},
|
||||
|
|
@ -291,6 +317,7 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise<void> {
|
|||
steps: step.steps,
|
||||
durationMs: step.durationMs,
|
||||
direction: step.direction,
|
||||
center: step.center ?? false,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -363,6 +390,41 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise<void> {
|
|||
case 'scrollPane':
|
||||
await scrollPaneTo(ctx.page, step.selector, step.top);
|
||||
return;
|
||||
case 'dragSheet': {
|
||||
const sheet = ctx.page.locator('section[class*="rounded-t-2xl"]').first();
|
||||
const sheetBox = await sheet.boundingBox().catch(() => null);
|
||||
if (!sheetBox) return; // desktop layout — nothing to drag
|
||||
const handle = ctx.page
|
||||
.locator('section[class*="rounded-t-2xl"] [class*="touch-none"]')
|
||||
.first();
|
||||
const handleBox = (await handle.boundingBox().catch(() => null)) ?? {
|
||||
x: sheetBox.x,
|
||||
y: sheetBox.y + 4,
|
||||
width: sheetBox.width,
|
||||
height: 20,
|
||||
};
|
||||
const viewport = ctx.page.viewportSize() ?? { width: 540, height: 960 };
|
||||
const startX = handleBox.x + handleBox.width / 2;
|
||||
const startY = handleBox.y + handleBox.height / 2;
|
||||
// The sheet resizes so its top tracks the pointer (the component
|
||||
// clamps to its own min/max). Aim the pointer where the handle would
|
||||
// sit when the sheet covers `toHeightFrac` of the viewport.
|
||||
const handleOffset = startY - sheetBox.y;
|
||||
const endY = viewport.height * (1 - step.toHeightFrac) + handleOffset;
|
||||
const approachMs = Math.min(260, Math.max(140, Math.round(step.durationMs * 0.25)));
|
||||
const dragMs = Math.max(180, step.durationMs - approachMs - 80);
|
||||
await smoothMove(ctx.page, ctx.cursor, { x: startX, y: startY }, { durationMs: approachMs });
|
||||
await ctx.page.mouse.down();
|
||||
await smoothMove(
|
||||
ctx.page,
|
||||
{ x: startX, y: startY },
|
||||
{ x: startX, y: endY },
|
||||
{ durationMs: dragMs, realMouse: true }
|
||||
);
|
||||
await ctx.page.mouse.up();
|
||||
ctx.cursor = { x: startX, y: endY };
|
||||
return;
|
||||
}
|
||||
case 'openFilterGroup':
|
||||
// Click is idempotent: if the group is already expanded, the click
|
||||
// would collapse it — which we don't want. Detect via aria-expanded
|
||||
|
|
@ -375,6 +437,9 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise<void> {
|
|||
trigger.click();
|
||||
}, step.selector);
|
||||
return;
|
||||
case 'pressKey':
|
||||
await ctx.page.keyboard.press(step.key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -395,7 +460,9 @@ async function resolveTarget(
|
|||
y: Math.round(Math.min(1, Math.max(0, target.yFrac)) * viewport.height),
|
||||
};
|
||||
}
|
||||
const box = await ctx.page.locator(target.selector).boundingBox();
|
||||
// Bounded wait: a storyboard pointing at a missing element should fail in
|
||||
// seconds with a clear error, not stall for Playwright's default 30s.
|
||||
const box = await ctx.page.locator(target.selector).boundingBox({ timeout: 5000 });
|
||||
if (!box) throw new Error(`No bounding box for selector: ${target.selector}`);
|
||||
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue