More FE changes
This commit is contained in:
parent
f114ada255
commit
a48eb945e0
48 changed files with 4127 additions and 1751 deletions
|
|
@ -1,24 +1,19 @@
|
|||
import type { Page } from 'playwright';
|
||||
import { installCursor, installZoomWrapper } from './dom.js';
|
||||
import { DashboardRecorder } from './dashboard.js';
|
||||
import { installCursor, installZoomWrapper } from './dom.js';
|
||||
import { sleep } from './motion.js';
|
||||
import { dashboardUrl } from './routes.js';
|
||||
import {
|
||||
prepareAiBox,
|
||||
sceneAiCloseUp,
|
||||
sceneClusterClick,
|
||||
sceneExportAndOutro,
|
||||
sceneTravelTimeSlider,
|
||||
sceneZoomOutResults,
|
||||
type SceneCtx,
|
||||
} from './scenes.js';
|
||||
import { runStoryboard, type RunnerResult } from './runner.js';
|
||||
import type { ScriptCtx, Storyboard } from './script.js';
|
||||
|
||||
export interface TimelineResult {
|
||||
sceneStartMs: number;
|
||||
sceneEndMs: number;
|
||||
}
|
||||
export type TimelineResult = RunnerResult;
|
||||
|
||||
export async function prepareTimeline(page: Page): Promise<SceneCtx> {
|
||||
/**
|
||||
* Boot the dashboard, wait for the first map response, and inject the
|
||||
* recording chrome (cursor, zoom wrapper, caption layer). Also opens the
|
||||
* AI prompt textarea so the storyboard can begin typing immediately.
|
||||
*/
|
||||
export async function prepareTimeline(page: Page): Promise<ScriptCtx> {
|
||||
const dashboard = new DashboardRecorder(page);
|
||||
const initialMapVersion = dashboard.getMapDataVersion();
|
||||
await page.goto(dashboardUrl(), { waitUntil: 'domcontentloaded' });
|
||||
|
|
@ -29,33 +24,46 @@ export async function prepareTimeline(page: Page): Promise<SceneCtx> {
|
|||
await page.locator('canvas').first().waitFor({ state: 'attached', timeout: 15000 });
|
||||
await dashboard.waitForMapSettled(initialMapVersion, 15000);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 400));
|
||||
await sleep(400);
|
||||
await installZoomWrapper(page);
|
||||
await installCursor(page);
|
||||
|
||||
const ctx: SceneCtx = { page, dashboard, cursor: { x: 200, y: 240 } };
|
||||
const ctx: ScriptCtx = { page, dashboard, cursor: { x: 200, y: 240 } };
|
||||
await page.mouse.move(ctx.cursor.x, ctx.cursor.y);
|
||||
await prepareAiBox(ctx);
|
||||
await sleep(80);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export async function runTimeline(ctx: SceneCtx): Promise<TimelineResult> {
|
||||
const sceneStartMs = Date.now();
|
||||
let mark = sceneStartMs;
|
||||
|
||||
mark = await runScene('AI close-up', mark, () => sceneAiCloseUp(ctx));
|
||||
mark = await runScene('Zoom out', mark, () => sceneZoomOutResults(ctx));
|
||||
mark = await runScene('TT slider', mark, () => sceneTravelTimeSlider(ctx));
|
||||
mark = await runScene('Cluster click', mark, () => sceneClusterClick(ctx));
|
||||
mark = await runScene('Export + outro', mark, () => sceneExportAndOutro(ctx));
|
||||
|
||||
return { sceneStartMs, sceneEndMs: mark };
|
||||
export async function runTimeline(
|
||||
ctx: ScriptCtx,
|
||||
storyboard: Storyboard
|
||||
): Promise<TimelineResult> {
|
||||
return runStoryboard(ctx, storyboard);
|
||||
}
|
||||
|
||||
async function runScene(label: string, prev: number, scene: () => Promise<void>): Promise<number> {
|
||||
await scene();
|
||||
const now = Date.now();
|
||||
console.log(`[scene] ${label}: ${((now - prev) / 1000).toFixed(2)}s wall`);
|
||||
return now;
|
||||
/**
|
||||
* Open the AI prompt before the timed scene starts. This is preparation
|
||||
* work, not part of the storyboard, because waiting for the textarea to
|
||||
* appear has indeterminate duration.
|
||||
*/
|
||||
async function prepareAiBox(ctx: ScriptCtx): Promise<void> {
|
||||
const { page } = ctx;
|
||||
const aiRoot = page.locator('[data-tutorial="ai-filters"]').first();
|
||||
await aiRoot.waitFor({ state: 'visible', timeout: 15000 });
|
||||
|
||||
const textarea = page.locator('[data-tutorial="ai-filters"] textarea');
|
||||
if (!(await textarea.isVisible().catch(() => false))) {
|
||||
const aiButton = aiRoot.locator('button').first();
|
||||
await aiButton.waitFor({ state: 'visible', timeout: 8000 });
|
||||
const btnBox = await aiButton.boundingBox();
|
||||
if (btnBox) await page.mouse.click(btnBox.x + btnBox.width / 2, btnBox.y + btnBox.height / 2);
|
||||
}
|
||||
if (!(await textarea.isVisible().catch(() => false))) {
|
||||
await page.evaluate(() => {
|
||||
document.querySelector<HTMLElement>('[data-tutorial="ai-filters"] button')?.click();
|
||||
});
|
||||
}
|
||||
await textarea.waitFor({ state: 'visible', timeout: 15000 });
|
||||
await sleep(100);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue