94 lines
4.9 KiB
TypeScript
94 lines
4.9 KiB
TypeScript
// Get started 1: the tutorial library. Every video uses the same small library (library.json, shipped
|
||
// with the videos as "WP Tutorials library.json"). A new install starts empty, so this one empties the
|
||
// library off camera, shows the empty dashboard, imports the file on Import / Export › Import, and
|
||
// shows the items. It names the example workflows' "Add to my library" note button as the other way.
|
||
// If the take breaks halfway, the library is loaded back so the next tutorials still have it.
|
||
import path from "node:path";
|
||
import { resetLibrary } from "../../bench/shared.js";
|
||
import { tutorial } from "../tutorial.js";
|
||
|
||
tutorial(
|
||
"T01",
|
||
{
|
||
title: "Before you start: the tutorial library",
|
||
description: "Every tutorial uses the same small library. Import it once so you can follow along.",
|
||
learn: "An empty library, Import / Export › Import, importing a file, where the items show up.",
|
||
},
|
||
async (t) => {
|
||
const page = t.page;
|
||
const fixture = path.join(t.packDir, "library.json");
|
||
// Off camera: an empty library, as on a new install.
|
||
const lib = await t.h.library();
|
||
for (const x of lib.bundles) await t.h.api(`/wp/api/bundles/${x.id}`, { method: "DELETE" });
|
||
for (const x of lib.templates) await t.h.api(`/wp/api/templates/${x.id}`, { method: "DELETE" });
|
||
for (const type of ["constraint", "derivation", "combine", "fixed_values", "wildcard"])
|
||
for (const x of lib.modules.filter((m) => m.type === type)) await t.h.api(`/wp/api/modules/${x.id}`, { method: "DELETE" });
|
||
|
||
let done = false;
|
||
try {
|
||
await t.h.step("An empty library", async () => {
|
||
await t.go("/wp/");
|
||
await t.say("The library from the tour is the one every tutorial uses: a role, a setting, outfits, and a few rules. A new install starts with an empty library, like this one.");
|
||
await t.waitVoice();
|
||
await t.say("So first, bring the tutorial library in. The file, WP Tutorials library.json, comes with the videos.");
|
||
await t.waitVoice();
|
||
});
|
||
|
||
await t.h.step("Import the file", async () => {
|
||
await t.go("/wp/import-export");
|
||
await t.say("Open Import and Export, then the Import tab.");
|
||
await t.click(page.locator('[data-test="io-tab-import"]'));
|
||
await t.beat(800);
|
||
await t.waitVoice();
|
||
await t.say("Choose WP Tutorials library.json.");
|
||
const chooser = page.waitForEvent("filechooser");
|
||
await t.click(page.locator('[data-test="import-file-btn"]'));
|
||
await (await chooser).setFiles(fixture);
|
||
await t.beat(1500);
|
||
await t.waitVoice();
|
||
await t.say("It lists what's inside. Select all, and continue.");
|
||
await t.click(page.locator('[data-test="import-preset-all"]'));
|
||
await t.beat(600);
|
||
await t.click(page.locator('[data-test="import-picker-continue"]'));
|
||
await t.beat(1500);
|
||
// With an empty library nothing clashes, so Continue imports straight away; a clash opens a
|
||
// dialog with its own Import button.
|
||
const commit = page.locator('[data-test="commit-btn"]');
|
||
if (await commit.isVisible().catch(() => false)) await t.click(commit);
|
||
// The message only stays a few seconds: box the whole card as it appears and say the line
|
||
// over it; the box leaves with the card.
|
||
const toast = page.locator('[data-test="toast"]').filter({ hasText: "Import committed" }).first();
|
||
const shown = await toast.waitFor({ state: "visible", timeout: 8000 }).then(() => true, () => false);
|
||
if (shown) await t.callout("done", toast, "Imported, with Undo", 4);
|
||
await t.say("Nothing clashes with an empty library, so it goes straight in. The message has Undo, in case you change your mind.");
|
||
await t.waitVoice();
|
||
await t.uncallout("done");
|
||
});
|
||
|
||
await t.h.step("The items", async () => {
|
||
await t.go("/wp/");
|
||
await t.say("The wildcards, rules and bundles are in your library now, and every tutorial can use them.");
|
||
await t.beat(1500);
|
||
await t.waitVoice();
|
||
const health = page.locator('[data-test="dashboard-health"]');
|
||
if (await health.isVisible().catch(() => false)) {
|
||
await t.callout("health", health, "One expected note", 6);
|
||
await t.say("Library health notes one problem: a LoRA rule reads a variable no list sets. That's expected. The LoRA video sets it from the canvas.");
|
||
await t.waitVoice();
|
||
await t.uncallout("health");
|
||
}
|
||
await t.say("The example workflows carry their lists too. Their first note has an Add to my library button that saves them, if you'd rather start there.");
|
||
await t.waitVoice();
|
||
});
|
||
done = true;
|
||
} finally {
|
||
const after = await t.h.library();
|
||
if (!done || after.modules.length < 10) await resetLibrary(t.h, fixture, { backupFirst: false });
|
||
}
|
||
|
||
await t.h.step("Outro", async () => {
|
||
await t.say("That's all the setup. Next: your first random prompt.");
|
||
});
|
||
},
|
||
);
|