generated from Labyricorn/labyricorn-project-template
Post Step 4 Completion
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Aquarium — XZBT Exhibit Contract 5.2 adapter.
|
||||
*
|
||||
* This file is the only place in the exhibit that knows the contract exists.
|
||||
* It does three things and nothing else:
|
||||
*
|
||||
* 1. declares the public target catalog against the exhibit's real model
|
||||
* 2. binds each target to a real exhibit service (setter / reader / action)
|
||||
* 3. routes the native UI through the canonical mutation path
|
||||
*
|
||||
* It holds no state of its own. Every value it reports is read live from the
|
||||
* exhibit, so there is no shadow copy that can drift (Authoring Guide §W).
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var Core = window.XZBTContractCore;
|
||||
|
||||
var IDENTITY = {
|
||||
product: 'Aquarium',
|
||||
version: '1.0.0',
|
||||
build: 'reference-exhibit'
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Public target catalog.
|
||||
*
|
||||
* IDs are canonical and domain-shaped. They are not DOM ids, CSS
|
||||
* selectors, or widget names (Authoring Guide §W).
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
var DESCRIPTORS = [
|
||||
{
|
||||
id: 'tank.species',
|
||||
kind: 'selection',
|
||||
label: 'Species',
|
||||
description: 'The species currently stocked in the tank.',
|
||||
options: [
|
||||
{ value: 'clownfish', label: 'Clownfish' },
|
||||
{ value: 'blue-tang', label: 'Blue Tang' },
|
||||
{ value: 'neon-tetra', label: 'Neon Tetra' },
|
||||
{ value: 'angelfish', label: 'Angelfish' },
|
||||
{ value: 'guppy', label: 'Guppy' }
|
||||
],
|
||||
readable: true, writable: true, restorable: true,
|
||||
category: 'tank', requires: []
|
||||
},
|
||||
{
|
||||
id: 'tank.population',
|
||||
kind: 'range',
|
||||
label: 'Population',
|
||||
description: 'Number of fish in the tank.',
|
||||
min: 1, max: 40, step: 1, unit: 'fish',
|
||||
readable: true, writable: true, restorable: true,
|
||||
category: 'tank', requires: []
|
||||
},
|
||||
{
|
||||
id: 'tank.lighting',
|
||||
kind: 'selection',
|
||||
label: 'Lighting',
|
||||
description: 'Lighting programme for the tank.',
|
||||
options: [
|
||||
{ value: 'daylight', label: 'Daylight' },
|
||||
{ value: 'twilight', label: 'Twilight' },
|
||||
{ value: 'moonlight', label: 'Moonlight' },
|
||||
{ value: 'blackwater', label: 'Blackwater' }
|
||||
],
|
||||
readable: true, writable: true, restorable: true,
|
||||
category: 'tank', requires: []
|
||||
},
|
||||
{
|
||||
id: 'tank.current',
|
||||
kind: 'range',
|
||||
label: 'Water current',
|
||||
description: 'Strength of the circulation pump, 0 (still) to 1 (strong).',
|
||||
min: 0, max: 1, step: 0.01,
|
||||
readable: true, writable: true, restorable: true,
|
||||
category: 'tank', requires: []
|
||||
},
|
||||
{
|
||||
id: 'tank.temperature',
|
||||
kind: 'range',
|
||||
label: 'Temperature',
|
||||
description: 'Heater setting, 0 (18 C) to 1 (28 C).',
|
||||
min: 0, max: 1, step: 0.01,
|
||||
readable: true, writable: true, restorable: true,
|
||||
category: 'tank', requires: []
|
||||
},
|
||||
{
|
||||
id: 'tank.paused',
|
||||
kind: 'state',
|
||||
valueType: 'boolean',
|
||||
label: 'Paused',
|
||||
description: 'Whether the simulation is frozen.',
|
||||
readable: true, writable: true, restorable: true,
|
||||
category: 'tank', requires: []
|
||||
},
|
||||
{
|
||||
id: 'action.feed',
|
||||
kind: 'impulse',
|
||||
label: 'Feed',
|
||||
description: 'Scatter food across the surface.',
|
||||
readable: false, writable: false, restorable: false,
|
||||
category: 'action', requires: []
|
||||
},
|
||||
{
|
||||
id: 'action.startle',
|
||||
kind: 'impulse',
|
||||
label: 'Startle',
|
||||
description: 'Send a pressure wave through the tank.',
|
||||
readable: false, writable: false, restorable: false,
|
||||
category: 'action', requires: []
|
||||
},
|
||||
{
|
||||
id: 'action.clean-glass',
|
||||
kind: 'impulse',
|
||||
label: 'Clean glass',
|
||||
description: 'Run the glass-cleaning cycle.',
|
||||
readable: false, writable: false, restorable: false,
|
||||
category: 'action', requires: []
|
||||
},
|
||||
{
|
||||
id: 'telemetry.fish-count',
|
||||
kind: 'state',
|
||||
valueType: 'number',
|
||||
label: 'Fish count',
|
||||
description: 'Live number of fish currently rendered.',
|
||||
readable: true, writable: false, restorable: false,
|
||||
category: 'telemetry', requires: []
|
||||
},
|
||||
{
|
||||
id: 'telemetry.food-level',
|
||||
kind: 'state',
|
||||
valueType: 'number',
|
||||
label: 'Food level',
|
||||
description: 'Remaining food in the water column, 0 to 1.',
|
||||
readable: true, writable: false, restorable: false,
|
||||
category: 'telemetry', requires: []
|
||||
},
|
||||
{
|
||||
id: 'telemetry.water-temp',
|
||||
kind: 'state',
|
||||
valueType: 'number',
|
||||
unit: 'C',
|
||||
label: 'Water temperature',
|
||||
description: 'Actual water temperature in degrees Celsius.',
|
||||
readable: true, writable: false, restorable: false,
|
||||
category: 'telemetry', requires: []
|
||||
}
|
||||
];
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Wiring
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
function createAquariumContract(exhibit, ui) {
|
||||
var catalog = new Core.Catalog(DESCRIPTORS);
|
||||
|
||||
/* The exhibit has no capability that can fail: it is pure canvas and
|
||||
* needs no device, no network, and no permission. Reporting `ready`
|
||||
* once is honest; inventing loading/error transitions would not be
|
||||
* (Authoring Guide §M). */
|
||||
var capabilities = new Core.CapabilityRegistry();
|
||||
capabilities.declare('render', 'ready');
|
||||
|
||||
var setters = {
|
||||
'tank.species': function (v) { return exhibit.setSpecies(v); },
|
||||
'tank.population': function (v) { return exhibit.setPopulation(v); },
|
||||
'tank.lighting': function (v) { return exhibit.setLighting(v); },
|
||||
'tank.current': function (v) { return exhibit.setCurrent(v); },
|
||||
'tank.temperature': function (v) { return exhibit.setTemperature(v); },
|
||||
'tank.paused': function (v) { return exhibit.setPaused(v); }
|
||||
};
|
||||
|
||||
var readers = {};
|
||||
var ids = catalog.ids();
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
(function (id) {
|
||||
readers[id] = function () { return exhibit.read(id); };
|
||||
})(ids[i]);
|
||||
}
|
||||
|
||||
var actions = {
|
||||
'action.feed': function () { return exhibit.feed(); },
|
||||
'action.startle': function () { return exhibit.startle(); },
|
||||
'action.clean-glass': function () { return exhibit.cleanGlass(); }
|
||||
};
|
||||
|
||||
var core = new Core.ContractCore({
|
||||
identity: IDENTITY,
|
||||
catalog: catalog,
|
||||
capabilities: capabilities,
|
||||
setters: setters,
|
||||
readers: readers,
|
||||
actions: actions
|
||||
});
|
||||
|
||||
/* Route the native UI through the canonical path. From here on, a
|
||||
* slider drag and a host `set` are the same transaction, and both are
|
||||
* reported with the same source semantics (Authoring Guide §H). */
|
||||
ui.dispatchOverride = function (targetId, value) {
|
||||
core.applyMutation(targetId, value, 'ui');
|
||||
ui.sync();
|
||||
};
|
||||
ui.invokeOverride = function (targetId) {
|
||||
core.invokeAction(targetId, {}, 'ui');
|
||||
ui.sync();
|
||||
};
|
||||
|
||||
/* Keep the controls honest when the exhibit changes for any reason. */
|
||||
exhibit.onChange = function () { ui.sync(); };
|
||||
|
||||
return core;
|
||||
}
|
||||
|
||||
window.AquariumContract = { create: createAquariumContract, IDENTITY: IDENTITY };
|
||||
})();
|
||||
Reference in New Issue
Block a user