PCO Plan 1.9.0: a Song Key tile, keys as notes, and AbleSet-shaped section chips #32
@@ -359,10 +359,14 @@ below. Nothing is installed without someone clicking.
|
||||
explain.
|
||||
|
||||
**Song Key** is one value, as large as the tile allows: the key of the item
|
||||
ProPresenter has live, set as a note (B♭, F♯m), with the song's name and the
|
||||
next key under it — both switches, as is the colour. When the feed that
|
||||
picks the item is gone the key stays up, faded, and the tile's dot says so
|
||||
rather than pretending to be current.
|
||||
ProPresenter has live, with the song's name and the next key under it — both
|
||||
switches, as is the colour. When the feed that picks the item is gone the key
|
||||
stays up, faded, and the tile's dot says so rather than pretending to be
|
||||
current.
|
||||
|
||||
Keys are written as notes wherever the module shows one: Planning Center's
|
||||
`Bb` and `F#m` read as B♭ and F♯m, on the Plan tile's item badge and in the
|
||||
Song Key tile alike.
|
||||
|
||||
**Teams** shows the same plan's team and position assignments — each team
|
||||
as a band of its people, with their photo (fetched through ProdDash, never
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
import createTeams from "./teams-client.js";
|
||||
import createKey from "./key-client.js";
|
||||
import { prettyKey } from "./key-notation.js";
|
||||
|
||||
const REFRESH_SVG = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg>';
|
||||
|
||||
@@ -369,7 +370,8 @@ function createPlan({ root, moduleApi }) {
|
||||
if (on('showKey') && item.keyName) {
|
||||
const key = document.createElement('span');
|
||||
key.className = 'pp-key';
|
||||
key.textContent = item.keyName;
|
||||
// B♭, F♯m — the same notation the Song Key tile uses.
|
||||
key.textContent = prettyKey(item.keyName);
|
||||
titleLine.appendChild(key);
|
||||
}
|
||||
if (on('showNowIndicator')) {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
streamed state the Plan tile does, so it needs no connection of its own;
|
||||
which item is current comes from the ProPresenter module, as ever. */
|
||||
|
||||
import { parseKey, prettyKey } from "./key-notation.js";
|
||||
|
||||
export default function createKey({ root, moduleApi }) {
|
||||
let state = null;
|
||||
let stream = null;
|
||||
@@ -69,13 +71,7 @@ export default function createKey({ root, moduleApi }) {
|
||||
}
|
||||
if (!state.live.ppReachable) return moduleApi.setStatus('error', 'ProPresenter unreachable — showing the last item');
|
||||
if (!item) return moduleApi.setStatus('ok', 'Waiting for an item to go live');
|
||||
moduleApi.setStatus('ok', `${titleOf(item)}${item.keyName ? ` — ${item.keyName}` : ' — no key in the plan'}`);
|
||||
}
|
||||
|
||||
/** "Bb" → "B♭", "F#m" → "F♯m" — for the plain-text line under the key. */
|
||||
function prettyKey(text) {
|
||||
const match = /^([A-G])([#b])(.*)$/.exec(String(text || '').trim());
|
||||
return match ? `${match[1]}${match[2] === '#' ? '♯' : '♭'}${match[3]}` : String(text || '');
|
||||
moduleApi.setStatus('ok', `${titleOf(item)}${item.keyName ? ` — ${prettyKey(item.keyName)}` : ' — no key in the plan'}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,23 +80,22 @@ export default function createKey({ root, moduleApi }) {
|
||||
*/
|
||||
function drawKey(text) {
|
||||
keyEl.innerHTML = '';
|
||||
const match = /^([A-G])([#b♯♭]?)(.*)$/.exec(String(text).trim());
|
||||
if (!match) {
|
||||
const parts = parseKey(text);
|
||||
if (!parts) {
|
||||
keyEl.textContent = text;
|
||||
return;
|
||||
}
|
||||
const [, note, accidental, rest] = match;
|
||||
keyEl.appendChild(document.createTextNode(note));
|
||||
if (accidental) {
|
||||
const sup = document.createElement('span');
|
||||
sup.className = 'pk-accidental';
|
||||
sup.textContent = accidental === '#' ? '♯' : accidental === 'b' ? '♭' : accidental;
|
||||
keyEl.appendChild(sup);
|
||||
keyEl.appendChild(document.createTextNode(parts.note));
|
||||
if (parts.accidental) {
|
||||
const acc = document.createElement('span');
|
||||
acc.className = 'pk-accidental';
|
||||
acc.textContent = parts.accidental;
|
||||
keyEl.appendChild(acc);
|
||||
}
|
||||
if (rest) {
|
||||
if (parts.quality) {
|
||||
const tail = document.createElement('span');
|
||||
tail.className = 'pk-quality';
|
||||
tail.textContent = rest;
|
||||
tail.textContent = parts.quality;
|
||||
keyEl.appendChild(tail);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/* PCO Plan — how a song key is written.
|
||||
|
||||
Planning Center stores keys as typed: "Bb", "F#m", "Ab". Every tile in this
|
||||
module shows them as notes instead — B♭, F♯m, A♭ — so the plan, the badge
|
||||
on an item and the Song Key tile all agree. Shared rather than repeated
|
||||
because the day one of them changes, they all should. */
|
||||
|
||||
/** "F#m" → { note: 'F', accidental: '♯', quality: 'm' }; anything odd → null. */
|
||||
export function parseKey(raw) {
|
||||
const match = /^([A-G])\s*([#b♯♭]?)\s*(.*)$/.exec(String(raw || '').trim());
|
||||
if (!match) return null;
|
||||
const [, note, accidental, quality] = match;
|
||||
return {
|
||||
note,
|
||||
accidental: accidental === '#' ? '♯' : accidental === 'b' ? '♭' : accidental,
|
||||
quality: quality.trim(),
|
||||
};
|
||||
}
|
||||
|
||||
/** The same thing as one string, for a badge or a caption. */
|
||||
export function prettyKey(raw) {
|
||||
const parts = parseKey(raw);
|
||||
if (!parts) return String(raw || '').trim();
|
||||
return `${parts.note}${parts.accidental}${parts.quality}`;
|
||||
}
|
||||
Reference in New Issue
Block a user