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>
660 lines
16 KiB
JSON
660 lines
16 KiB
JSON
{
|
|
"id": "pco-plan",
|
|
"name": "PCO Plan",
|
|
"version": "1.10.0",
|
|
"proddash": ">=1.12.0",
|
|
"description": "Order of service, team assignments and the song key from Planning Center Services",
|
|
"provides": [
|
|
"timers"
|
|
],
|
|
"client": "client.js",
|
|
"server": "server.js",
|
|
"style": "style.css",
|
|
"minSize": {
|
|
"w": 3,
|
|
"h": 3
|
|
},
|
|
"defaultSize": {
|
|
"w": 5,
|
|
"h": 6
|
|
},
|
|
"configGroups": {
|
|
"Planning Center credentials": {
|
|
"collapsed": "whenSet",
|
|
"help": "Create a Personal Access Token at api.planningcenteronline.com and paste its Client ID + Secret here, or paste an OAuth 2.0 access token instead. Saved secrets never reach browsers."
|
|
},
|
|
"Service timing": {
|
|
"help": "Each service in the plan takes its turn owning the service clock: the next one takes over Pre-roll minutes before its scheduled start, and the elapsed clock, item timing and countdown start afresh for it. A service starts at its scheduled time — or, with document triggers on, the moment the chosen Start document goes live in ProPresenter (falling back to the schedule after the start failsafe) — and ends when the End document goes live, or when the next service takes over. The document lists hold what ProPresenter has shown or listed since ProdDash started plus what is saved here; names are matched loosely, so a renamed \"Post-Service Loop\" still counts."
|
|
}
|
|
},
|
|
"configSchema": {
|
|
"appId": {
|
|
"type": "string",
|
|
"label": "Client ID",
|
|
"default": "",
|
|
"group": "Planning Center credentials"
|
|
},
|
|
"secret": {
|
|
"type": "password",
|
|
"label": "Secret",
|
|
"default": "",
|
|
"group": "Planning Center credentials"
|
|
},
|
|
"accessToken": {
|
|
"type": "password",
|
|
"label": "OAuth 2.0 access token (instead of Client ID + Secret)",
|
|
"default": "",
|
|
"group": "Planning Center credentials"
|
|
},
|
|
"serviceTypes": {
|
|
"type": "multiselect",
|
|
"label": "Service types",
|
|
"orderable": true,
|
|
"optionsRoute": "/service-types",
|
|
"help": "Add every service type whose plans this ProdDash should follow. Order is priority: when two of them have a plan at the same date and time, the one higher up wins.",
|
|
"addLabel": "Add a service type…"
|
|
},
|
|
"planId": {
|
|
"type": "select",
|
|
"label": "Plan",
|
|
"default": "",
|
|
"options": [
|
|
{
|
|
"value": "",
|
|
"label": "Automatic — today's plan, else the next upcoming"
|
|
}
|
|
],
|
|
"optionsRoute": "/plans",
|
|
"optionsDependsOn": "serviceTypes",
|
|
"help": "Leave on Automatic for services. Pin a specific plan for rehearsals or to review a past service."
|
|
},
|
|
"pollSeconds": {
|
|
"type": "number",
|
|
"label": "Refresh Interval (s)",
|
|
"default": 30
|
|
},
|
|
"followProPresenter": {
|
|
"type": "switch",
|
|
"label": "Follow ProPresenter Module",
|
|
"default": true
|
|
},
|
|
"preRollMinutes": {
|
|
"type": "number",
|
|
"label": "Pre-roll (min)",
|
|
"default": 30,
|
|
"group": "Service timing",
|
|
"help": "How long before a service's scheduled start the clock, item timing and countdown switch to it."
|
|
},
|
|
"useDocumentTriggers": {
|
|
"type": "switch",
|
|
"label": "ProPresenter document triggers",
|
|
"default": false,
|
|
"group": "Service timing"
|
|
},
|
|
"serviceStartTrigger": {
|
|
"type": "select",
|
|
"label": "Service start",
|
|
"default": "schedule",
|
|
"group": "Service timing",
|
|
"showWhen": "useDocumentTriggers",
|
|
"options": [
|
|
{
|
|
"value": "schedule",
|
|
"label": "Scheduled service time"
|
|
},
|
|
{
|
|
"value": "document",
|
|
"label": "ProPresenter document"
|
|
}
|
|
]
|
|
},
|
|
"serviceStartDocument": {
|
|
"type": "select",
|
|
"label": "Start document",
|
|
"default": "",
|
|
"group": "Service timing",
|
|
"showWhen": "useDocumentTriggers",
|
|
"optionsRoute": "/documents",
|
|
"help": "The document that goes live as the service begins."
|
|
},
|
|
"startGraceMinutes": {
|
|
"type": "number",
|
|
"label": "Start failsafe (min)",
|
|
"default": 10,
|
|
"group": "Service timing",
|
|
"showWhen": "useDocumentTriggers",
|
|
"help": "Without the start document this long after the scheduled start, the service counts from the scheduled start anyway."
|
|
},
|
|
"serviceEndDocument": {
|
|
"type": "select",
|
|
"label": "End document",
|
|
"default": "",
|
|
"group": "Service timing",
|
|
"showWhen": "useDocumentTriggers",
|
|
"optionsRoute": "/documents",
|
|
"help": "The document that goes live as the service ends; it freezes the service clock."
|
|
}
|
|
},
|
|
"instanceGroups": {
|
|
"Top bar": {
|
|
"collapsed": false
|
|
},
|
|
"Colors": {
|
|
"collapsed": false,
|
|
"columns": 2
|
|
},
|
|
"Item notes": {
|
|
"toggle": "showNotes"
|
|
},
|
|
"Show": {
|
|
"collapsed": true,
|
|
"columns": 2
|
|
}
|
|
},
|
|
"instanceSchema": {
|
|
"autoScroll": {
|
|
"type": "boolean",
|
|
"label": "Auto-scroll",
|
|
"default": true
|
|
},
|
|
"headerTemplate": {
|
|
"type": "template",
|
|
"label": "Top bar text",
|
|
"default": "{series} • {part} | {serviceType} • {date}",
|
|
"group": "Top bar",
|
|
"variables": [
|
|
{
|
|
"value": "{series}",
|
|
"label": "Series"
|
|
},
|
|
{
|
|
"value": "{part}",
|
|
"label": "Part / plan title"
|
|
},
|
|
{
|
|
"value": "{serviceType}",
|
|
"label": "Service type"
|
|
},
|
|
{
|
|
"value": "{date}",
|
|
"label": "Date"
|
|
},
|
|
{
|
|
"value": "{shortDate}",
|
|
"label": "Short date"
|
|
},
|
|
{
|
|
"value": "{time}",
|
|
"label": "Service time"
|
|
},
|
|
{
|
|
"value": "{length}",
|
|
"label": "Planned length"
|
|
},
|
|
{
|
|
"value": "{items}",
|
|
"label": "Item count"
|
|
}
|
|
],
|
|
"help": "Use • between items and | to start a second line."
|
|
},
|
|
"keyColor": {
|
|
"type": "color",
|
|
"label": "Song key",
|
|
"default": "#4ea1ff",
|
|
"group": "Colors"
|
|
},
|
|
"headerColor": {
|
|
"type": "color",
|
|
"label": "Section headers",
|
|
"default": "#2ee59a",
|
|
"group": "Colors"
|
|
},
|
|
"currentColor": {
|
|
"type": "color",
|
|
"label": "Current item",
|
|
"default": "#2ee59a",
|
|
"group": "Colors"
|
|
},
|
|
"headerGradient": {
|
|
"type": "boolean",
|
|
"label": "Header fade",
|
|
"default": true,
|
|
"group": "Colors"
|
|
},
|
|
"tintByType": {
|
|
"type": "boolean",
|
|
"label": "Tint by type",
|
|
"default": false,
|
|
"group": "Colors"
|
|
},
|
|
"showNotes": {
|
|
"type": "switch",
|
|
"label": "Item notes",
|
|
"default": true,
|
|
"group": "Item notes"
|
|
},
|
|
"noteCategories": {
|
|
"type": "multiselect",
|
|
"label": "Categories",
|
|
"group": "Item notes",
|
|
"optionsRoute": "/note-categories",
|
|
"columns": 3,
|
|
"allByDefault": true
|
|
},
|
|
"showTopBar": {
|
|
"type": "boolean",
|
|
"label": "Top bar",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showSeries": {
|
|
"type": "boolean",
|
|
"label": "Series name",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showPart": {
|
|
"type": "boolean",
|
|
"label": "Part / title",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showServiceType": {
|
|
"type": "boolean",
|
|
"label": "Service type",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showDate": {
|
|
"type": "boolean",
|
|
"label": "Plan date",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showServiceTime": {
|
|
"type": "boolean",
|
|
"label": "Service time",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showTotalLength": {
|
|
"type": "boolean",
|
|
"label": "Total length",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showRunningClock": {
|
|
"type": "boolean",
|
|
"label": "Running clock",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showCountdown": {
|
|
"type": "boolean",
|
|
"label": "Countdown",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showHeaders": {
|
|
"type": "boolean",
|
|
"label": "Section headers",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showItemNumbers": {
|
|
"type": "boolean",
|
|
"label": "Item numbers",
|
|
"default": false,
|
|
"group": "Show"
|
|
},
|
|
"showStartTimes": {
|
|
"type": "boolean",
|
|
"label": "Start times",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showLength": {
|
|
"type": "boolean",
|
|
"label": "Item length",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showItemType": {
|
|
"type": "boolean",
|
|
"label": "Type badge",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showKey": {
|
|
"type": "boolean",
|
|
"label": "Song key",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showArrangement": {
|
|
"type": "boolean",
|
|
"label": "Arrangement",
|
|
"default": false,
|
|
"group": "Show"
|
|
},
|
|
"showSongAuthor": {
|
|
"type": "boolean",
|
|
"label": "Author / CCLI",
|
|
"default": false,
|
|
"group": "Show"
|
|
},
|
|
"showDescription": {
|
|
"type": "boolean",
|
|
"label": "Item description",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showPreService": {
|
|
"type": "boolean",
|
|
"label": "Pre-service",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showPostService": {
|
|
"type": "boolean",
|
|
"label": "Post-service",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showNowIndicator": {
|
|
"type": "boolean",
|
|
"label": "Now indicator",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showItemRuntime": {
|
|
"type": "boolean",
|
|
"label": "Item runtime",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"dimCompleted": {
|
|
"type": "boolean",
|
|
"label": "Dim completed",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showLiveFooter": {
|
|
"type": "boolean",
|
|
"label": "Match line",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"absProgress": {
|
|
"type": "boolean",
|
|
"label": "Song progress",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"absSections": {
|
|
"type": "boolean",
|
|
"label": "Song sections",
|
|
"default": true,
|
|
"group": "Show"
|
|
}
|
|
},
|
|
"tiles": [
|
|
{
|
|
"id": "plan",
|
|
"name": "Plan",
|
|
"description": "The order of service, item by item",
|
|
"defaultSize": {
|
|
"w": 5,
|
|
"h": 6
|
|
},
|
|
"minSize": {
|
|
"w": 3,
|
|
"h": 3
|
|
}
|
|
},
|
|
{
|
|
"id": "teams",
|
|
"name": "Teams",
|
|
"description": "Who is scheduled, by team and position",
|
|
"defaultSize": {
|
|
"w": 4,
|
|
"h": 6
|
|
},
|
|
"minSize": {
|
|
"w": 2,
|
|
"h": 2
|
|
},
|
|
"instanceSchema": {
|
|
"columns": {
|
|
"type": "select",
|
|
"label": "Columns",
|
|
"default": "auto",
|
|
"options": [
|
|
{
|
|
"value": "auto",
|
|
"label": "Fit the tile"
|
|
},
|
|
{
|
|
"value": "1",
|
|
"label": "One"
|
|
},
|
|
{
|
|
"value": "2",
|
|
"label": "Two"
|
|
},
|
|
{
|
|
"value": "3",
|
|
"label": "Three"
|
|
}
|
|
]
|
|
},
|
|
"teams": {
|
|
"type": "multiselect",
|
|
"label": "Teams",
|
|
"columns": 2,
|
|
"allByDefault": true,
|
|
"optionsRoute": "/teams"
|
|
},
|
|
"headerTemplate": {
|
|
"type": "template",
|
|
"label": "Top bar text",
|
|
"default": "{serviceType} • {date} | {people} on {teams}",
|
|
"group": "Top bar",
|
|
"variables": [
|
|
{
|
|
"value": "{series}",
|
|
"label": "Series"
|
|
},
|
|
{
|
|
"value": "{part}",
|
|
"label": "Part / plan title"
|
|
},
|
|
{
|
|
"value": "{serviceType}",
|
|
"label": "Service type"
|
|
},
|
|
{
|
|
"value": "{date}",
|
|
"label": "Date"
|
|
},
|
|
{
|
|
"value": "{shortDate}",
|
|
"label": "Short date"
|
|
},
|
|
{
|
|
"value": "{time}",
|
|
"label": "Service time"
|
|
},
|
|
{
|
|
"value": "{people}",
|
|
"label": "People count"
|
|
},
|
|
{
|
|
"value": "{teams}",
|
|
"label": "Team count"
|
|
},
|
|
{
|
|
"value": "{confirmed}",
|
|
"label": "Confirmed count"
|
|
}
|
|
],
|
|
"help": "Use • between items and | to start a second line."
|
|
},
|
|
"teamColor": {
|
|
"type": "color",
|
|
"label": "Team headers",
|
|
"default": "#2ee59a",
|
|
"group": "Colors"
|
|
},
|
|
"tintTeams": {
|
|
"type": "boolean",
|
|
"label": "Tint teams",
|
|
"default": false,
|
|
"group": "Colors"
|
|
},
|
|
"tintRows": {
|
|
"type": "boolean",
|
|
"label": "Tint rows too",
|
|
"default": false,
|
|
"group": "Colors",
|
|
"showWhen": "tintTeams"
|
|
},
|
|
"headerGradient": {
|
|
"type": "boolean",
|
|
"label": "Header fade",
|
|
"default": true,
|
|
"group": "Colors"
|
|
},
|
|
"confirmedColor": {
|
|
"type": "color",
|
|
"label": "Confirmed",
|
|
"default": "#2ee59a",
|
|
"group": "Colors"
|
|
},
|
|
"unconfirmedColor": {
|
|
"type": "color",
|
|
"label": "Unconfirmed",
|
|
"default": "#f0b429",
|
|
"group": "Colors"
|
|
},
|
|
"declinedColor": {
|
|
"type": "color",
|
|
"label": "Declined",
|
|
"default": "#f4433c",
|
|
"group": "Colors"
|
|
},
|
|
"showTopBar": {
|
|
"type": "boolean",
|
|
"label": "Top bar",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showTeamHeaders": {
|
|
"type": "boolean",
|
|
"label": "Team headers",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showTeamCounts": {
|
|
"type": "boolean",
|
|
"label": "Team counts",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showAvatars": {
|
|
"type": "boolean",
|
|
"label": "Avatars",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showPosition": {
|
|
"type": "boolean",
|
|
"label": "Positions",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"groupByPosition": {
|
|
"type": "boolean",
|
|
"label": "Group by position",
|
|
"default": false,
|
|
"group": "Show"
|
|
},
|
|
"showStatus": {
|
|
"type": "boolean",
|
|
"label": "Status",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showNotes": {
|
|
"type": "boolean",
|
|
"label": "Person notes",
|
|
"default": false,
|
|
"group": "Show"
|
|
},
|
|
"showDeclined": {
|
|
"type": "boolean",
|
|
"label": "Declined people",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showUnconfirmed": {
|
|
"type": "boolean",
|
|
"label": "Unconfirmed people",
|
|
"default": true,
|
|
"group": "Show"
|
|
},
|
|
"showEmptyTeams": {
|
|
"type": "boolean",
|
|
"label": "Teams with nobody",
|
|
"default": false,
|
|
"group": "Show"
|
|
}
|
|
},
|
|
"instanceGroups": {
|
|
"Top bar": {
|
|
"collapsed": false
|
|
},
|
|
"Colors": {
|
|
"collapsed": false,
|
|
"columns": 2
|
|
},
|
|
"Show": {
|
|
"collapsed": true,
|
|
"columns": 2
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"id": "key",
|
|
"name": "Song Key",
|
|
"description": "The key of the item on screen, big",
|
|
"defaultSize": {
|
|
"w": 2,
|
|
"h": 2
|
|
},
|
|
"minSize": {
|
|
"w": 1,
|
|
"h": 1
|
|
},
|
|
"instanceSchema": {
|
|
"keyColor": {
|
|
"type": "color",
|
|
"label": "Key",
|
|
"default": "#4ea1ff"
|
|
},
|
|
"showSongName": {
|
|
"type": "boolean",
|
|
"label": "Song name",
|
|
"default": true
|
|
},
|
|
"showNextKey": {
|
|
"type": "boolean",
|
|
"label": "Next key",
|
|
"default": true
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|