Rules for a clean interface, and the modules brought to them
Seven rules join the four in the module guide, as rules 5 to 11: no
explanatory text in a tile's gear and no label over three words; server-side
settings in configSchema only; a tile setting changes that tile alone;
hide-toggles ungrouped when there are four or fewer and in a folded "Show"
group when there are five or more; title-bar controls are icons with
tooltips, never names; anything needing words belongs in the gear; and one
control per action, either place but not both.
Two passages said the opposite and now say the same thing — "keep labels
short and put the explanation there" is scoped to configSchema, and "icon
buttons, short labels" becomes icons with no labels at all. CLAUDE.md
carries the summary.
Then the modules. Nothing here changes a setting key or a default, so saved
layouts, named layouts and every tile already on a wall keep working; group
names, labels, help and fold state are presentation and are not stored.
- pco-plan: sixteen labels cut to three words or fewer ("Tint items by type
(song / media / other)" is now "Tint by type"), "Show / hide" renamed
"Show" and folded. Its one remaining help line stays by request: the top
bar's template needs its separator syntax spelled out, which the variable
chips beside it cannot convey, so rule 5 carves out template fields.
- propresenter-timers: the "Timers ▾" menu was the only named control left
in any title bar. It is a stopwatch icon now, with the count riding
alongside it while some timers are hidden — setLabel() writes over the
button's contents, so the count needed ProdCom's badge treatment rather
than a label. "Show" folds; the Colours help goes.
- propresenter-now-next: "Show" folds, in the manifest and in the Now
entry's own copy.
- prodcom-transcript: "Show channel icons" loses the redundant verb.
slack and clock were already compliant and are untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
82e6386c73
commit
12f0b20e5f
@@ -32,6 +32,12 @@ title-bar controls). The rules that matter most:
|
|||||||
and bump the module's `version` on every change.
|
and bump the module's `version` on every change.
|
||||||
- Browsers never call upstream services directly; the module's server routes
|
- Browsers never call upstream services directly; the module's server routes
|
||||||
proxy everything (no CORS, secrets stay server-side).
|
proxy everything (no CORS, secrets stay server-side).
|
||||||
|
- **Keep the interface clean** (guide rules 5-11): no `help` and no label
|
||||||
|
over three words in a tile's gear; server-side settings in `configSchema`
|
||||||
|
only; a tile setting changes that tile alone; hide-toggles ungrouped when
|
||||||
|
there are four or fewer, in a folded **Show** group when there are five or
|
||||||
|
more; title-bar controls are icons with tooltips, never labels, and never
|
||||||
|
repeat something the gear already offers.
|
||||||
- Style with the shell's CSS variables only, scoped under
|
- Style with the shell's CSS variables only, scoped under
|
||||||
`[data-module="<id>"]`; survive upstream loss (reconnect + `setStatus`).
|
`[data-module="<id>"]`; survive upstream loss (reconnect + `setStatus`).
|
||||||
- Verify against the guide's testing section and resilience checklist. Mocks
|
- Verify against the guide's testing section and resilience checklist. Mocks
|
||||||
|
|||||||
+62
-16
@@ -34,6 +34,46 @@ people running the booth depend on them.
|
|||||||
The admin page refuses to install a module whose requirement the running
|
The admin page refuses to install a module whose requirement the running
|
||||||
shell doesn't meet, and it offers updates by comparing versions.
|
shell doesn't meet, and it offers updates by comparing versions.
|
||||||
|
|
||||||
|
### Keeping the interface clean
|
||||||
|
|
||||||
|
A dashboard is read across a booth, at a glance, by someone doing three
|
||||||
|
other things. These rules are what keep every tile looking like one product
|
||||||
|
instead of six. They are as binding as the four above.
|
||||||
|
|
||||||
|
5. **No explanatory text in a tile's gear.** No `help` on an
|
||||||
|
`instanceSchema` field or an `instanceGroups` entry. Labels are **three
|
||||||
|
words at most, one or two preferred** — "Slide count", "Item tags",
|
||||||
|
"Running clock". A control that cannot be understood in three words is
|
||||||
|
not a display choice; it belongs in `configSchema`, where `help` is
|
||||||
|
welcome and expected. *One exception:* a `template` field may keep a
|
||||||
|
single `help` line naming its placeholders, which nobody can guess.
|
||||||
|
6. **Server-side settings are admin-only.** Anything the module's server
|
||||||
|
acts on — a host, a credential, what to fetch, how often to poll — is
|
||||||
|
`configSchema`. `instanceSchema` is display choices and nothing else:
|
||||||
|
what this tile shows, and how big.
|
||||||
|
7. **A tile setting changes that tile and nothing else.** Never another
|
||||||
|
tile, never the module as a whole, never the server. If two tiles must
|
||||||
|
agree on something, it is either admin config or page state the shell
|
||||||
|
owns — not a setting each tile keeps its own copy of.
|
||||||
|
8. **Offer toggles for the elements you draw.** Every tile should let the
|
||||||
|
operator switch off the parts it does not need. **One to four toggles:**
|
||||||
|
give them no `group` at all, so they sit unlabelled at the top of the
|
||||||
|
gear. **Five or more:** one group named **Show**, `collapsed: true`,
|
||||||
|
`columns: 2`. Nothing else goes in that group — colours, formats and
|
||||||
|
text belong in groups of their own.
|
||||||
|
9. **Title-bar controls are icons.** No `label` on `addButton` or
|
||||||
|
`addMenu`; the tooltip carries the words, and `docs` above covers drawing
|
||||||
|
the icon. A small state indicator may ride alongside an icon — ProdCom's
|
||||||
|
channel button shows a count while a filter is on — but a control is
|
||||||
|
never named in the bar.
|
||||||
|
10. **If a control needs words to explain it, it is not a title-bar
|
||||||
|
control.** Put it in the gear. The bar is for actions whose icon and
|
||||||
|
tooltip say everything: refresh, clear, jump to latest, bigger text.
|
||||||
|
11. **One control per action.** Whatever has a dedicated title-bar button is
|
||||||
|
not offered again in the gear, and the reverse. Text size is A− / A+ up
|
||||||
|
there, never a "Text size" field; a timestamps button in the bar means
|
||||||
|
no timestamps switch below it.
|
||||||
|
|
||||||
## The shape of a module
|
## The shape of a module
|
||||||
|
|
||||||
A module is one folder dropped into `modules/`:
|
A module is one folder dropped into `modules/`:
|
||||||
@@ -177,14 +217,18 @@ the group name:
|
|||||||
|
|
||||||
`help` on a field puts one line of guidance under its control — allowed
|
`help` on a field puts one line of guidance under its control — allowed
|
||||||
placeholders, where a value comes from. Keep labels short and put the
|
placeholders, where a value comes from. Keep labels short and put the
|
||||||
explanation there.
|
explanation there. **This is for `configSchema` only**: a tile's gear
|
||||||
|
carries no explanations at all (rule 5), so a setting that needs one is
|
||||||
|
telling you it belongs in admin.
|
||||||
|
|
||||||
`group` and `help` work on `instanceSchema` fields too, and a manifest
|
`group` works on `instanceSchema` fields too, and a manifest
|
||||||
`instanceGroups` map plays the `configGroups` role for the tile's gear
|
`instanceGroups` map plays the `configGroups` role for the tile's gear
|
||||||
popover (`collapsed: true | false`, `columns`, `help`, `toggle`). That is how
|
popover (`collapsed: true | false`, `columns`, `toggle` — but not `help`,
|
||||||
a module with a long show/hide checklist keeps the popover usable — see
|
per rule 5). That is how a module with a long show/hide checklist keeps the
|
||||||
`pco-plan`. Display choices belong there, per tile; admin config is for
|
popover usable: five or more hide-toggles go in one **Show** group, folded
|
||||||
connections. The popover itself is a small window beside the tile — sized to
|
by default, and fewer than five go in no group at all — see `pco-plan` for
|
||||||
|
the long form and `slack` for the short one. Display choices belong there,
|
||||||
|
per tile; admin config is for connections. The popover itself is a small window beside the tile — sized to
|
||||||
its content, moved by the slim bar on top, closed by clicking anywhere else
|
its content, moved by the slim bar on top, closed by clicking anywhere else
|
||||||
or pressing Escape — so a tile never has to be big enough to hold its own
|
or pressing Escape — so a tile never has to be big enough to hold its own
|
||||||
settings, and a change can be watched as it lands. Under everything you
|
settings, and a change can be watched as it lands. Under everything you
|
||||||
@@ -354,17 +398,19 @@ menu.setLabel('Channels (2/4) ▾'); // update the button text any time
|
|||||||
```
|
```
|
||||||
|
|
||||||
Everything you add is removed automatically when your instance stops, so a
|
Everything you add is removed automatically when your instance stops, so a
|
||||||
remount never duplicates controls. Keep them compact (icon buttons, short
|
remount never duplicates controls. **Icons only, no labels** (rule 9): the
|
||||||
labels): the title bar is 32 px tall and shared with the tile's name. Draw
|
title bar is 32 px tall and shared with the tile's name, and the tooltip
|
||||||
icons as inline SVG rather than characters — a glyph such as ✕ or + takes
|
says what the icon means. Draw icons as inline SVG rather than characters —
|
||||||
its size from whatever font each browser picks; text-size buttons are a
|
a glyph such as ✕ or + takes its size from whatever font each browser
|
||||||
drawn − and +.
|
picks; text-size buttons are a drawn − and +. A small state indicator may
|
||||||
|
ride alongside an icon (ProdCom's channel button carries a count while a
|
||||||
|
filter is on); a name may not.
|
||||||
|
|
||||||
**One control per action.** Whatever has a dedicated header button is not
|
**One control per action** (rule 11). Whatever has a dedicated header button
|
||||||
offered again in the gear popover: text size is A− / A+ up here (persisted
|
is not offered again in the gear popover: text size is − / + up here
|
||||||
through `saveInstanceSettings({ textSize })`, read back with a sane
|
(persisted through `saveInstanceSettings({ textSize })`, read back with a
|
||||||
fallback), never a "Text size" field in `instanceSchema`; a timestamps or
|
sane fallback), never a "Text size" field in `instanceSchema`; a timestamps
|
||||||
newest-first toggle in the header means no such switch in the gear. The
|
or newest-first toggle in the header means no such switch in the gear. The
|
||||||
gear is for display choices that have no button of their own.
|
gear is for display choices that have no button of their own.
|
||||||
|
|
||||||
One more thing to know: the whole title bar folds away when the dashboard
|
One more thing to know: the whole title bar folds away when the dashboard
|
||||||
|
|||||||
+245
-49
@@ -1,15 +1,23 @@
|
|||||||
{
|
{
|
||||||
"id": "pco-plan",
|
"id": "pco-plan",
|
||||||
"name": "PCO Plan",
|
"name": "PCO Plan",
|
||||||
"version": "1.4.0",
|
"version": "1.5.0",
|
||||||
"proddash": ">=1.4.0",
|
"proddash": ">=1.4.0",
|
||||||
"description": "Order of service from Planning Center Services",
|
"description": "Order of service from Planning Center Services",
|
||||||
"provides": ["timers"],
|
"provides": [
|
||||||
|
"timers"
|
||||||
|
],
|
||||||
"client": "client.js",
|
"client": "client.js",
|
||||||
"server": "server.js",
|
"server": "server.js",
|
||||||
"style": "style.css",
|
"style": "style.css",
|
||||||
"minSize": { "w": 3, "h": 3 },
|
"minSize": {
|
||||||
"defaultSize": { "w": 5, "h": 6 },
|
"w": 3,
|
||||||
|
"h": 3
|
||||||
|
},
|
||||||
|
"defaultSize": {
|
||||||
|
"w": 5,
|
||||||
|
"h": 6
|
||||||
|
},
|
||||||
"configGroups": {
|
"configGroups": {
|
||||||
"Planning Center credentials": {
|
"Planning Center credentials": {
|
||||||
"collapsed": "whenSet",
|
"collapsed": "whenSet",
|
||||||
@@ -48,7 +56,12 @@
|
|||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Plan",
|
"label": "Plan",
|
||||||
"default": "",
|
"default": "",
|
||||||
"options": [{ "value": "", "label": "Automatic — today's plan, else the next upcoming" }],
|
"options": [
|
||||||
|
{
|
||||||
|
"value": "",
|
||||||
|
"label": "Automatic — today's plan, else the next upcoming"
|
||||||
|
}
|
||||||
|
],
|
||||||
"optionsRoute": "/plans",
|
"optionsRoute": "/plans",
|
||||||
"optionsDependsOn": "serviceTypeId"
|
"optionsDependsOn": "serviceTypeId"
|
||||||
},
|
},
|
||||||
@@ -82,8 +95,14 @@
|
|||||||
"group": "Service timing",
|
"group": "Service timing",
|
||||||
"showWhen": "useDocumentTriggers",
|
"showWhen": "useDocumentTriggers",
|
||||||
"options": [
|
"options": [
|
||||||
{ "value": "schedule", "label": "Scheduled service time" },
|
{
|
||||||
{ "value": "document", "label": "ProPresenter document" }
|
"value": "schedule",
|
||||||
|
"label": "Scheduled service time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "document",
|
||||||
|
"label": "ProPresenter document"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"serviceStartDocument": {
|
"serviceStartDocument": {
|
||||||
@@ -124,37 +143,94 @@
|
|||||||
"Item notes": {
|
"Item notes": {
|
||||||
"toggle": "showNotes"
|
"toggle": "showNotes"
|
||||||
},
|
},
|
||||||
"Show / hide": {
|
"Show": {
|
||||||
"collapsed": true,
|
"collapsed": true,
|
||||||
"columns": 3,
|
"columns": 2
|
||||||
"help": "Switch off anything this tile should not show."
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"instanceSchema": {
|
"instanceSchema": {
|
||||||
"autoScroll": { "type": "boolean", "label": "Keep the current item in view", "default": true },
|
"autoScroll": {
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Auto-scroll",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
"headerTemplate": {
|
"headerTemplate": {
|
||||||
"type": "template",
|
"type": "template",
|
||||||
"label": "Top bar text",
|
"label": "Top bar text",
|
||||||
"default": "{series} • {part} | {serviceType} • {date}",
|
"default": "{series} • {part} | {serviceType} • {date}",
|
||||||
"group": "Top bar",
|
"group": "Top bar",
|
||||||
"variables": [
|
"variables": [
|
||||||
{ "value": "{series}", "label": "Series" },
|
{
|
||||||
{ "value": "{part}", "label": "Part / plan title" },
|
"value": "{series}",
|
||||||
{ "value": "{serviceType}", "label": "Service type" },
|
"label": "Series"
|
||||||
{ "value": "{date}", "label": "Date" },
|
},
|
||||||
{ "value": "{shortDate}", "label": "Short date" },
|
{
|
||||||
{ "value": "{time}", "label": "Service time" },
|
"value": "{part}",
|
||||||
{ "value": "{length}", "label": "Planned length" },
|
"label": "Part / plan title"
|
||||||
{ "value": "{items}", "label": "Item count" }
|
},
|
||||||
|
{
|
||||||
|
"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."
|
"help": "Use • between items and | to start a second line."
|
||||||
},
|
},
|
||||||
"keyColor": { "type": "color", "label": "Song key", "default": "#4ea1ff", "group": "Colors" },
|
"keyColor": {
|
||||||
"headerColor": { "type": "color", "label": "Section headers", "default": "#2ee59a", "group": "Colors" },
|
"type": "color",
|
||||||
"currentColor": { "type": "color", "label": "Current item", "default": "#2ee59a", "group": "Colors" },
|
"label": "Song key",
|
||||||
"headerGradient": { "type": "boolean", "label": "Fade header color to the right", "default": true, "group": "Colors" },
|
"default": "#4ea1ff",
|
||||||
"tintByType": { "type": "boolean", "label": "Tint items by type (song / media / other)", "default": false, "group": "Colors" },
|
"group": "Colors"
|
||||||
"showNotes": { "type": "switch", "label": "Item notes", "default": true, "group": "Item notes" },
|
},
|
||||||
|
"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": {
|
"noteCategories": {
|
||||||
"type": "multiselect",
|
"type": "multiselect",
|
||||||
"label": "Categories",
|
"label": "Categories",
|
||||||
@@ -163,29 +239,149 @@
|
|||||||
"columns": 3,
|
"columns": 3,
|
||||||
"allByDefault": true
|
"allByDefault": true
|
||||||
},
|
},
|
||||||
"showTopBar": { "type": "boolean", "label": "Top bar", "default": true, "group": "Show / hide" },
|
"showTopBar": {
|
||||||
"showSeries": { "type": "boolean", "label": "Series name", "default": true, "group": "Show / hide" },
|
"type": "boolean",
|
||||||
"showPart": { "type": "boolean", "label": "Part # / plan title", "default": true, "group": "Show / hide" },
|
"label": "Top bar",
|
||||||
"showServiceType": { "type": "boolean", "label": "Service type", "default": true, "group": "Show / hide" },
|
"default": true,
|
||||||
"showDate": { "type": "boolean", "label": "Plan date", "default": true, "group": "Show / hide" },
|
"group": "Show"
|
||||||
"showServiceTime": { "type": "boolean", "label": "Service time", "default": true, "group": "Show / hide" },
|
},
|
||||||
"showTotalLength": { "type": "boolean", "label": "Planned total length", "default": true, "group": "Show / hide" },
|
"showSeries": {
|
||||||
"showRunningClock": { "type": "boolean", "label": "Running clock (ahead / behind)", "default": true, "group": "Show / hide" },
|
"type": "boolean",
|
||||||
"showCountdown": { "type": "boolean", "label": "Countdown to service start", "default": true, "group": "Show / hide" },
|
"label": "Series name",
|
||||||
"showHeaders": { "type": "boolean", "label": "Section headers", "default": true, "group": "Show / hide" },
|
"default": true,
|
||||||
"showItemNumbers": { "type": "boolean", "label": "Item numbers", "default": false, "group": "Show / hide" },
|
"group": "Show"
|
||||||
"showStartTimes": { "type": "boolean", "label": "Scheduled start times", "default": true, "group": "Show / hide" },
|
},
|
||||||
"showLength": { "type": "boolean", "label": "Item length", "default": true, "group": "Show / hide" },
|
"showPart": {
|
||||||
"showItemType": { "type": "boolean", "label": "Item type badge", "default": true, "group": "Show / hide" },
|
"type": "boolean",
|
||||||
"showKey": { "type": "boolean", "label": "Song key", "default": true, "group": "Show / hide" },
|
"label": "Part / title",
|
||||||
"showArrangement": { "type": "boolean", "label": "Song arrangement", "default": false, "group": "Show / hide" },
|
"default": true,
|
||||||
"showSongAuthor": { "type": "boolean", "label": "Song author / CCLI", "default": false, "group": "Show / hide" },
|
"group": "Show"
|
||||||
"showDescription": { "type": "boolean", "label": "Item description", "default": true, "group": "Show / hide" },
|
},
|
||||||
"showPreService": { "type": "boolean", "label": "Pre-service items", "default": true, "group": "Show / hide" },
|
"showServiceType": {
|
||||||
"showPostService": { "type": "boolean", "label": "Post-service items", "default": true, "group": "Show / hide" },
|
"type": "boolean",
|
||||||
"showNowIndicator": { "type": "boolean", "label": "Now indicator", "default": true, "group": "Show / hide" },
|
"label": "Service type",
|
||||||
"showItemRuntime": { "type": "boolean", "label": "Item runtime (elapsed vs planned)", "default": true, "group": "Show / hide" },
|
"default": true,
|
||||||
"dimCompleted": { "type": "boolean", "label": "Dim completed items", "default": true, "group": "Show / hide" },
|
"group": "Show"
|
||||||
"showLiveFooter": { "type": "boolean", "label": "ProPresenter match line", "default": true, "group": "Show / hide" }
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "prodcom-transcript",
|
"id": "prodcom-transcript",
|
||||||
"name": "ProdCom",
|
"name": "ProdCom",
|
||||||
"version": "1.3.1",
|
"version": "1.3.2",
|
||||||
"proddash": ">=1.1.0",
|
"proddash": ">=1.1.0",
|
||||||
"description": "Live ProdCom transcript",
|
"description": "Live ProdCom transcript",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"instanceSchema": {
|
"instanceSchema": {
|
||||||
"showAvatars": {
|
"showAvatars": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Show channel icons",
|
"label": "Channel icons",
|
||||||
"default": true
|
"default": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,34 @@
|
|||||||
{
|
{
|
||||||
"id": "propresenter-now-next",
|
"id": "propresenter-now-next",
|
||||||
"name": "ProPresenter",
|
"name": "ProPresenter",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"proddash": ">=1.5.0",
|
"proddash": ">=1.5.0",
|
||||||
"description": "What is live in ProPresenter and what comes next",
|
"description": "What is live in ProPresenter and what comes next",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{ "kind": "local-network", "reason": "Reaching ProPresenter on this network" }
|
{
|
||||||
|
"kind": "local-network",
|
||||||
|
"reason": "Reaching ProPresenter on this network"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"client": "client.js",
|
"client": "client.js",
|
||||||
"server": "server.js",
|
"server": "server.js",
|
||||||
"style": "style.css",
|
"style": "style.css",
|
||||||
"minSize": { "w": 3, "h": 3 },
|
"minSize": {
|
||||||
"defaultSize": { "w": 4, "h": 5 },
|
"w": 3,
|
||||||
|
"h": 3
|
||||||
|
},
|
||||||
|
"defaultSize": {
|
||||||
|
"w": 4,
|
||||||
|
"h": 5
|
||||||
|
},
|
||||||
"configSchema": {
|
"configSchema": {
|
||||||
"propresenter": {
|
"propresenter": {
|
||||||
"type": "endpoint",
|
"type": "endpoint",
|
||||||
"label": "ProPresenter",
|
"label": "ProPresenter",
|
||||||
"default": { "host": "", "port": 1025 }
|
"default": {
|
||||||
|
"host": "",
|
||||||
|
"port": 1025
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"type": "password",
|
"type": "password",
|
||||||
@@ -25,17 +37,64 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"instanceSchema": {
|
"instanceSchema": {
|
||||||
"cleanTitles": { "type": "switch", "label": "Tidy titles", "default": true },
|
"cleanTitles": {
|
||||||
"showPlaylist": { "type": "switch", "label": "Playlist", "default": true, "group": "Show" },
|
"type": "switch",
|
||||||
"showLive": { "type": "switch", "label": "Live status", "default": true, "group": "Show" },
|
"label": "Tidy titles",
|
||||||
"showNowLabel": { "type": "switch", "label": "Now label", "default": true, "group": "Show" },
|
"default": true
|
||||||
"showSlideCount": { "type": "switch", "label": "Slide count", "default": true, "group": "Show" },
|
},
|
||||||
"showProgress": { "type": "switch", "label": "Progress bar", "default": true, "group": "Show" },
|
"showPlaylist": {
|
||||||
"showNext": { "type": "switch", "label": "Next list", "default": true, "group": "Show" },
|
"type": "switch",
|
||||||
"showNextLabel": { "type": "switch", "label": "Next label", "default": true, "group": "Show" },
|
"label": "Playlist",
|
||||||
"showTags": { "type": "switch", "label": "Item tags", "default": true, "group": "Show" }
|
"default": true,
|
||||||
|
"group": "Show"
|
||||||
|
},
|
||||||
|
"showLive": {
|
||||||
|
"type": "switch",
|
||||||
|
"label": "Live status",
|
||||||
|
"default": true,
|
||||||
|
"group": "Show"
|
||||||
|
},
|
||||||
|
"showNowLabel": {
|
||||||
|
"type": "switch",
|
||||||
|
"label": "Now label",
|
||||||
|
"default": true,
|
||||||
|
"group": "Show"
|
||||||
|
},
|
||||||
|
"showSlideCount": {
|
||||||
|
"type": "switch",
|
||||||
|
"label": "Slide count",
|
||||||
|
"default": true,
|
||||||
|
"group": "Show"
|
||||||
|
},
|
||||||
|
"showProgress": {
|
||||||
|
"type": "switch",
|
||||||
|
"label": "Progress bar",
|
||||||
|
"default": true,
|
||||||
|
"group": "Show"
|
||||||
|
},
|
||||||
|
"showNext": {
|
||||||
|
"type": "switch",
|
||||||
|
"label": "Next list",
|
||||||
|
"default": true,
|
||||||
|
"group": "Show"
|
||||||
|
},
|
||||||
|
"showNextLabel": {
|
||||||
|
"type": "switch",
|
||||||
|
"label": "Next label",
|
||||||
|
"default": true,
|
||||||
|
"group": "Show"
|
||||||
|
},
|
||||||
|
"showTags": {
|
||||||
|
"type": "switch",
|
||||||
|
"label": "Item tags",
|
||||||
|
"default": true,
|
||||||
|
"group": "Show"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"instanceGroups": {
|
"instanceGroups": {
|
||||||
"Show": { "collapsed": false, "columns": 2 }
|
"Show": {
|
||||||
|
"collapsed": true,
|
||||||
|
"columns": 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ module.exports = {
|
|||||||
showSlideCount: { type: 'switch', label: 'Slide count', default: true, group: 'Show' },
|
showSlideCount: { type: 'switch', label: 'Slide count', default: true, group: 'Show' },
|
||||||
showProgress: { type: 'switch', label: 'Progress bar', default: true, group: 'Show' },
|
showProgress: { type: 'switch', label: 'Progress bar', default: true, group: 'Show' },
|
||||||
},
|
},
|
||||||
instanceGroups: { Show: { collapsed: false, columns: 2 } },
|
instanceGroups: { Show: { collapsed: true, columns: 2 } },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
Per-tile choices: the gear popover's static switches (whole sources,
|
Per-tile choices: the gear popover's static switches (whole sources,
|
||||||
headings, status lines, the clock's format, the state colours) and the
|
headings, status lines, the clock's format, the state colours) and the
|
||||||
"Timers ▾" header menu, which hides any single timer — keyed `source:id`
|
timers header menu, which hides any single timer — keyed `source:id`
|
||||||
and saved with the layout via saveInstanceSettings. The header can be
|
and saved with the layout via saveInstanceSettings. The header can be
|
||||||
hidden by the user, so the static switches stay the primary controls.
|
hidden by the user, so the static switches stay the primary controls.
|
||||||
A solo tile's gear carries only the settings its one card can use (the
|
A solo tile's gear carries only the settings its one card can use (the
|
||||||
@@ -196,6 +196,10 @@ function defaultStatus(timer, value, tone) {
|
|||||||
|
|
||||||
/* ── the tile ─────────────────────────────────────────────────────────── */
|
/* ── the tile ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/* The header menu's icon: a stopwatch, for the list of timers behind it.
|
||||||
|
Title-bar controls carry no words (module guide, rule 9). */
|
||||||
|
const TIMERS_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 2h4"/><path d="M12 14V9"/><circle cx="12" cy="14" r="8"/></svg>';
|
||||||
|
|
||||||
export default function create({ root, moduleApi }) {
|
export default function create({ root, moduleApi }) {
|
||||||
let state = null;
|
let state = null;
|
||||||
let feedOffline = false;
|
let feedOffline = false;
|
||||||
@@ -227,7 +231,7 @@ export default function create({ root, moduleApi }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Which picker entry this tile was added as (the module's tiles() list):
|
/* Which picker entry this tile was added as (the module's tiles() list):
|
||||||
'' or 'all' = every timer, selectable via the Timers ▾ menu;
|
'' or 'all' = every timer, selectable via the timers menu;
|
||||||
'solo:<source>:<id>' = exactly one card, no menu, no headings. The
|
'solo:<source>:<id>' = exactly one card, no menu, no headings. The
|
||||||
earlier entries 'timer:<uuid>' and 'ltc' map onto the new keys. */
|
earlier entries 'timer:<uuid>' and 'ltc' map onto the new keys. */
|
||||||
const variant = String(moduleApi.variant || '');
|
const variant = String(moduleApi.variant || '');
|
||||||
@@ -283,8 +287,8 @@ export default function create({ root, moduleApi }) {
|
|||||||
|
|
||||||
let menuEl = null; // the open menu's element (filled on each open)
|
let menuEl = null; // the open menu's element (filled on each open)
|
||||||
const itemsMenu = solo ? null : moduleApi.header.addMenu({
|
const itemsMenu = solo ? null : moduleApi.header.addMenu({
|
||||||
label: 'Timers ▾',
|
icon: TIMERS_SVG,
|
||||||
title: 'Choose timers',
|
title: 'Timers',
|
||||||
build(menu) {
|
build(menu) {
|
||||||
// the shell hands over an emptied element on every open
|
// the shell hands over an emptied element on every open
|
||||||
menu.classList.add('tm-menu');
|
menu.classList.add('tm-menu');
|
||||||
@@ -292,6 +296,12 @@ export default function create({ root, moduleApi }) {
|
|||||||
rebuildMenu(true);
|
rebuildMenu(true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
// The icon carries the name; a count rides alongside it while some timers
|
||||||
|
// are hidden, so the bar says "filtered" without saying it in words.
|
||||||
|
const itemsCountEl = document.createElement('span');
|
||||||
|
itemsCountEl.className = 'tm-count';
|
||||||
|
itemsCountEl.hidden = true;
|
||||||
|
if (itemsMenu) itemsMenu.button.appendChild(itemsCountEl);
|
||||||
|
|
||||||
function menuRows() {
|
function menuRows() {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
@@ -319,7 +329,9 @@ export default function create({ root, moduleApi }) {
|
|||||||
if (!itemsMenu) return;
|
if (!itemsMenu) return;
|
||||||
const rows = menuRows();
|
const rows = menuRows();
|
||||||
const visible = rows.filter((r) => !hidden.has(r.key)).length;
|
const visible = rows.filter((r) => !hidden.has(r.key)).length;
|
||||||
itemsMenu.setLabel(visible === rows.length ? 'Timers ▾' : `Timers (${visible}/${rows.length}) ▾`);
|
const filtering = visible !== rows.length;
|
||||||
|
itemsCountEl.textContent = filtering ? `${visible}/${rows.length}` : '';
|
||||||
|
itemsCountEl.hidden = !filtering;
|
||||||
}
|
}
|
||||||
|
|
||||||
let menuSignature = '';
|
let menuSignature = '';
|
||||||
@@ -779,7 +791,7 @@ export default function create({ root, moduleApi }) {
|
|||||||
let hint = '';
|
let hint = '';
|
||||||
const groups = visibleGroups();
|
const groups = visibleGroups();
|
||||||
if (state && !state.sources.length) hint = 'Every source is switched off — turn one on in Admin.';
|
if (state && !state.sources.length) hint = 'Every source is switched off — turn one on in Admin.';
|
||||||
else if (state && !groups.length) hint = 'Nothing selected — switch a source on in this tile’s settings, or pick timers in the Timers ▾ menu.';
|
else if (state && !groups.length) hint = 'Nothing selected — switch a source on in this tile’s settings, or pick timers in the title bar’s timers menu.';
|
||||||
hintEl.hidden = !hint;
|
hintEl.hidden = !hint;
|
||||||
hintEl.textContent = hint;
|
hintEl.textContent = hint;
|
||||||
groupsEl.hidden = Boolean(hint);
|
groupsEl.hidden = Boolean(hint);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "propresenter-timers",
|
"id": "propresenter-timers",
|
||||||
"name": "Timers",
|
"name": "Timers",
|
||||||
"version": "1.3.0",
|
"version": "1.4.0",
|
||||||
"proddash": ">=1.4.0",
|
"proddash": ">=1.4.0",
|
||||||
"description": "Timers, Clocks, and LTC From configured Modules",
|
"description": "Timers, Clocks, and LTC From configured Modules",
|
||||||
"provides": [
|
"provides": [
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
},
|
},
|
||||||
"instanceGroups": {
|
"instanceGroups": {
|
||||||
"Show": {
|
"Show": {
|
||||||
"collapsed": false,
|
"collapsed": true,
|
||||||
"columns": 2
|
"columns": 2
|
||||||
},
|
},
|
||||||
"Clock": {
|
"Clock": {
|
||||||
@@ -121,8 +121,7 @@
|
|||||||
},
|
},
|
||||||
"Colours": {
|
"Colours": {
|
||||||
"collapsed": true,
|
"collapsed": true,
|
||||||
"columns": 2,
|
"columns": 2
|
||||||
"help": "The time's colour per state. Left at the default, a colour follows the dashboard theme; one you change here wins over it."
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"instanceSchema": {
|
"instanceSchema": {
|
||||||
|
|||||||
@@ -321,7 +321,7 @@
|
|||||||
|
|
||||||
@keyframes tm-nosignal-blink { 50% { opacity: 0.35; } }
|
@keyframes tm-nosignal-blink { 50% { opacity: 0.35; } }
|
||||||
|
|
||||||
/* ── the "Timers ▾" menu (container styled/positioned by the shell) ── */
|
/* ── the timers menu (container styled/positioned by the shell) ── */
|
||||||
|
|
||||||
[data-module="propresenter-timers"] .tm-menu-item {
|
[data-module="propresenter-timers"] .tm-menu-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -383,3 +383,11 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* the count beside the title-bar timers icon, while some are hidden */
|
||||||
|
[data-module="propresenter-timers"] .tm-count {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
[data-module="propresenter-timers"] .tm-count[hidden] { display: none; }
|
||||||
|
|||||||
Reference in New Issue
Block a user