Click a key.${editing() ? " Drag to move; drop on the tray to hide." : ""}
`;
- if (ready) { const i = sel && c.ready_page ? c.ready_page.keys.findIndex((x) => x.id === sel) : -1; panel = await readyKeyPanel(i, hold); }
+ if (ready) {
+ const i = sel && c.ready_page ? c.ready_page.keys.findIndex((x) => x.id === sel) : -1;
+ panel = await readyKeyPanel(i, hold);
+ if (i >= 0 && editing() && c.ready_page.keys[i].kind !== "ready") panel += await keyPanel(i, hold, "ready_page.keys");
+ }
else if (sel && !restart) { const i = c.tiles.findIndex((x) => x.id === sel); if (i >= 0) panel = await keyPanel(i, hold); }
else if (sel && restart && c.restart_page) { const i = c.restart_page.keys.findIndex((x) => x.id === sel); if (i >= 0) panel = restartKeyPanel(i, hold); }
const trayKeys = c.tiles.filter((t) => !t.pos && t.kind !== "master");
const tray = trayKeys.map((t) => ``).join(" ");
let note = "";
if (state.draft) { try { const v = await post("/api/validate", {}); if (v.plan && v.plan.pages.length) note = `
Layout changed: Apply re-imports the ${v.plan.pages.filter((p) => p === "status" || p === "restart" || p === "ready").map((p) => p[0].toUpperCase() + p.slice(1)).join(" and ")} page.
+ How do I add… (everything is done here; nothing needs a terminal)
Watch a new Companion connection. Edit (top right, the PIN) › Settings › Companion. Find it marked “not watched” › + Key. When the Status page is full it asks which group key to put it in (AUDIO, SHOW…): its problems show on that key, and holding the key reconnects it. Then the badge › Apply.
Add a connection to a key that is already there. Keys › click the key › + check… › “A Companion connection is good” › pick the connection from the list › fix: “reconnect its Companion connection” › Apply.
Watch that a device is on the network. Keys › the key › + check… › “An address answers ping” › type the address and the name the key should show (it says “NAME NO PING”).
Watch a program on another computer, and restart it. Keys › the key › + check… › “A command on another computer succeeds” › built as “A program is running on a Mac” › pick the computer and the program (on a Windows PC: Custom, e.g. tasklist | find /I \"OBS.exe\"). Then fix › a fix that starts it (make one first: next line).
Make a new fix (something a hold can do). Keys › Fixes › + Fix › pick how it is built (start a Windows program, restart a Mac program, restart a helper, reopen Ableton…) and fill in the fields; the command is written for you. Then choose it as a check's “fix”, and use “Run the fix” to try it.
Let a fix run by itself on Sundays. Settings › Auto-fix › tick it, and paint the hours on the calendar. A fix that closes a running program asks first.
Add a line to the weekly checklist. Keys › READY page › + READY key › “Tap to confirm” (a person taps it done; resets Monday), “a checklist line the page checks by itself”, or “something else” (then + check… like any key: a connection, a ping, a program). Drag it where you want it.
Change where the READY page looks. Settings › READY page: the Planning Center service type, the ProPresenter and Resolume addresses, the Tracks Mac and its projects folder, which locations get tracks, the lineup TV, the bundle folder, and when gaps turn red.
A new computer. Computers › + Computer (its log-in address), then Computers › Setup › that computer › copy its set-up script and run it there once.
Made a mistake?. Nothing is live until Apply. After Apply: History › Undo, then Apply again.
Try it (ten minutes with Editing on; nothing is live until APPLY)
Keys › HD2: untick "may have no disk"; the badge counts it; Apply reads it back. Throw away.
Keys: drag LED onto the tray and back. + Key "HD7", add a check (Is a recorder OK?), delete it.
@@ -782,10 +820,12 @@
}
// ---- Settings: one page, every section ------------------------------------------
- const HAND_MADE = ["tiles", "fixes", "hosts", "gear", "auto_fix.windows", "service_windows", "site.live_services", "auto_fix.fixes", "atem.reference", "spotify.playlist", "spotify.name", "restart_page.keys", "site.editing_code"];
+ const HAND_MADE = ["tiles", "fixes", "hosts", "gear", "auto_fix.windows", "service_windows", "site.live_services", "auto_fix.fixes", "atem.reference", "spotify.playlist", "spotify.name", "restart_page.keys", "site.editing_code",
+ "ready_page.keys", "ready_page.back_pos", "ready.prop_lists"];
const SECTIONS = [
["playlist", "Playlist", "The Spotify playlist the SPOTIFY key and the play fix use"],
["autofix", "Auto-fix", "When the panel may fix things by itself, and which fixes"],
+ ["ready", "READY page", "Where the weekly checklist looks: Planning Center, ProPresenter, the Tracks Mac, the lineup TV, Resolume, the bundles"],
["wait", "Changes wait", "When changes to the panel must wait (a recording deck always counts)"],
["hyperdecks", "HyperDecks", "Which decks are in use"],
["midi", "MIDI links", "Network MIDI sessions and the peers that must stay in them"],
@@ -809,6 +849,7 @@
let body = "";
if (which === "playlist") body = spotifyHtml(c);
else if (which === "autofix") body = autofixHtml(c);
+ else if (which === "ready") body = readySettingsHtml(c);
else if (which === "wait") body = `
When gaps turn red${hint("Before this, a READY key that is not done is amber; inside it, still missing is red and blinks")}
${calendarHtml("ready.due", (c.ready || {}).due)}
`
+ + fieldsHtml("Ready")
+ + `
lists the slides must match${hint('PCO items whose description lists things: {"match": a word in the item title, "kind": "count" (one slide per - line) or "names" (every name has a slide), "short": what the key calls it}')}
+
The keys themselves (add, move, delete, what each checks) are on Keys › READY page.
`;
+ }
function fieldsHtml(screen, includeAdvanced) {
const own = Object.entries(state.schema.fields).filter(([p, f]) => f.screen === screen && !p.includes("[]") && !p.includes("*") && !HAND_MADE.includes(p) && !HAND_MADE.some((h) => h.startsWith(p + ".")) && !Object.keys(state.schema.fields).some((k) => k.startsWith(p + ".") || k.startsWith(p + "[]")));
const plain = own.filter(([, f]) => !f.advanced), adv = own.filter(([, f]) => f.advanced);
@@ -1011,6 +1060,7 @@
function wireSettings(which) {
const q = (s) => main.querySelector(s);
wireCalendars();
+ for (const ta of main.querySelectorAll("[data-jsonpath]")) ta.onchange = () => { try { setPath(ta.dataset.jsonpath, JSON.parse(ta.value)); } catch (e) { alertBox("That is not valid: " + e.message); } };
for (const b of main.querySelectorAll("[data-job]")) b.onclick = () => job(b.dataset.job);
const ok = q("#okeys"); if (ok) ok.onclick = async () => { ok.disabled = true; ok.textContent = "Testing…"; try { const r = await post("/api/outlets/keys", { access_id: q("#oid").value, access_secret: q("#osecret").value, region: q("#oregion").value }); alertBox(r.ok ? `Works: ${r.devices} devices on the account.` : r.error); route(); } catch (e) { alertBox(e.message); route(); } };
const oa = q("#oadd"); if (oa) oa.onclick = outletDialog;
@@ -1214,7 +1264,7 @@
detail = `
${esc(f.short || id)} ${esc(f.label || "")}
${inputs}${keys.length ? `
Used by ${esc(keys.join(", "))}.
` : ""}everything stored
${Object.entries(f).map(([k, v]) => `
${esc(k)}
${esc(typeof v === "object" ? JSON.stringify(v) : v)}