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.
|
explain.
|
||||||
|
|
||||||
**Song Key** is one value, as large as the tile allows: the key of the item
|
**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
|
ProPresenter has live, with the song's name and the next key under it — both
|
||||||
next key under it — both switches, as is the colour. When the feed that
|
switches, as is the colour. When the feed that picks the item is gone the key
|
||||||
picks the item is gone the key stays up, faded, and the tile's dot says so
|
stays up, faded, and the tile's dot says so rather than pretending to be
|
||||||
rather than pretending to be current.
|
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
|
**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
|
as a band of its people, with their photo (fetched through ProdDash, never
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
import createTeams from "./teams-client.js";
|
import createTeams from "./teams-client.js";
|
||||||
import createKey from "./key-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>';
|
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) {
|
if (on('showKey') && item.keyName) {
|
||||||
const key = document.createElement('span');
|
const key = document.createElement('span');
|
||||||
key.className = 'pp-key';
|
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);
|
titleLine.appendChild(key);
|
||||||
}
|
}
|
||||||
if (on('showNowIndicator')) {
|
if (on('showNowIndicator')) {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
streamed state the Plan tile does, so it needs no connection of its own;
|
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. */
|
which item is current comes from the ProPresenter module, as ever. */
|
||||||
|
|
||||||
|
import { parseKey, prettyKey } from "./key-notation.js";
|
||||||
|
|
||||||
export default function createKey({ root, moduleApi }) {
|
export default function createKey({ root, moduleApi }) {
|
||||||
let state = null;
|
let state = null;
|
||||||
let stream = 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 (!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');
|
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'}`);
|
moduleApi.setStatus('ok', `${titleOf(item)}${item.keyName ? ` — ${prettyKey(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 || '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,23 +80,22 @@ export default function createKey({ root, moduleApi }) {
|
|||||||
*/
|
*/
|
||||||
function drawKey(text) {
|
function drawKey(text) {
|
||||||
keyEl.innerHTML = '';
|
keyEl.innerHTML = '';
|
||||||
const match = /^([A-G])([#b♯♭]?)(.*)$/.exec(String(text).trim());
|
const parts = parseKey(text);
|
||||||
if (!match) {
|
if (!parts) {
|
||||||
keyEl.textContent = text;
|
keyEl.textContent = text;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [, note, accidental, rest] = match;
|
keyEl.appendChild(document.createTextNode(parts.note));
|
||||||
keyEl.appendChild(document.createTextNode(note));
|
if (parts.accidental) {
|
||||||
if (accidental) {
|
const acc = document.createElement('span');
|
||||||
const sup = document.createElement('span');
|
acc.className = 'pk-accidental';
|
||||||
sup.className = 'pk-accidental';
|
acc.textContent = parts.accidental;
|
||||||
sup.textContent = accidental === '#' ? '♯' : accidental === 'b' ? '♭' : accidental;
|
keyEl.appendChild(acc);
|
||||||
keyEl.appendChild(sup);
|
|
||||||
}
|
}
|
||||||
if (rest) {
|
if (parts.quality) {
|
||||||
const tail = document.createElement('span');
|
const tail = document.createElement('span');
|
||||||
tail.className = 'pk-quality';
|
tail.className = 'pk-quality';
|
||||||
tail.textContent = rest;
|
tail.textContent = parts.quality;
|
||||||
keyEl.appendChild(tail);
|
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