generated from Labyricorn/labyricorn-project-template
Post Step 4 Completion
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Shared presentation helpers for the reference exhibits.
|
||||
*
|
||||
* Generic DOM plumbing only: element lookup, safe text writing, and the
|
||||
* announcement strip. It has no contract awareness and no domain knowledge,
|
||||
* so an exhibit can use it, ignore it, or replace it without touching the
|
||||
* contract layer.
|
||||
*
|
||||
* The one rule worth stating out loud: every piece of text that reaches the
|
||||
* DOM goes through `textContent`. Nothing in this project ever assigns
|
||||
* `innerHTML`, so a string arriving from a host or a scenario can never
|
||||
* become markup or script (Contract §20, Authoring Guide §O).
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function el(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
/** Write text safely. Always textContent, never innerHTML. */
|
||||
function setText(node, text) {
|
||||
if (!node) return;
|
||||
node.textContent = text === null || text === undefined ? '' : String(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Announcement strip. Text is data: it is truncated to the declared
|
||||
* maximum and written with textContent.
|
||||
*/
|
||||
function Announcer(options) {
|
||||
this.node = options.node;
|
||||
this.idleText = options.idleText || '';
|
||||
this.maxLength = options.maxLength || 120;
|
||||
this._timer = null;
|
||||
this.setText(this.idleText, true);
|
||||
}
|
||||
|
||||
Announcer.prototype.setText = function (text, idle) {
|
||||
if (!this.node) return;
|
||||
var value = text === null || text === undefined ? '' : String(text);
|
||||
if (value.length > this.maxLength) value = value.slice(0, this.maxLength);
|
||||
this.node.textContent = value;
|
||||
if (idle) {
|
||||
this.node.classList.add('is-idle');
|
||||
} else {
|
||||
this.node.classList.remove('is-idle');
|
||||
}
|
||||
};
|
||||
|
||||
/** Show a transient message, then fall back to the idle text. */
|
||||
Announcer.prototype.flash = function (text, ms) {
|
||||
this.setText(text, false);
|
||||
if (this._timer) clearTimeout(this._timer);
|
||||
var self = this;
|
||||
this._timer = setTimeout(function () {
|
||||
self.setText(self.idleText, true);
|
||||
}, ms || 2600);
|
||||
};
|
||||
|
||||
/** Format a number for a readout, with a fixed number of decimals. */
|
||||
function formatNumber(value, decimals) {
|
||||
if (typeof value !== 'number' || !isFinite(value)) return '--';
|
||||
return value.toFixed(decimals === undefined ? 2 : decimals);
|
||||
}
|
||||
|
||||
/** Format a 0..1 value as a whole percentage. */
|
||||
function formatPercent(value) {
|
||||
if (typeof value !== 'number' || !isFinite(value)) return '--';
|
||||
return Math.round(value * 100) + '%';
|
||||
}
|
||||
|
||||
/** Format a 0..1 value as a whole number of degrees. */
|
||||
function formatDegrees(value) {
|
||||
if (typeof value !== 'number' || !isFinite(value)) return '--';
|
||||
return Math.round(value * 360) + '\u00b0';
|
||||
}
|
||||
|
||||
/** Format a 0..1 value as a whole number of minutes. */
|
||||
function formatMinutes(value) {
|
||||
if (typeof value !== 'number' || !isFinite(value)) return '--';
|
||||
return Math.round(value * 60) + ' min';
|
||||
}
|
||||
|
||||
window.XZBTShell = {
|
||||
el: el,
|
||||
setText: setText,
|
||||
Announcer: Announcer,
|
||||
formatNumber: formatNumber,
|
||||
formatPercent: formatPercent,
|
||||
formatDegrees: formatDegrees,
|
||||
formatMinutes: formatMinutes
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user