Band Lineup 1.11.0: into the catalog, and to the guide's rules
The module arrived in the previous commit exactly as it runs in North Attleboro. This brings it to the interface rules every module follows and puts it where the admin page can offer it. The three hide-toggles — names, mic labels, parallax — sat in a folded "Show / hide" group. Rule 8 puts four or fewer at the top of the gear with no group at all, so they are ungrouped and first; the Birthday group keeps its switch. The test-mode select loses its help line (rule 5). Everything else in the gear was already within the rules, and every title-bar control was already an icon with a tooltip. The Planning Center mock moves out of the module folder to tools/, beside the other upstream mocks, so an install does not download a development tool with the module; README and CLAUDE.md say how to run it. The version goes to 1.11.0 so the copy already installed on NA is offered the update from the repo, and the module joins the README's list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
684f06abed
commit
5fb90190ae
@@ -42,7 +42,7 @@ in the ProdDash data directory, never in the repo.
|
||||
## Testing without Planning Center
|
||||
|
||||
```bash
|
||||
node modules/band-lineup/tools/pco-band-mock.js 24710 # mock PCO
|
||||
node tools/pco-band-mock.js 24710 # mock PCO
|
||||
BAND_LINEUP_API_BASE=http://127.0.0.1:24710 node server.js # point the module at it
|
||||
node modules/band-lineup/matching.test.js # slot-matching unit tests
|
||||
```
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"id": "band-lineup",
|
||||
"name": "Band Lineup",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"proddash": ">=1.4.0",
|
||||
"description": "Stage lineup photos with mic/pack labels \u2014 a drag-in photo library plus optional Planning Center auto-fill",
|
||||
"description": "Stage lineup photos with mic/pack labels — a drag-in photo library plus optional Planning Center auto-fill",
|
||||
"client": "client.js",
|
||||
"server": "server.js",
|
||||
"style": "style.css",
|
||||
@@ -24,7 +24,7 @@
|
||||
"help": "One mic/pack label per line, in the order they sit on stage. Scheduled people are matched into these by their Planning Center position (e.g. a line '[VOX 2] MIX 2' takes whoever is scheduled on Vox 2). Blank lines are empty spots."
|
||||
},
|
||||
"Photo library": {
|
||||
"help": "Paste a Google Drive folder share link (set the folder to 'Anyone with the link \u2192 Viewer'). Every image is imported (file name \u2192 person's name) and kept in sync \u2014 add or remove photos in Drive and the board follows. A local folder path also works. If Drive can't be read, the board falls back to Planning Center photos."
|
||||
"help": "Paste a Google Drive folder share link (set the folder to 'Anyone with the link → Viewer'). Every image is imported (file name → person's name) and kept in sync — add or remove photos in Drive and the board follows. A local folder path also works. If Drive can't be read, the board falls back to Planning Center photos."
|
||||
}
|
||||
},
|
||||
"configSchema": {
|
||||
@@ -59,7 +59,7 @@
|
||||
"options": [
|
||||
{
|
||||
"value": "",
|
||||
"label": "Automatic \u2014 today's plan, else the next upcoming"
|
||||
"label": "Automatic — today's plan, else the next upcoming"
|
||||
}
|
||||
],
|
||||
"optionsRoute": "/plans",
|
||||
@@ -82,26 +82,37 @@
|
||||
"label": "Columns",
|
||||
"default": 7,
|
||||
"group": "Stage",
|
||||
"help": "Default slots per row (each screen can still adjust with the \u2212 / + buttons in its title bar)."
|
||||
"help": "Default slots per row (each screen can still adjust with the − / + buttons in its title bar)."
|
||||
},
|
||||
"photoFolder": {
|
||||
"type": "string",
|
||||
"label": "Google Drive folder link",
|
||||
"default": "",
|
||||
"group": "Photo library",
|
||||
"help": "e.g. https://drive.google.com/drive/folders/\u2026 (a local folder path also works). Leave blank to use Planning Center photos only."
|
||||
"help": "e.g. https://drive.google.com/drive/folders/… (a local folder path also works). Leave blank to use Planning Center photos only."
|
||||
}
|
||||
},
|
||||
"instanceGroups": {
|
||||
"Birthday": {
|
||||
"toggle": "birthdays"
|
||||
},
|
||||
"Show / hide": {
|
||||
"collapsed": true,
|
||||
"columns": 2
|
||||
}
|
||||
},
|
||||
"instanceSchema": {
|
||||
"showNames": {
|
||||
"type": "boolean",
|
||||
"label": "Names",
|
||||
"default": true
|
||||
},
|
||||
"showLabels": {
|
||||
"type": "boolean",
|
||||
"label": "Mic labels",
|
||||
"default": true
|
||||
},
|
||||
"parallax": {
|
||||
"type": "boolean",
|
||||
"label": "Parallax motion",
|
||||
"default": true
|
||||
},
|
||||
"birthdays": {
|
||||
"type": "switch",
|
||||
"label": "Birthday confetti",
|
||||
@@ -137,26 +148,7 @@
|
||||
"label": "Off"
|
||||
}
|
||||
],
|
||||
"optionsRoute": "/people",
|
||||
"help": "Force one person's tile to celebrate for testing. Clears when you set it back to Off."
|
||||
},
|
||||
"parallax": {
|
||||
"type": "boolean",
|
||||
"label": "Parallax motion",
|
||||
"default": true,
|
||||
"group": "Show / hide"
|
||||
},
|
||||
"showNames": {
|
||||
"type": "boolean",
|
||||
"label": "Names",
|
||||
"default": true,
|
||||
"group": "Show / hide"
|
||||
},
|
||||
"showLabels": {
|
||||
"type": "boolean",
|
||||
"label": "Mic labels",
|
||||
"default": true,
|
||||
"group": "Show / hide"
|
||||
"optionsRoute": "/people"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// Minimal Planning Center mock for the Band Lineup module: service types, one
|
||||
// plan today, its team_members (with person+team includes), People birthdates,
|
||||
// and avatar bytes. Enough to exercise the whole board build.
|
||||
const http = require('http');
|
||||
const PORT = Number(process.argv[2] || 24710);
|
||||
const JPEG = Buffer.from('/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAAAP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AfwD/2Q==', 'base64');
|
||||
const TODAY = new Date();
|
||||
const iso = (d) => d.toISOString();
|
||||
// One person's birthday lands in this Wed–Tue week (today is a Wed in the test).
|
||||
const inWeek = new Date(TODAY.getFullYear(), TODAY.getMonth(), TODAY.getDate() + 2);
|
||||
const bdayInWeek = `1994-${String(inWeek.getMonth()+1).padStart(2,'0')}-${String(inWeek.getDate()).padStart(2,'0')}`;
|
||||
|
||||
const PEOPLE = {
|
||||
p1: { name: 'Lemuel Maldonado', pos: 'Vox 1', team: 'Vocals', bd: '1990-03-04' },
|
||||
p2: { name: 'Wes Carter', pos: 'Vox 2', team: 'Vocals', bd: null },
|
||||
p3: { name: 'Jessica Proulx', pos: 'Vox 3', team: 'Vocals', bd: '1992-11-01' },
|
||||
p4: { name: 'Jeremy Cruz', pos: 'Guitar / Lead', team: 'Band', bd: null },
|
||||
p5: { name: 'Alex Gaitan', pos: 'Drums', team: 'Band', bd: bdayInWeek }, // celebrating
|
||||
p6: { name: 'Rosaliz Gomez', pos: 'Piano', team: 'Band', bd: null },
|
||||
p7: { name: 'George Acosta', pos: 'MC', team: 'Production', bd: null },
|
||||
p8: { name: 'Not Shown', pos: 'Camera 2', team: 'Video', bd: null }, // team not allowed → filtered out
|
||||
};
|
||||
const TEAMS = { Vocals: 't1', Band: 't2', Production: 't3', Video: 't4' };
|
||||
|
||||
function send(res, obj) { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(obj)); }
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
const u = new URL(req.url, 'http://x'); const p = u.pathname;
|
||||
if (p.startsWith('/avatar/')) { res.writeHead(200, { 'Content-Type': 'image/jpeg' }); return res.end(JPEG); }
|
||||
if (p === '/services/v2/folders') return send(res, { data: [] });
|
||||
if (p === '/services/v2/service_types') return send(res, { data: [{ id: '1', type: 'ServiceType', attributes: { name: 'Sunday Service' }, relationships: { parent: { data: null } } }] });
|
||||
if (/^\/services\/v2\/service_types\/1\/plans$/.test(p)) {
|
||||
if (u.searchParams.get('filter') === 'past') return send(res, { data: [] });
|
||||
return send(res, { data: [{ id: '100', type: 'Plan', attributes: { sort_date: iso(TODAY), title: 'Taking Thoughts Captive - Part 4', dates: 'September 20, 2026' } }] });
|
||||
}
|
||||
if (/^\/services\/v2\/service_types\/1\/plans\/100\/team_members$/.test(p)) {
|
||||
const data = []; const included = [];
|
||||
for (const [id, v] of Object.entries(PEOPLE)) {
|
||||
data.push({ id: `tm-${id}`, type: 'PlanPerson', attributes: { status: 'C', team_position_name: v.pos }, relationships: { person: { data: { type: 'Person', id } }, team: { data: { type: 'Team', id: TEAMS[v.team] } } } });
|
||||
included.push({ id, type: 'Person', attributes: { full_name: v.name, photo_url: `http://127.0.0.1:${PORT}/avatar/${id}` } });
|
||||
}
|
||||
for (const [name, id] of Object.entries(TEAMS)) included.push({ id, type: 'Team', attributes: { name } });
|
||||
return send(res, { data, included, links: {} });
|
||||
}
|
||||
const person = p.match(/^\/people\/v2\/people\/([^/]+)$/);
|
||||
if (person) { const v = PEOPLE[person[1]]; return send(res, { data: { id: person[1], type: 'Person', attributes: { birthdate: v ? v.bd : null } } }); }
|
||||
res.writeHead(404); res.end('{}');
|
||||
});
|
||||
server.listen(PORT, '127.0.0.1', () => console.log(`[pco-band-mock] on http://127.0.0.1:${PORT}`));
|
||||
Reference in New Issue
Block a user