generated from Labyricorn/labyricorn-project-template
Step 5 Complete
This commit is contained in:
@@ -1,12 +1,21 @@
|
||||
import { ExhibitHost } from './host.js';
|
||||
import { argumentSchema } from './validation.js';
|
||||
import { postMessageTransport } from './transport/post-message.js';
|
||||
import { ExhibitConnection } from './connection.js';
|
||||
|
||||
const host = new ExhibitHost();
|
||||
const byId = id => document.getElementById(id);
|
||||
const json = value => JSON.stringify(value, null, 2);
|
||||
const rows = new Map();
|
||||
let frame = null, renderedCatalog = null;
|
||||
let renderedCatalog = null;
|
||||
const connection = new ExhibitConnection({ host, base: location.href, transport: postMessageTransport, changed: render,
|
||||
createFrame() {
|
||||
const frame = document.createElement('iframe'); frame.title = 'Connected exhibit';
|
||||
// Append only after src is assigned to avoid negotiating with the blank document.
|
||||
queueMicrotask(() => { if (connection.frame === frame) byId('frame-container').append(frame); });
|
||||
return frame;
|
||||
}
|
||||
});
|
||||
function element(tag, text, parent) {
|
||||
const node = document.createElement(tag); if (text !== undefined) node.textContent = text;
|
||||
parent?.append(node); return node;
|
||||
@@ -38,8 +47,15 @@ function editor(spec, label, parent) {
|
||||
}
|
||||
function buildCatalog() {
|
||||
rows.clear(); byId('catalog').replaceChildren();
|
||||
if (!host.catalog.length) element('p', 'Connect an exhibit to discover its controls.', byId('catalog'));
|
||||
const groups = new Map();
|
||||
for (const target of host.catalog) {
|
||||
const section = element('article', undefined, byId('catalog')); section.className = 'target';
|
||||
const category = target.category || 'Other';
|
||||
if (!groups.has(category)) {
|
||||
const group = element('div', undefined, byId('catalog')); group.className = 'target-group';
|
||||
element('h3', category, group); groups.set(category, group);
|
||||
}
|
||||
const section = element('article', undefined, groups.get(category)); section.className = 'target';
|
||||
element('h3', target.label ?? target.id, section); element('code', target.id, section);
|
||||
element('p', `${target.kind} · ${target.readable ? 'readable' : 'not readable'} · ${target.writable ? 'writable' : 'not writable'}`, section);
|
||||
const current = element('output', '', section);
|
||||
@@ -85,16 +101,27 @@ function buildCatalog() {
|
||||
}
|
||||
}
|
||||
function render() {
|
||||
byId('status').textContent = `${host.status} · ${host.sync}`;
|
||||
byId('status').textContent = connection.loading ? 'Loading exhibit…' : `${host.status} · ${host.sync}`;
|
||||
byId('status').dataset.state = host.status;
|
||||
byId('identity').textContent = host.exhibit
|
||||
? `${host.exhibit.product} · ${host.exhibit.version ?? ''} · Contract ${host.contract.major}.${host.contract.minor}`
|
||||
: 'No exhibit connected. Enter a served path to begin.';
|
||||
byId('loaded-url').textContent = connection.url ? `Exhibit location: ${connection.url}` : '';
|
||||
byId('metadata').textContent = json({ sessionId: host.sessionId, contract: host.contract, exhibit: host.exhibit,
|
||||
registryRevision: host.registryRevision, stateRevision: host.stateRevision, sequence: host.sequence });
|
||||
byId('capabilities').textContent = json(host.capabilities);
|
||||
byId('capabilities').replaceChildren();
|
||||
if (!host.capabilities.length) element('p', host.sessionId ? 'No capabilities declared.' : 'Capabilities appear after discovery.', byId('capabilities'));
|
||||
for (const capability of host.capabilities) element('li', `${capability.id}: ${capability.state}`, byId('capabilities'));
|
||||
byId('state').textContent = json(Object.fromEntries(host.values));
|
||||
byId('events').textContent = json(host.eventLog);
|
||||
byId('errors').textContent = json(host.logs);
|
||||
const latest = host.logs.findLast(entry => entry.code !== 'SESSION');
|
||||
byId('latest-error').textContent = latest ? `Latest diagnostic (${latest.time}): ${latest.code}: ${latest.message}` : '';
|
||||
byId('latest-error').hidden = !latest;
|
||||
byId('target-count').textContent = `(${host.catalog.length})`;
|
||||
byId('refresh').disabled = host.status !== 'connected';
|
||||
byId('reconnect').disabled = !frame;
|
||||
byId('reconnect').disabled = !connection.url || connection.loading || host.status === 'negotiating';
|
||||
byId('disconnect').disabled = !connection.frame;
|
||||
if (host.catalog !== renderedCatalog) { renderedCatalog = host.catalog; buildCatalog(); }
|
||||
for (const row of rows.values()) {
|
||||
row.current.textContent = host.values.has(row.target.id) ? `Reported value: ${json(host.values.get(row.target.id))}` : 'No persistent value reported.';
|
||||
@@ -106,22 +133,14 @@ function render() {
|
||||
if (row.submit) row.submit.disabled = row.busy || host.status !== 'connected' || !!row.schemaError || unavailable.length > 0;
|
||||
}
|
||||
}
|
||||
async function attach() {
|
||||
if (!frame) return;
|
||||
try { await host.connect(postMessageTransport(frame)); } catch (error) { if (!error.code) host.report(error); }
|
||||
}
|
||||
byId('connection').addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const url = new URL(byId('exhibit-url').value, location.href);
|
||||
if (url.origin !== location.origin || !['http:', 'https:'].includes(url.protocol)) throw new Error('Enter a same-origin HTTP exhibit URL.');
|
||||
host.disconnect(); frame?.remove();
|
||||
frame = document.createElement('iframe'); frame.title = 'Connected exhibit';
|
||||
frame.addEventListener('load', attach); frame.src = url.href;
|
||||
byId('frame-container').append(frame); render();
|
||||
connection.load(byId('exhibit-url').value);
|
||||
} catch (error) { host.report(error); }
|
||||
});
|
||||
byId('reconnect').addEventListener('click', attach);
|
||||
byId('disconnect').addEventListener('click', () => { host.disconnect(); frame?.remove(); frame = null; render(); });
|
||||
byId('reconnect').addEventListener('click', () => connection.reconnect());
|
||||
byId('disconnect').addEventListener('click', () => connection.disconnect());
|
||||
byId('refresh').addEventListener('click', () => host.refresh().catch(error => host.report(error)));
|
||||
byId('clear-errors').addEventListener('click', () => { host.logs = []; render(); });
|
||||
host.subscribe(render); render();
|
||||
|
||||
Reference in New Issue
Block a user