Shell: settings window sized to its content, with a slim grab bar
No title and no ✕ — the window sits beside its tile and closes on a click anywhere else or Escape — and it is as wide as its fields want, so a couple of switches make a small window. Help lines wrap to the fields' width instead of widening it. Guide: one control per action — whatever has a header button is not offered again in the gear. ProdCom 1.2.2 drops its timestamps and newest-first gear switches (both are header buttons). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
5dd1b734aa
commit
20d4e0f524
+10
-8
@@ -184,9 +184,10 @@ explanation there.
|
||||
popover (`collapsed: true | false`, `columns`, `help`, `toggle`). That is how
|
||||
a module with a long show/hide checklist keeps the popover usable — see
|
||||
`pco-plan`. Display choices belong there, per tile; admin config is for
|
||||
connections. The popover itself is a small window beside the tile (moved by
|
||||
its bar, closed by clicking away) so a tile never has to be big enough to
|
||||
hold its own settings, and a change can be watched as it lands.
|
||||
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
|
||||
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.
|
||||
|
||||
A picker entry (see **Presenting multiple tiles**) may carry its own
|
||||
`instanceSchema` and `instanceGroups`: tiles added from that entry get those
|
||||
@@ -350,11 +351,12 @@ Everything you add is removed automatically when your instance stops, so a
|
||||
remount never duplicates controls. Keep them compact (icon buttons, short
|
||||
labels): the title bar is 32 px tall and shared with the tile's name.
|
||||
|
||||
**Text size lives here, not in the gear popover.** A module whose text can
|
||||
be sized offers A− / A+ header buttons that persist through
|
||||
`saveInstanceSettings({ textSize })` and reads the value back with a sane
|
||||
fallback; it does not also expose a "Text size" number field in
|
||||
`instanceSchema` — one control per setting.
|
||||
**One control per action.** Whatever has a dedicated header button is not
|
||||
offered again in the gear popover: text size is A− / A+ up here (persisted
|
||||
through `saveInstanceSettings({ textSize })`, read back with a sane
|
||||
fallback), never a "Text size" field in `instanceSchema`; a timestamps 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.
|
||||
|
||||
One more thing to know: the user can hide the whole title bar with the
|
||||
small notch on its bottom edge (a clean-view mode for wall displays). Your
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
{
|
||||
"id": "prodcom-transcript",
|
||||
"name": "ProdCom",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"proddash": ">=1.1.0",
|
||||
"description": "Live ProdCom transcript stream",
|
||||
"permissions": [
|
||||
{ "kind": "local-network", "reason": "Reaching the ProdCom API on this network" }
|
||||
{
|
||||
"kind": "local-network",
|
||||
"reason": "Reaching the ProdCom API on this network"
|
||||
}
|
||||
],
|
||||
"client": "client.js",
|
||||
"server": "server.js",
|
||||
"style": "style.css",
|
||||
"minSize": { "w": 3, "h": 2 },
|
||||
"defaultSize": { "w": 4, "h": 4 },
|
||||
"minSize": {
|
||||
"w": 3,
|
||||
"h": 2
|
||||
},
|
||||
"defaultSize": {
|
||||
"w": 4,
|
||||
"h": 4
|
||||
},
|
||||
"configSchema": {
|
||||
"prodcom": {
|
||||
"type": "endpoint",
|
||||
"label": "ProdCom server",
|
||||
"default": { "host": "10.3.11.152", "port": 24480 }
|
||||
"default": {
|
||||
"host": "10.3.11.152",
|
||||
"port": 24480
|
||||
}
|
||||
},
|
||||
"apiKey": {
|
||||
"type": "password",
|
||||
@@ -29,16 +41,6 @@
|
||||
"type": "boolean",
|
||||
"label": "Show channel icons",
|
||||
"default": true
|
||||
},
|
||||
"newestFirst": {
|
||||
"type": "boolean",
|
||||
"label": "Newest first (transcript reads bottom-up)",
|
||||
"default": false
|
||||
},
|
||||
"showTimes": {
|
||||
"type": "boolean",
|
||||
"label": "Show timestamps",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-13
@@ -1157,20 +1157,12 @@ function toggleSettingsPopover(tile, el) {
|
||||
pop.setAttribute('role', 'dialog');
|
||||
pop.setAttribute('aria-label', `${tile.title || man.name} settings`);
|
||||
|
||||
// A bar to move it by, and the one control every window has.
|
||||
// A slim bar to move it by — nothing else. No title (the window sits beside
|
||||
// the tile it belongs to) and no close button: a click anywhere else, or
|
||||
// Escape, closes it.
|
||||
const bar = document.createElement('div');
|
||||
bar.className = 'tile-settings-bar';
|
||||
const barTitle = document.createElement('span');
|
||||
barTitle.className = 'tile-settings-title';
|
||||
barTitle.textContent = tile.title || man.name;
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.type = 'button';
|
||||
closeBtn.className = 'tile-settings-close';
|
||||
closeBtn.title = 'Close';
|
||||
closeBtn.setAttribute('aria-label', 'Close settings');
|
||||
closeBtn.textContent = '✕';
|
||||
closeBtn.addEventListener('click', () => closeSettingsPopovers());
|
||||
bar.append(barTitle, closeBtn);
|
||||
bar.title = 'Drag to move';
|
||||
const body = document.createElement('div');
|
||||
body.className = 'tile-settings-body';
|
||||
pop.append(bar, body);
|
||||
@@ -1486,7 +1478,7 @@ function toggleSettingsPopover(tile, el) {
|
||||
|
||||
// Moved by its bar.
|
||||
bar.addEventListener('pointerdown', (e) => {
|
||||
if (e.target === closeBtn || (e.button !== 0 && e.pointerType === 'mouse')) return;
|
||||
if (e.button !== 0 && e.pointerType === 'mouse') return;
|
||||
e.preventDefault();
|
||||
bar.setPointerCapture(e.pointerId);
|
||||
const dx = e.clientX - pop.offsetLeft;
|
||||
|
||||
+22
-38
@@ -542,7 +542,10 @@ body.fullscreen #grid { padding-bottom: 0; }
|
||||
z-index: 80;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 260px;
|
||||
/* as wide as its content wants and no wider: a couple of switches make a
|
||||
small window, a three-column checklist a wide one */
|
||||
width: max-content;
|
||||
min-width: 150px;
|
||||
max-width: min(560px, calc(100vw - 16px));
|
||||
max-height: calc(100vh - 16px);
|
||||
overflow: hidden;
|
||||
@@ -551,12 +554,13 @@ body.fullscreen #grid { padding-bottom: 0; }
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
/* the grab bar: a slim strip with a grip mark, nothing to read or press */
|
||||
.tile-settings-bar {
|
||||
flex: none;
|
||||
height: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 6px 6px 12px;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-bottom: 1px solid var(--border);
|
||||
cursor: grab;
|
||||
@@ -564,36 +568,28 @@ body.fullscreen #grid { padding-bottom: 0; }
|
||||
-webkit-user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.tile-settings-bar::before {
|
||||
content: "";
|
||||
width: 34px;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
background: var(--muted);
|
||||
opacity: 0.55;
|
||||
transition: opacity 0.12s;
|
||||
}
|
||||
.tile-settings-bar:hover::before { opacity: 0.9; }
|
||||
.tile-settings-bar:active { cursor: grabbing; }
|
||||
.tile-settings-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.tile-settings-close {
|
||||
flex: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tile-settings-close:hover { background: rgba(255, 255, 255, 0.08); color: var(--text); }
|
||||
.tile-settings-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
/* Help lines never widen the window: they take no room of their own when the
|
||||
content is measured, then wrap to whatever width the fields settled on. */
|
||||
.tile-settings .field-help,
|
||||
.tile-settings .field-group-help,
|
||||
.tile-settings .multiselect-empty { width: 0; min-width: 100%; }
|
||||
/* a group's on/off sits in its title row; the body dims while it is off */
|
||||
.tile-settings .field-group-title { display: flex; align-items: center; gap: 8px; }
|
||||
.tile-settings .field-group-name { flex: 1; min-width: 0; }
|
||||
@@ -633,12 +629,6 @@ body.fullscreen #grid { padding-bottom: 0; }
|
||||
.tile-settings .template-var:hover { border-color: var(--accent); }
|
||||
.tile-settings .template-var:active { cursor: grabbing; }
|
||||
|
||||
/* A popover with grouped settings needs room for its columns; it still
|
||||
never grows past the tile (max-width above). */
|
||||
.tile-settings.has-groups {
|
||||
min-width: min(540px, calc(100vw - 16px));
|
||||
container-type: inline-size;
|
||||
}
|
||||
.tile-settings .field-group {
|
||||
margin: 0 0 10px;
|
||||
padding: 8px 10px;
|
||||
@@ -688,12 +678,6 @@ body.fullscreen #grid { padding-bottom: 0; }
|
||||
many columns the group lays its other fields out in */
|
||||
.tile-settings .field-group-body.is-columns .multiselect-field,
|
||||
.tile-settings .field-group-body.is-columns .template-field { grid-column: 1 / -1; }
|
||||
@container (max-width: 420px) {
|
||||
.tile-settings .field-group-body.is-columns { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
@container (max-width: 260px) {
|
||||
.tile-settings .field-group-body.is-columns { grid-template-columns: 1fr; }
|
||||
}
|
||||
.tile-settings .field-group-help,
|
||||
.tile-settings .field-help {
|
||||
display: block;
|
||||
|
||||
Reference in New Issue
Block a user