Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ with this machine's shell settings — the same keys as the checked-in
|
|||||||
| `adminPin` | The admin PIN, as a scrypt hash. **Set it in the admin page's Admin PIN section**, not by hand — 6 digits, and only the hash is stored. Absent means admin is open to anyone on the network. |
|
| `adminPin` | The admin PIN, as a scrypt hash. **Set it in the admin page's Admin PIN section**, not by hand — 6 digits, and only the hash is stored. Absent means admin is open to anyone on the network. |
|
||||||
| `adminPasscode` | An older free-text passcode. Still honoured, so an existing install keeps working; new installs should use `adminPin`. |
|
| `adminPasscode` | An older free-text passcode. Still honoured, so an existing install keeps working; new installs should use `adminPin`. |
|
||||||
| `forge` | Where updates and module installs are fetched from. Absent or `"github"` uses GitHub; a Gitea or Forgejo base URL such as `"http://10.3.8.90:3000"` uses a server of your own. Set it per machine in the data directory, alongside `repo`. |
|
| `forge` | Where updates and module installs are fetched from. Absent or `"github"` uses GitHub; a Gitea or Forgejo base URL such as `"http://10.3.8.90:3000"` uses a server of your own. Set it per machine in the data directory, alongside `repo`. |
|
||||||
| `theme` | The dashboard palette: `booth` (default), `harbor`, `graphite`, `ember`, `daylight`, `midnight`, `moss`, `plum`, `sepia`, `blackout`, `paper` or `iron`. The admin page's **Theme** section writes this one — see [Themes](#themes). |
|
| `theme` | The dashboard palette: `booth` (default), `harbor`, `graphite`, `ember` or `daylight`. The admin page's **Theme** section writes this one — see [Themes](#themes). |
|
||||||
|
|
||||||
**Locked out?** Delete `adminPin` from the data directory's `proddash.json`
|
**Locked out?** Delete `adminPin` from the data directory's `proddash.json`
|
||||||
and restart, or start the server with `PRODDASH_PASSCODE` set — that passcode
|
and restart, or start the server with `PRODDASH_PASSCODE` set — that passcode
|
||||||
@@ -140,26 +140,12 @@ ignored from then on.
|
|||||||
|
|
||||||
The whole dashboard — every tile, on every browser — draws with one palette,
|
The whole dashboard — every tile, on every browser — draws with one palette,
|
||||||
chosen in the admin page's **Theme** section and switched live: pick a swatch
|
chosen in the admin page's **Theme** section and switched live: pick a swatch
|
||||||
and open dashboards change without a reload. The section starts folded —
|
and open dashboards change without a reload. Five presets ship: **Booth**
|
||||||
a building picks its palette once — and holds twelve presets:
|
(the default dark, mint accent), **Harbor** (deep navy, cyan), **Graphite**
|
||||||
|
(neutral charcoal, electric blue), **Ember** (warm broadcast black, orange)
|
||||||
| | |
|
and **Daylight**, a light palette for a front-of-house desk in daytime. Each
|
||||||
|---|---|
|
keeps the status colours apart — the accent is never near the danger red or
|
||||||
| **Booth** | the default dark, mint accent |
|
the warning amber — and muted text readable in a dark booth. The choice is
|
||||||
| **Harbor** | deep navy, cyan |
|
|
||||||
| **Graphite** | neutral charcoal, electric blue |
|
|
||||||
| **Ember** | warm broadcast black, orange |
|
|
||||||
| **Daylight** | a cool light palette, for a front-of-house desk in daytime |
|
|
||||||
| **Midnight** | indigo night, violet |
|
|
||||||
| **Moss** | forest floor, lime |
|
|
||||||
| **Plum** | aubergine, purple |
|
|
||||||
| **Sepia** | warm brown room, cool teal — Ember's arrangement reversed |
|
|
||||||
| **Blackout** | true black, cyan, for a dark room where any glow carries |
|
|
||||||
| **Paper** | the warm light one, indigo accent |
|
|
||||||
| **Iron** | no colour of its own: a near-white accent, so the only hues on screen are a module's own and the red and amber that mean something |
|
|
||||||
|
|
||||||
Each keeps the status colours apart — the accent is never near the danger red
|
|
||||||
or the warning amber — and muted text readable in a dark booth. The choice is
|
|
||||||
stored as `theme` in the data directory's `proddash.json` (table above), so
|
stored as `theme` in the data directory's `proddash.json` (table above), so
|
||||||
it holds across restarts and updates; a browser remembers the last theme it
|
it holds across restarts and updates; a browser remembers the last theme it
|
||||||
saw and applies it before the server answers, so nothing flashes. Modules
|
saw and applies it before the server answers, so nothing flashes. Modules
|
||||||
|
|||||||
+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; }
|
||||||
|
|||||||
@@ -451,36 +451,6 @@ a.btn { text-decoration: none; }
|
|||||||
|
|
||||||
/* one swatch per preset: a miniature dashboard (page, a tile on it, the
|
/* one swatch per preset: a miniature dashboard (page, a tile on it, the
|
||||||
accent as a status dot) and the name; the current one wears the accent */
|
accent as a status dot) and the name; the current one wears the accent */
|
||||||
/* ── a collapsible admin section ─────────────────────────────────────
|
|
||||||
The pattern for any section of this page that folds. The markup is a
|
|
||||||
<details class="admin-fold"> holding the section's contents, headed by a
|
|
||||||
<summary class="admin-h1 admin-fold-title">: the title keeps the same
|
|
||||||
type as every other section heading — a <summary> would otherwise come
|
|
||||||
out at the normal weight — and the caret follows the words, so a folded
|
|
||||||
section still reads as a heading first. The whole row is the hit target. */
|
|
||||||
details.admin-fold > summary.admin-fold-title {
|
|
||||||
cursor: pointer;
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-weight: 700; /* matches h1.admin-h1; summary defaults to normal */
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
details.admin-fold > summary.admin-fold-title::-webkit-details-marker { display: none; }
|
|
||||||
/* after the title, not before it — the heading reads first, the caret says
|
|
||||||
there is more behind it */
|
|
||||||
details.admin-fold > summary.admin-fold-title::after {
|
|
||||||
content: "";
|
|
||||||
width: 0; height: 0;
|
|
||||||
border-left: 6px solid var(--muted);
|
|
||||||
border-top: 5px solid transparent;
|
|
||||||
border-bottom: 5px solid transparent;
|
|
||||||
transition: transform 0.15s;
|
|
||||||
}
|
|
||||||
details.admin-fold[open] > summary.admin-fold-title::after { transform: rotate(90deg); }
|
|
||||||
details.admin-fold > summary.admin-fold-title:hover { color: var(--accent); }
|
|
||||||
|
|
||||||
.theme-swatches {
|
.theme-swatches {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
|
||||||
|
|||||||
@@ -41,11 +41,9 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="theme-section" hidden>
|
<section id="theme-section" hidden>
|
||||||
<details class="admin-fold">
|
<h1 class="admin-h1">Theme</h1>
|
||||||
<summary class="admin-h1 admin-fold-title">Theme</summary>
|
|
||||||
<div class="theme-swatches" id="theme-swatches" role="radiogroup" aria-label="Theme"></div>
|
<div class="theme-swatches" id="theme-swatches" role="radiogroup" aria-label="Theme"></div>
|
||||||
<div class="admin-status" id="theme-status"></div>
|
<div class="admin-status" id="theme-status"></div>
|
||||||
</details>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="modules-section" hidden>
|
<section id="modules-section" hidden>
|
||||||
|
|||||||
+2
-101
@@ -941,10 +941,8 @@ body.fullscreen #grid { padding-bottom: 0; }
|
|||||||
every shell element follows, and a colour a tile is configured with (set
|
every shell element follows, and a colour a tile is configured with (set
|
||||||
on the tile itself) still wins. The :root block at the top of this file is
|
on the tile itself) still wins. The :root block at the top of this file is
|
||||||
"booth", the default; the list of ids and names lives in server.js
|
"booth", the default; the list of ids and names lives in server.js
|
||||||
(GET /api/theme). Every one is booth-grade: high contrast, the accent
|
(GET /api/theme). All four are booth-grade: high contrast, the accent
|
||||||
nowhere near the danger red or the warning amber, muted still readable.
|
nowhere near the danger red or the warning amber, muted still readable. */
|
||||||
Each sets --accent-text too when the default (a near-black green, for
|
|
||||||
booth's mint) would look wrong against its own accent. */
|
|
||||||
|
|
||||||
/* Harbor — deep navy, cyan accent */
|
/* Harbor — deep navy, cyan accent */
|
||||||
html[data-theme="harbor"] {
|
html[data-theme="harbor"] {
|
||||||
@@ -995,100 +993,3 @@ html[data-theme="daylight"] {
|
|||||||
--danger: #d1242f;
|
--danger: #d1242f;
|
||||||
--warn: #b26a00;
|
--warn: #b26a00;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Midnight — indigo night, violet accent */
|
|
||||||
html[data-theme="midnight"] {
|
|
||||||
--bg: #0a0b18;
|
|
||||||
--panel: #14162b;
|
|
||||||
--border: #272a4a;
|
|
||||||
--text: #e8e9fb;
|
|
||||||
--muted: #9a9cc8;
|
|
||||||
--accent: #9d8cff;
|
|
||||||
--accent-text: #12103a;
|
|
||||||
--danger: #ff5a72;
|
|
||||||
--warn: #f5b93a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Moss — forest floor, lime accent (warn pushed orange so the two greens
|
|
||||||
and the amber never read as one another) */
|
|
||||||
html[data-theme="moss"] {
|
|
||||||
--bg: #08120c;
|
|
||||||
--panel: #101e15;
|
|
||||||
--border: #1f3728;
|
|
||||||
--text: #e9f4ea;
|
|
||||||
--muted: #8fb09a;
|
|
||||||
--accent: #8fdb3f;
|
|
||||||
--accent-text: #15250a;
|
|
||||||
--danger: #ff5a52;
|
|
||||||
--warn: #ff9f1c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Plum — aubergine, purple accent */
|
|
||||||
html[data-theme="plum"] {
|
|
||||||
--bg: #110a17;
|
|
||||||
--panel: #1d1228;
|
|
||||||
--border: #332042;
|
|
||||||
--text: #f2e9f8;
|
|
||||||
--muted: #b195c2;
|
|
||||||
--accent: #c084fc;
|
|
||||||
--accent-text: #260a37;
|
|
||||||
--danger: #ff4d4f;
|
|
||||||
--warn: #f7b53b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sepia — warm brown room, cool teal accent: the opposite arrangement to
|
|
||||||
Ember, which is warm throughout */
|
|
||||||
html[data-theme="sepia"] {
|
|
||||||
--bg: #12100b;
|
|
||||||
--panel: #1d1913;
|
|
||||||
--border: #33291d;
|
|
||||||
--text: #f4ede0;
|
|
||||||
--muted: #b3a48b;
|
|
||||||
--accent: #5eead4;
|
|
||||||
--accent-text: #06231e;
|
|
||||||
--danger: #ff5a52;
|
|
||||||
--warn: #ffc247;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Blackout — true black for a dark room, where any glow off the screen
|
|
||||||
carries; everything else is as bright as it can be against it */
|
|
||||||
html[data-theme="blackout"] {
|
|
||||||
--bg: #000000;
|
|
||||||
--panel: #0a0a0a;
|
|
||||||
--border: #1f1f1f;
|
|
||||||
--text: #ffffff;
|
|
||||||
--muted: #9e9e9e;
|
|
||||||
--accent: #00e5ff;
|
|
||||||
--accent-text: #00212a;
|
|
||||||
--danger: #ff3b30;
|
|
||||||
--warn: #ffcc00;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Paper — the warm light one, next to Daylight's cool white. Danger and warn
|
|
||||||
darken, as they must on a light background, and the indigo accent takes
|
|
||||||
white text. */
|
|
||||||
html[data-theme="paper"] {
|
|
||||||
--bg: #f5f1e8;
|
|
||||||
--panel: #fffdf8;
|
|
||||||
--border: #ddd5c4;
|
|
||||||
--text: #1c1a15;
|
|
||||||
--muted: #6b6455;
|
|
||||||
--accent: #4f46e5;
|
|
||||||
--accent-text: #ffffff;
|
|
||||||
--danger: #c8102e;
|
|
||||||
--warn: #9a6700;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Iron — no colour of its own: the accent is near-white, so the only hues on
|
|
||||||
screen are a module's own and the red/amber that mean something */
|
|
||||||
html[data-theme="iron"] {
|
|
||||||
--bg: #0c0c0d;
|
|
||||||
--panel: #161617;
|
|
||||||
--border: #2b2b2d;
|
|
||||||
--text: #f2f2f3;
|
|
||||||
--muted: #9a9a9d;
|
|
||||||
--accent: #d8d8dc;
|
|
||||||
--accent-text: #131314;
|
|
||||||
--danger: #ff4d4f;
|
|
||||||
--warn: #ffb224;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -712,13 +712,6 @@ const THEMES = [
|
|||||||
{ id: 'graphite', name: 'Graphite', colors: { bg: '#0f1012', panel: '#17191d', accent: '#6cb4ff' } },
|
{ id: 'graphite', name: 'Graphite', colors: { bg: '#0f1012', panel: '#17191d', accent: '#6cb4ff' } },
|
||||||
{ id: 'ember', name: 'Ember', colors: { bg: '#120c0a', panel: '#1d1411', accent: '#ff8f3a' } },
|
{ id: 'ember', name: 'Ember', colors: { bg: '#120c0a', panel: '#1d1411', accent: '#ff8f3a' } },
|
||||||
{ id: 'daylight', name: 'Daylight', colors: { bg: '#eef1f5', panel: '#ffffff', accent: '#0b8a5f' } },
|
{ id: 'daylight', name: 'Daylight', colors: { bg: '#eef1f5', panel: '#ffffff', accent: '#0b8a5f' } },
|
||||||
{ id: 'midnight', name: 'Midnight', colors: { bg: '#0a0b18', panel: '#14162b', accent: '#9d8cff' } },
|
|
||||||
{ id: 'moss', name: 'Moss', colors: { bg: '#08120c', panel: '#101e15', accent: '#8fdb3f' } },
|
|
||||||
{ id: 'plum', name: 'Plum', colors: { bg: '#110a17', panel: '#1d1228', accent: '#c084fc' } },
|
|
||||||
{ id: 'sepia', name: 'Sepia', colors: { bg: '#12100b', panel: '#1d1913', accent: '#5eead4' } },
|
|
||||||
{ id: 'blackout', name: 'Blackout', colors: { bg: '#000000', panel: '#0a0a0a', accent: '#00e5ff' } },
|
|
||||||
{ id: 'paper', name: 'Paper', colors: { bg: '#f5f1e8', panel: '#fffdf8', accent: '#4f46e5' } },
|
|
||||||
{ id: 'iron', name: 'Iron', colors: { bg: '#0c0c0d', panel: '#161617', accent: '#d8d8dc' } },
|
|
||||||
];
|
];
|
||||||
const DEFAULT_THEME = THEMES[0].id;
|
const DEFAULT_THEME = THEMES[0].id;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user