Init WebCG

This commit is contained in:
2026-08-12 14:21:28 +09:00
commit 5c01660f97
170 changed files with 46248 additions and 0 deletions
+616
View File
@@ -0,0 +1,616 @@
<!DOCTYPE html>
<!-- DO NOT MODIFY THIS FILE -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -->
<!-- SPX GRAPHICS CONTROLLER -->
<!-- ¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨ -->
<!-- (c) 2020- SPX Graphics -->
<!-- http://spxgraphics.com -->
<!-- -->
<!-- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -->
<!--
Scripts, no frameworks.
"Some extract of vanilla, enough to feed Godzilla."
-->
<script src="./js/socket.io.js"></script>
<script src="/js/lib/dotlottie-player.js"></script>
<script src="/js/spx_rendererUtils.js" type="module"></script> <!-- added in 1.3.3 RC-->
<script src="/js/ograf_functions.js"></script>
<script src="/js/spx_gc.js"></script>
<title>SPX Renderer</title>
<style>
* {
box-sizing: border-box;
overflow: hidden;
margin: 0;
padding: 0px;
}
body, html {
margin: 0;
padding: 0px;
background-color: transparent;
background-color: rgba(0,0,0,0);
color: black;
font-size: 3em;
}
#root {
padding: 0px;
/* MAIN RENDERER size set by SPX to {{width}}x{{height}} */
width: {{width}};
height: {{height}};
position: relative;
border: 0px dashed red;
background-size: 100% 100%;
background-color: rgba(0,0,0,0);
}
#overlays {
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 200;
position: absolute;
border: 0px solid red;
display: flex;
justify-content: center;
align-items: center;
}
.frame {
margin: 0;
width: 100%;
height: 100%;
padding: 0px;
position: absolute;
border: 0px solid red;
}
.ografRenderTarget > iframe,
.ografRenderTarget {
margin: 0;
width: 100%;
height: 100%;
padding: 0px;
overflow: hidden;
position: absolute;
border: 0px dashed green;
}
.debugMessage{
top: 5%;
left: 10%;
opacity: 0.0;
z-index: 500;
font-size: 2em;
font-weight: bold;
position: absolute;
padding: 0.1em 0.5em;
display: inline-block;
color: rgb(3, 42, 3);
transform: rotate(1deg);
border: 1px solid green;
background-color: rgba(205, 255, 218, 0.85);
font-family: 'Courier New', Courier, monospace;
}
#cursorHider {
z-index: 200;
background-color: rgba(0,0,0,0.0);
cursor: none;
}
#wm {
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 200;
position: absolute;
border: 0px dashed red;
background-color: rgba(0,0,0,0);
}
@keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<script>
// Renderer info to console
console.log('RENDERER: ' + navigator?.userAgent);
// Renderer info to console
console.log('RENDERER: ' + navigator?.userAgent);
let incomingData; // a placeholder for templateData
let WMtimer = null;
let startTime = new Date().getTime();
function setTarget(LayerNro, projectFormat) {
if (projectFormat === "OGRAF") {
return 'div' + LayerNro;
} else {
return 'layer' + LayerNro;
}
}
function allowLayerControl(LayerNro) {
let playMode = incomingData.playmode || 'notFoundSoMustBeProgramCommand' ;
if ( isPreviewMode() && playMode != 'preview') {
return false;
}
if ( !isPreviewMode() && playMode == 'preview') {
return false;
}
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const limitLayers = urlParams.get('layers');
LayerNro = Math.min(parseInt(LayerNro), 5);
if (limitLayers) {
let allowedLayers = JSON.parse(limitLayers);
if ( allowedLayers.includes(LayerNro) ) {
return true;
} else {
console.log('Layer ' + LayerNro + ' blocked, allows ', allowedLayers);
return false;
}
} else {
return true
}
}
function validateLayerNro(LayerNro) {
if (!allowLayerControl(LayerNro)) { return false }
if ( isPreviewMode() ) {
return 1
} else {
// console.log('Validating LayerNro ' + LayerNro); // debug
return Math.min(parseInt(LayerNro),5);
}
}
function isPreviewMode() {
const url = new URL(window.location.href);
let previewFlag = url.searchParams.get('preview') || '';
let previewPage = url.pathname || '';
if ( previewFlag == 'true' || previewPage.includes('preview') ) {
return true
} else {
return false
}
}
function isLocalRenderer() {
if (this.name == 'LOCAL') {
return true
} else {
return false
}
}
function clearLayer(LayerNro){
let ValidatedLayerNro = validateLayerNro(LayerNro);
// v1.1.4 allow clearing of all layers (even restricted ones)
if (!ValidatedLayerNro) {
ValidatedLayerNro = LayerNro
}
populateLayer(ValidatedLayerNro,'',true); // clear layer forcefully
}
function echo(msg) {
document.getElementById('debugMessage').innerText=msg;
}
function spxWM(mode=0,ref=0, minMins=15, maxMins=30) {
clearTimeout(WMtimer);
WMtimer = null;
let MODES = ['init','wait', 'play','stop', 'force'];
const player = document.getElementById('wm');
let file = '/img/json/wm-' + ref + '.json';
let fadeOutAfter = 8000; // Anim duration
switch (MODES[mode]) {
case 'init':
console.log('0: spxWM init');
player.style.opacity=0;
player.load(file);
spxWM(1,ref, minMins, maxMins);
return;
break;
case 'wait':
// DEBUG #######
// minMins = 0.2
// maxMins = 0.5
// #############
let minMS = (parseFloat(minMins) * 60 * 1000); // default 15 min
let maxMS = (parseFloat(maxMins) * 60 * 1000); // default 30 min
minMS = Math.max(minMS, fadeOutAfter+500)
let rnd = Math.floor(Math.random() * maxMS) + minMS;
console.log('1: spxWM delay sec', (rnd/1000));
WMtimer = setTimeout(function () {
spxWM(2,ref, minMins, maxMins);
}, rnd);
return;
break;
case 'play':
console.log('2: spxWM play');
player.style.opacity=1;
player.seek(0);
player.play();
spxWM(1,ref, minMins, maxMins);
setTimeout(function () {
player.style.opacity=0;
}, fadeOutAfter);
break;
case 'stop':
console.log('3: spxWM stop');
clearTimeout(WMtimer);
WMtimer = null;
player.stop();
player.style.opacity=0;
break;
case 'force':
console.log('4: force play wm');
player.load(file);
setTimeout(function () {
player.seek(0);
player.style.opacity=1;
player.play();
}, 200);
setTimeout(function () {
player.style.opacity=0;
if (WMtimer) {
clearTimeout(WMtimer);
spxWM(1,ref);
} else {
clearTimeout(WMtimer);
WMtimer = null;
}
}, fadeOutAfter*1000);
break;
}
}
function protocolCheck() {
// check if we are http or file.
let protocol = location.protocol;
if (protocol != "http:" && protocol != "https:") {
alert('Please use this via server! Functions and CORS may not work when used as a file. (Current protocol: ' + protocol + ')');
}
}
function remoteFunction(LayerNro, functionName, arg) {
LayerNro = validateLayerNro(LayerNro);
if (!LayerNro) { return }
let iframe = 'layer' + LayerNro;
document.getElementById(iframe).contentWindow.echo(arg);
}
function populateLayer(LayerNro,TemplateFileRef='', forced=false) {
let projectFormat = incomingData.projectFormat || 'SPX'; // default value
if (!forced) {
LayerNro = validateLayerNro(LayerNro);
if (!LayerNro) { return }
}
let renderTarget = setTarget(LayerNro, projectFormat);
let templa = '';
if (TemplateFileRef!='') {
templa = TemplateRoot + TemplateFileRef;
} else {
templa = TemplateRoot + 'empty.html';
}
templa = templa.replace(/\/\//g, "/");
let urlParams = new URLSearchParams(window.location.search);
let customParams = '';
for(let param of urlParams) {
customParams += '&' + param[0] + '=' + param[1];
}
let ranNro = (new Date()).getTime() + Math.floor(Math.random() * 1000000);
document.getElementById(renderTarget).src=templa + "?random=" + ranNro + customParams;
}
function playLayer(LayerNro,fieldData, projectFormat = 'SPX', incomingData) {
LayerNro = validateLayerNro(LayerNro);
if (!LayerNro) { return }
let renderTarget = setTarget(LayerNro, projectFormat)
let formattedJSON = formatJSONAndSetDomFields(fieldData, renderTarget);
let manifestPath = incomingData.relpath;
if (projectFormat === "OGRAF") {
loadTheGraphic("/templates" + manifestPath, renderTarget, incomingData);
} else {
document.getElementById(renderTarget).contentWindow?.update(formattedJSON);
document.getElementById(renderTarget).contentWindow?.play();
}
}
async function updateLayer(LayerNro,fieldData, projectFormat = "SPX") {
LayerNro = validateLayerNro(LayerNro);
if (!LayerNro) { return }
let renderTarget = setTarget(LayerNro, projectFormat)
let formattedJSON = formatJSONAndSetDomFields(fieldData, renderTarget);
if (projectFormat === "OGRAF") {
updateItem()
} else {
document.getElementById(renderTarget).contentWindow?.update(formattedJSON);
}
}
function formatJSONAndSetDomFields(fieldData, iframeID) {
let formattedJsonOut = {};
if (fieldData) {
var keys = [];
for (var k in fieldData) keys.push(k);
fieldData.forEach((item,index) => {
let KEY = Object.keys(item)[0];
let VAL = fieldData[index][Object.keys(item)[0]]
formattedJsonOut[KEY]=VAL;
});
return JSON.stringify(formattedJsonOut)
}
}
function stopLayer(LayerNro, projectFormat = "SPX") {
if (!allowLayerControl(LayerNro)) return; // check layer param
LayerNro = validateLayerNro(LayerNro);
let renderTarget = setTarget(LayerNro, projectFormat)
if (projectFormat === "OGRAF") {
graphicInstances[renderTarget]?.stopAction({})
} else {
document.getElementById(renderTarget).contentWindow?.stop();
}
}
var url_string = window.location.href;
var url = new URL(url_string);
let TemplateRoot;
var GCserver = url.searchParams.get("gc") || undefined;
if (GCserver==undefined) {
TemplateRoot = "/templates/";
var socket = io();
} else {
TemplateRoot = "templates/";
var socket = io(GCserver);
}
socket.on('connect', function () {
data={};
data.name = 'SPX_RENDERER'; // <---- Name of this socket connection
data.spxcmd = 'identifyClient';
socket.emit('SPXMessage2Server', data);
});
socket.on('SPXMessage2Client', function (data) {
incomingData = data;
let LayerNro;
let projectFormat = ""
let customCommand = ""
switch (data.spxcmd) {
case 'reloadRendererWithLayers':
let currURL = window.location.href;
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
let userName = urlParams.get('name') || 'NO_NAME';
let nameMatch = (userName.toUpperCase() === data?.name?.toUpperCase());
if (urlParams.get('remote')=='true' && nameMatch) {
let baseURL = currURL.split('?')[0];
let layerList = "[" + data.layers.join(',') + "]";
if (data?.layers?.length>0 ) {
window.location.href = baseURL + '?layers=' + layerList + '&remote=true&name=' + userName;
} else {
window.location.href = baseURL + '?remote=true&name=' + userName;
}
}
break;
case 'clearAllLayers':
spxWM(3);
document.getElementById('root').style.opacity=0;
document.querySelectorAll('.htmlFrame').forEach(function(frame,index) {
LayerNro = (index+1);
clearLayer(LayerNro,'');
});
document.querySelectorAll('.ografFrame').forEach(function(frame,index) {
LayerNro = (index+1);
frame.innerHTML = '';
});
setTimeout(function() {
document.getElementById('root').style.opacity=1;
}, 200);
break;
case 'clearLayer':
clearLayer(data.layerno);
break;
case 'loadTemplate':
populateLayer(data.webplayout,data.relpath);
break;
case 'forceWM':
spxWM(4);
break;
case 'playTemplate':
projectFormat = incomingData.projectFormat || 'SPX';
if (data.modify && data.modify==='autoPlayLocal' ) {
if (!isLocalRenderer()) {
return;
}
}
LayerNro = validateLayerNro(data.webplayout);
if (!LayerNro) { return }
if (projectFormat === "OGRAF") {
playLayer(LayerNro, data.fields, projectFormat, incomingData);
}else {
document.getElementById('layer' + LayerNro).onload = function() {
playLayer(LayerNro, data.fields, projectFormat, incomingData);
};
}
populateLayer(data.webplayout,data.relpath);
break;
case 'nextTemplate':
projectFormat = incomingData.projectFormat || 'SPX';
LayerNro = validateLayerNro(data.webplayout);
let renderTarget = setTarget(LayerNro, projectFormat);
if (!LayerNro) { return }
if (projectFormat === "OGRAF") {
graphicInstances[renderTarget].playAction();
} else {
document.getElementById(renderTarget).contentWindow?.next();
}
break;
case 'updateTemplate':
projectFormat = incomingData.projectFormat || 'OGRAF'; // default value
LayerNro = validateLayerNro(data.webplayout);
setTimeout(function(){ updateLayer(data.webplayout, data.fields, projectFormat); }, 10);
break;
case 'stopTemplate':
projectFormat = incomingData.projectFormat || 'SPX'; // default value
LayerNro = validateLayerNro(data.webplayout);
if (!LayerNro) { return }
stopLayer(LayerNro, projectFormat);
break;
case 'customAction':
LayerNro = validateLayerNro(data.webplayout)
graphicInstances['div' + LayerNro].customAction({id: incomingData.id})
case 'invokeFunction':
LayerNro = validateLayerNro(data.webplayout);
if (!LayerNro) { return }
try {
// Important change! --------------------------------------------------
// Starting from version 1.2.0 it is recommended to use
// "data.function" and "data.params" instead of "data.invoke"
// that was a string representation of a function call with
// parameters. Server will log all uses of the old "data.invoke"
// and will recommend to use the new "data.function" and "data.params"
// instead. The old "data.invoke" will be deprecated in the future.
// --------------------------------------------------------------------
if (data.function) {
var myFunction = data.function;
var myParamets = data.params || null;
if (typeof document.getElementById('layer' + LayerNro).contentWindow[myFunction] === "function") {
let paramPreview = '';
if (myParamets && myParamets!="undefined") {
paramPreview = 'First 30 chars of args: ' + myParamets.substring(0, 30);
} else {
paramPreview = 'No args.';
}
document.getElementById('layer' + LayerNro).contentWindow[myFunction](myParamets);
} else {
console.warn('Function "' + myFunction + '" not found on layer ' + LayerNro + '. Verify function and loaded template.');
}
let ografLayer = document.getElementById('div' + LayerNro)
let ografFrame = ografLayer.querySelector('.ografRenderTarget').querySelector('iframe')
let ografContent = ografFrame.contentWindow
if (typeof ografContent[myFunction] === "function") {
ografContent[myFunction](myParamets);
} else {
console.warn('Function "' + myFunction + '" not found on OGraf ' + LayerNro + '. Verify function and loaded graphic.');
}
} else {
if (data.invoke) {
let warning = '\DEPRECATION WARNING\n';
warning += '`data.invoke` will be deprecated in the next SPX version. Refactor your extensions to '
warning += 'use `data.function` and `data.params` instead when calling the /gc/playout API or use ';
warning += 'the public API endpoint /api/v1/invokeTemplateFunction instead.\n';
warning += 'You had a call [' + data.invoke + '].\n\n';
console.warn(warning);
let myFunction = data.invoke.split('(')[0]; // Checking if template has this function
if (typeof document.getElementById('layer' + LayerNro).contentWindow[myFunction] === "function") {
eval("document.getElementById('layer' + " + LayerNro + ").contentWindow." + data.invoke);
} else {
console.warn('Function "' + myFunction + '" not found on layer ' + LayerNro + '. Verify function and loaded template.');
}
}
}
} catch(err) {
console.warn('Error invoking "' + myFunction + '" on layer ' + LayerNro + '. Verify function and loaded template.');
}
break;
case 'setRendererBackgroundImage':
if (data.background != '' ) {
document.getElementById('root').style.backgroundImage = 'url(/' + data.background + ')';
} else {
document.getElementById('root').style.backgroundImage = '';
}
break;
default:
// console.log('Unknown SPXMessage2Client command in renderer: ' + data.spxcmd);
}
});
socket.on('SPXWebRendererMessage', function (data) {
// Handles messages coming from server to this client.
// All comms using 'SPXWebRendererMessage' as a conduit with data object and
// data.spxcmd as function identifier. Additional object values are payload.
// console.log('SPXWebRendererMessage received (but doing nothing to it)', data)
}); // socketMessage handler ended
window.addEventListener('load',protocolCheck,false);
</script>
</head>
<body>
<div id="root">
<dotlottie-player id="wm"></dotlottie-player>
{{#ifValue hideCursor "true"}}
<div id="cursorHider"></div>
{{/ifValue}}
<!-- SPX Solo does not support in-layer transitions -->
<iframe class="frame htmlFrame" allow="autoplay" id="layer1" style="z-index: 101;" src="about:blank"></iframe>
<iframe class="frame htmlFrame" allow="autoplay" id="layer2" style="z-index: 102;" src="about:blank"></iframe>
<iframe class="frame htmlFrame" allow="autoplay" id="layer3" style="z-index: 103;" src="about:blank"></iframe>
<iframe class="frame htmlFrame" allow="autoplay" id="layer4" style="z-index: 104;" src="about:blank"></iframe>
<iframe class="frame htmlFrame" allow="autoplay" id="layer5" style="z-index: 105;" src="about:blank"></iframe>
<div class="frame ografFrame" allow="autoplay" id="div1" style="z-index: 201"></div>
<div class="frame ografFrame" allow="autoplay" id="div2" style="z-index: 202"></div>
<div class="frame ografFrame" allow="autoplay" id="div3" style="z-index: 203"></div>
<div class="frame ografFrame" allow="autoplay" id="div4" style="z-index: 204"></div>
<div class="frame ografFrame" allow="autoplay" id="div5" style="z-index: 205" ></div>
</div>
<div class="debugMessage" id="debugMessage" style="opacity:0; position: absolute; z-index:1000;"></div>
</body>
</html>