generated from Labyricorn/labyricorn-project-template
Step 6.2 Complete — Museum Gallery reference exhibit and Contract 5.3 spec
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Museum Gallery — Artifact Display boot (non-primary surface).
|
||||
*
|
||||
* This document deliberately never loads contract-core.js, exhibit.js, or
|
||||
* contract-adapter.js. It has no way to construct an Exhibit State Core --
|
||||
* it can only attach to the one the Control Room document created, over
|
||||
* surface-bus.js. If the Control Room is not open, this degrades to a clear
|
||||
* waiting state rather than inventing its own state (Contract 5.3 Section
|
||||
* 31.8, Step 6.1 Section 7.2).
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var Shell = window.XZBTShell;
|
||||
var el = Shell.el;
|
||||
var statusEl = el('status');
|
||||
var announcer = new Shell.Announcer({ node: el('announcement'), idleText: 'Waiting for shared state.' });
|
||||
var cycleButton = el('cycle-button');
|
||||
|
||||
var ARTIFACT_ORDER = ['the-orrery', 'star-map', 'meteorite'];
|
||||
var ARTIFACT_LABELS = { 'the-orrery': 'The Orrery', 'star-map': 'Star Map', meteorite: 'Meteorite Fragment' };
|
||||
|
||||
/* This is a read-only MIRROR of authoritative state, not a second copy of
|
||||
* exhibit state: it is discarded and re-seeded from a fresh snapshot on
|
||||
* every attach, and every write attempt is a mutate() call routed back
|
||||
* through the owner's canonical mutation path -- this document never
|
||||
* writes to `latest` in response to a user action, only in response to
|
||||
* a snapshot or an event relayed from the one Core. */
|
||||
var latest = null;
|
||||
|
||||
function render() {
|
||||
if (!latest) return;
|
||||
Shell.setText(el('artifact-name'), ARTIFACT_LABELS[latest['artifact.selected']] || latest['artifact.selected']);
|
||||
Shell.setText(el('lighting-readout'), Shell.formatPercent(latest['lighting.level']));
|
||||
Shell.setText(el('rotation-readout'), latest['rotation.speed'].toFixed(1) + 'x');
|
||||
}
|
||||
|
||||
var link = window.MuseumGallerySurfaceBus.attach({
|
||||
timeoutMs: 1500,
|
||||
onSnapshot: function (snapshot) {
|
||||
latest = snapshot.values;
|
||||
Shell.setText(statusEl, 'Attached to Control Room.');
|
||||
statusEl.classList.remove('is-waiting');
|
||||
cycleButton.disabled = false;
|
||||
render();
|
||||
},
|
||||
onEvent: function (event) {
|
||||
if (!latest) return;
|
||||
if (event.type === 'state.changed' || event.type === 'selection.changed') {
|
||||
latest[event.target] = event.value;
|
||||
render();
|
||||
} else if (event.type === 'action.executed' && event.target === 'action.spotlight-flash') {
|
||||
announcer.flash('Spotlight flashed.');
|
||||
}
|
||||
},
|
||||
onTimeout: function () {
|
||||
Shell.setText(statusEl, 'Waiting for the Control Room surface to be open…');
|
||||
statusEl.classList.add('is-waiting');
|
||||
cycleButton.disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
cycleButton.addEventListener('click', function () {
|
||||
if (!latest) return;
|
||||
var idx = ARTIFACT_ORDER.indexOf(latest['artifact.selected']);
|
||||
var next = ARTIFACT_ORDER[(idx + 1) % ARTIFACT_ORDER.length];
|
||||
/* Routed to the owner's canonical mutation path -- this is the required
|
||||
* proof that a native interaction on a non-primary surface obeys the
|
||||
* same mutation/revision/source/event semantics as primary UI or a
|
||||
* contract set/invoke (Contract 5.3 Section 31.7). */
|
||||
link.mutate('set', 'artifact.selected', next);
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', function () { link.detach(); });
|
||||
window.__museumGalleryDebug = { link: link };
|
||||
})();
|
||||
Reference in New Issue
Block a user