PCO Plan 1.10.0: follow several service types in priority order, and read any of them from any dashboard
An account runs more than one service type — a Sunday morning, a Wednesday night, a youth service, a second campus — and until now that meant one ProdDash per type. The module now follows as many as are configured. Admin picks them in priority order. Shell 1.12.0 gives `multiselect` an `orderable: true` that stops being a ticklist: the picks sit in a numbered list with a nudge either way and a remove, over a dropdown holding what is left. Forty switches to read past is not a picker, and a ticklist cannot carry an order anyway. The order is the setting — when two types have a plan at the same date and time, the one higher up wins. The server gathers candidate plans from every ticked type and tiers them: today's plan first, then the next upcoming, then the most recent past, with the configured order breaking a tie. A type that fails is skipped rather than failing the read, unless it is an auth or rate-limit answer, which belongs to the whole connection. The types not being followed are read on a slower lane (45 s) into `state.others`, so they are there to look at without a second connection. Every tile's title bar carries a service-type control. Picking one moves every PCO Plan tile on that browser's dashboard — plan, teams and song key together — onto its plan, over a small page bus in `viewed-type.js`; the choice is per browser and survives a reload. The control hides itself when only one type is configured. Live machinery stays with the type being followed: another type's plan shows its running order with no now marker, no AbleSet and no service clock — it shows that plan's own scheduled time instead, since borrowing the running clock of a different service is exactly the misreading the amber top-bar stripe exists to prevent. Old configs carry `serviceTypeId`; it is read once into the new array and saved, and plan pins still resolve bare, so nothing has to be re-entered. `tools/pco-mock.js` takes `--types N` to pad the list out, for trying the picker against an account with dozens of them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f9cd292693
commit
7bb1131378
@@ -5,11 +5,14 @@
|
||||
which item is current comes from the ProPresenter module, as ever. */
|
||||
|
||||
import { parseKey, prettyKey } from "./key-notation.js";
|
||||
import { viewOf, onViewedType, addServiceTypeSwitch } from "./viewed-type.js";
|
||||
|
||||
export default function createKey({ root, moduleApi }) {
|
||||
let state = null;
|
||||
let stream = null;
|
||||
let feedOffline = false;
|
||||
let view = viewOf(null);
|
||||
let offViewed = null;
|
||||
|
||||
root.innerHTML = `
|
||||
<div class="pk-wrap">
|
||||
@@ -23,17 +26,21 @@ export default function createKey({ root, moduleApi }) {
|
||||
const nameEl = root.querySelector('.pk-name');
|
||||
const nextEl = root.querySelector('.pk-next');
|
||||
|
||||
const switcher = addServiceTypeSwitch({ moduleApi, getState: () => state });
|
||||
|
||||
const cfg = () => moduleApi.instanceSettings || {};
|
||||
const on = (key) => cfg()[key] !== false; // unset = shown
|
||||
|
||||
/* ── the item on screen, and the one after it ───────────────────── */
|
||||
|
||||
function items() {
|
||||
return Array.isArray(state?.items) ? state.items : [];
|
||||
return Array.isArray(view.items) ? view.items : [];
|
||||
}
|
||||
|
||||
function currentItem() {
|
||||
const id = state?.live?.following ? state.live.currentItemId : '';
|
||||
// Only the followed service type has a live item; on another one there
|
||||
// is nothing current, so the tile says so rather than guessing.
|
||||
const id = view.live && state?.live?.following ? state.live.currentItemId : '';
|
||||
return id ? items().find((it) => it.id === id) || null : null;
|
||||
}
|
||||
|
||||
@@ -63,6 +70,7 @@ export default function createKey({ root, moduleApi }) {
|
||||
return moduleApi.setStatus(state.lastFetched ? 'error' : 'connecting', state.lastError || 'Reading the plan…');
|
||||
}
|
||||
const item = currentItem();
|
||||
if (!view.live) return moduleApi.setStatus('ok', `${view.name || 'Another service type'} — not the one live`);
|
||||
if (!state.live?.following) return moduleApi.setStatus('ok', 'Not following ProPresenter — no item is current');
|
||||
// The key on screen stays — the band still needs it — but a tile must
|
||||
// never look confidently live while the feed that chose it is gone.
|
||||
@@ -101,7 +109,10 @@ export default function createKey({ root, moduleApi }) {
|
||||
}
|
||||
|
||||
function render() {
|
||||
view = viewOf(state);
|
||||
switcher.sync();
|
||||
wrap.classList.toggle('is-offline', feedOffline);
|
||||
wrap.classList.toggle('is-other-type', Boolean(state) && !view.live);
|
||||
const live = state?.live;
|
||||
wrap.classList.toggle('is-stale', Boolean(live?.following && (!live.available || !live.ppReachable)));
|
||||
const color = String(cfg().keyColor || '').trim();
|
||||
@@ -120,7 +131,9 @@ export default function createKey({ root, moduleApi }) {
|
||||
}
|
||||
if (!item) {
|
||||
drawKey('—');
|
||||
nameEl.textContent = state.live?.following ? 'Nothing live' : 'Not following ProPresenter';
|
||||
nameEl.textContent = !view.live
|
||||
? (view.name || 'Another service type')
|
||||
: (state.live?.following ? 'Nothing live' : 'Not following ProPresenter');
|
||||
nextEl.textContent = '';
|
||||
return;
|
||||
}
|
||||
@@ -167,9 +180,12 @@ export default function createKey({ root, moduleApi }) {
|
||||
.then((body) => { if (body && !state) accept(body); })
|
||||
.catch(() => { /* the stream will cover it */ });
|
||||
connect();
|
||||
offViewed = onViewedType(render);
|
||||
render();
|
||||
},
|
||||
stop() {
|
||||
offViewed?.();
|
||||
offViewed = null;
|
||||
stream?.close();
|
||||
stream = null;
|
||||
root.innerHTML = '';
|
||||
|
||||
Reference in New Issue
Block a user