diff --git a/README.md b/README.md index 2d9f047..0efedf6 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/modules/pco-plan/client.js b/modules/pco-plan/client.js index ca2f29d..685bc75 100644 --- a/modules/pco-plan/client.js +++ b/modules/pco-plan/client.js @@ -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 = ''; @@ -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')) { diff --git a/modules/pco-plan/key-client.js b/modules/pco-plan/key-client.js index eda8e5b..1297227 100644 --- a/modules/pco-plan/key-client.js +++ b/modules/pco-plan/key-client.js @@ -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); } } diff --git a/modules/pco-plan/key-notation.js b/modules/pco-plan/key-notation.js new file mode 100644 index 0000000..f5b7dba --- /dev/null +++ b/modules/pco-plan/key-notation.js @@ -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}`; +}