Web changes: Split shared helpers, bootstrap logic, route groups, and upload-sync behavior out of web.js. Kept web.js focused on wiring and server startup. Fixed screen playlist reassignment so changing a screen’s playlist now triggers a refresh. Fixed single-slide playlist refresh behavior so updates do not get stuck behind the current slide. Player changes: Split websocket/runtime handling into runtime.js. Split playlist assembly and revision hashing into playlist.js. Split onboarding and player HTTP routes into dedicated modules. Split render utilities and template loading into render-helpers.js. Kept player.js mostly as startup/orchestration. Validation: Rebuilt both services with Docker Compose. Smoke-checked web and player routes after the refactor. Verified get_errors was clean on the touched modules.
124 lines
4.6 KiB
JavaScript
124 lines
4.6 KiB
JavaScript
const Handlebars = require('handlebars');
|
|
const { mediaKind, safeJsonForScript, getPlayerPageTemplate, getPlayerClientNameScript, getPlayerPageScript, getPlayerOnboardingLandingScript, getPlayerOnboardingFormScript } = require('./render-helpers');
|
|
|
|
function renderPage(template, options) {
|
|
return template({
|
|
TITLE: options.title,
|
|
BODY_CLASS: options.bodyClass || '',
|
|
BODY: new Handlebars.SafeString(options.body || ''),
|
|
SCRIPT_BLOCK: new Handlebars.SafeString(options.script || '')
|
|
});
|
|
}
|
|
|
|
function renderOnboardingLandingBody() {
|
|
return [
|
|
'<main class="onboarding-shell">',
|
|
' <section class="onboarding-card onboarding-card--landing">',
|
|
' <p class="onboarding-kicker">Pulse Signage</p>',
|
|
' <h1>Onboard this player</h1>',
|
|
' <p class="onboarding-copy">Choose an existing screen, name the client, and either scan the QR code or finish right here with a keyboard and mouse.</p>',
|
|
' <div class="onboarding-layout">',
|
|
' <div class="onboarding-qr-pane">',
|
|
' <div class="onboarding-qr-frame">',
|
|
' <img id="onboarding-qr" alt="Onboarding QR code" />',
|
|
' </div>',
|
|
' <div id="onboarding-status" class="onboarding-status">Preparing onboarding link...</div>',
|
|
' </div>',
|
|
' <form id="onboarding-local-form" class="onboarding-form onboarding-form--local">',
|
|
' <label>',
|
|
' <span>Client name</span>',
|
|
' <input name="clientName" type="text" maxlength="255" required placeholder="Lobby player" autocomplete="off" />',
|
|
' </label>',
|
|
' <label>',
|
|
' <span>Screen</span>',
|
|
' <select name="screenSlug" id="onboarding-screen-select" required>',
|
|
' <option value="">Loading screens...</option>',
|
|
' </select>',
|
|
' </label>',
|
|
' <button type="submit">Save client</button>',
|
|
' <div id="onboarding-message" class="onboarding-status"></div>',
|
|
' </form>',
|
|
' </div>',
|
|
' </section>',
|
|
'</main>'
|
|
].join('');
|
|
}
|
|
|
|
function renderOnboardingFormBody(deviceId) {
|
|
return [
|
|
'<main class="onboarding-shell">',
|
|
' <section class="onboarding-card onboarding-card--form">',
|
|
' <p class="onboarding-kicker">Pulse Signage</p>',
|
|
' <h1>Name this client</h1>',
|
|
' <p class="onboarding-copy">Pick an existing screen and give this player a friendly name that will persist after refreshes.</p>',
|
|
' <form id="onboarding-form" class="onboarding-form">',
|
|
' <label>',
|
|
' <span>Client name</span>',
|
|
' <input name="clientName" type="text" maxlength="255" required placeholder="Lobby player" />',
|
|
' </label>',
|
|
' <label>',
|
|
' <span>Screen</span>',
|
|
' <select name="screenSlug" id="onboarding-screen-select" required>',
|
|
' <option value="">Loading screens...</option>',
|
|
' </select>',
|
|
' </label>',
|
|
' <input type="hidden" name="deviceId" value="' + Handlebars.escapeExpression(deviceId || '') + '" />',
|
|
' <button type="submit">Save client</button>',
|
|
' <div id="onboarding-message" class="onboarding-status"></div>',
|
|
' </form>',
|
|
' </section>',
|
|
'</main>'
|
|
].join('');
|
|
}
|
|
|
|
|
|
function renderOnboardingLandingScript() {
|
|
return getPlayerOnboardingLandingScript()();
|
|
}
|
|
|
|
function renderOnboardingFormScript(deviceId) {
|
|
return getPlayerOnboardingFormScript()({
|
|
DEVICE_ID_JSON: new Handlebars.SafeString(JSON.stringify(deviceId || ''))
|
|
});
|
|
}
|
|
|
|
function renderPlayerPage(slug, initialData) {
|
|
const onboardingScript = getPlayerClientNameScript()();
|
|
const template = getPlayerPageTemplate();
|
|
const script = getPlayerPageScript()({
|
|
SLUG_JSON: new Handlebars.SafeString(JSON.stringify(slug)),
|
|
INITIAL_DATA_JSON: new Handlebars.SafeString(safeJsonForScript(initialData || null))
|
|
});
|
|
|
|
return renderPage(template, {
|
|
title: 'Screen ' + slug,
|
|
body: '<div id="app"><div class="empty">Loading screen...</div></div>',
|
|
script: onboardingScript + script
|
|
});
|
|
}
|
|
|
|
function renderPlayerOnboardingLandingPage() {
|
|
return renderPage(getPlayerPageTemplate(), {
|
|
title: 'Onboard player',
|
|
bodyClass: 'onboarding-page',
|
|
body: renderOnboardingLandingBody(),
|
|
script: renderOnboardingLandingScript()
|
|
});
|
|
}
|
|
|
|
function renderPlayerOnboardingFormPage(deviceId) {
|
|
return renderPage(getPlayerPageTemplate(), {
|
|
title: 'Onboard screen',
|
|
bodyClass: 'onboarding-page',
|
|
body: renderOnboardingFormBody(deviceId),
|
|
script: renderOnboardingFormScript(deviceId)
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
mediaKind,
|
|
renderPlayerPage,
|
|
renderPlayerOnboardingLandingPage,
|
|
renderPlayerOnboardingFormPage
|
|
};
|