Compare commits
2 Commits
9c3ce5c94e
...
7b33266bfd
| Author | SHA1 | Date | |
|---|---|---|---|
|
7b33266bfd
|
|||
|
022dca2d7f
|
@@ -20,7 +20,7 @@
|
|||||||
<![endif]-->
|
<![endif]-->
|
||||||
|
|
||||||
<div id="container" class="container redoutline">
|
<div id="container" class="container redoutline">
|
||||||
<img id="image" class="image hidden" src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.pixabay.com%2Fphoto%2F2018%2F01%2F05%2F00%2F20%2Ftest-image-3061864_1280.png&f=1&nofb=1&ipt=5f164ea7108fd967454bd94e6ad0022588b6ac82819b88cc790551694bb97522" alt="no image">
|
<img id="image" class="image hidden" src="" alt="no image">
|
||||||
<iframe id="timer" class="timer" src="/timer" frameborder="0"></iframe>
|
<iframe id="timer" class="timer" src="/timer" frameborder="0"></iframe>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
187
timeroverlaytouch/app.js
Normal file
187
timeroverlaytouch/app.js
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
/*eslint-env browser*/
|
||||||
|
/**
|
||||||
|
* This is a very minimal example for a websocket client
|
||||||
|
* You could use this as a starting point to creating your own interfaces
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Data that the user needs to provide depending on the Ontime URL
|
||||||
|
const isSecure = window.location.protocol === 'https:';
|
||||||
|
const userProvidedSocketUrl = `${isSecure ? 'wss' : 'ws'}://${window.location.host}${getStageHash()}/ws`;
|
||||||
|
let websocket = null
|
||||||
|
|
||||||
|
connectSocket();
|
||||||
|
|
||||||
|
let reconnectTimeout;
|
||||||
|
const reconnectInterval = 1000;
|
||||||
|
let reconnectAttempts = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to the websocket server
|
||||||
|
* @param {string} socketUrl
|
||||||
|
*/
|
||||||
|
function connectSocket(socketUrl = userProvidedSocketUrl) {
|
||||||
|
websocket = new WebSocket(socketUrl);
|
||||||
|
|
||||||
|
websocket.onopen = () => {
|
||||||
|
clearTimeout(reconnectTimeout);
|
||||||
|
reconnectAttempts = 0;
|
||||||
|
console.warn('WebSocket connected');
|
||||||
|
};
|
||||||
|
|
||||||
|
websocket.onclose = () => {
|
||||||
|
console.warn('WebSocket disconnected');
|
||||||
|
reconnectTimeout = setTimeout(() => {
|
||||||
|
console.warn(`WebSocket: attempting reconnect ${reconnectAttempts}`);
|
||||||
|
if (websocket && websocket.readyState === WebSocket.CLOSED) {
|
||||||
|
reconnectAttempts += 1;
|
||||||
|
connectSocket();
|
||||||
|
}
|
||||||
|
}, reconnectInterval);
|
||||||
|
};
|
||||||
|
websocket.onerror = (error) => {
|
||||||
|
console.error('WebSocket error:', error);
|
||||||
|
};
|
||||||
|
|
||||||
|
websocket.onmessage = (event) => {
|
||||||
|
// all objects from ontime are structured with tag and payload
|
||||||
|
const { tag, payload } = JSON.parse(event.data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* runtime-data is sent
|
||||||
|
* - on connect with the full state
|
||||||
|
* - and then on every update with a patch
|
||||||
|
*/
|
||||||
|
if (tag === 'runtime-data') {
|
||||||
|
handleOntimePayload(payload);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let localData = {};
|
||||||
|
let localVars = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the ontime payload updates
|
||||||
|
* @param {object} payload - The payload object containing the updates
|
||||||
|
*/
|
||||||
|
function handleOntimePayload(payload) {
|
||||||
|
// 1. apply the patch into your local copy of the data
|
||||||
|
localData = { ...localData, ...payload };
|
||||||
|
|
||||||
|
if ('eventNow' in payload) {
|
||||||
|
let imgelement = document.getElementById("image");
|
||||||
|
let timerelement = document.getElementById("timer");
|
||||||
|
|
||||||
|
localVars.nowid = payload.eventNow.id;
|
||||||
|
localVars.layoutimg = payload.eventNow.custom["layoutimage"] ?? null;
|
||||||
|
|
||||||
|
let newready = payload.eventNow.custom["ready"] == "true";
|
||||||
|
if (localVars.ready != newready) {
|
||||||
|
localVars.ready = newready;
|
||||||
|
updateReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
newready = payload.eventNow.custom["stageready"] == "true";
|
||||||
|
if (localVars.stageready != newready) {
|
||||||
|
localVars.stageready = newready;
|
||||||
|
updateStageButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
imgelement.src = localVars.layoutimg;
|
||||||
|
if (localVars.layoutimg) {
|
||||||
|
imgelement.classList.remove("hidden");
|
||||||
|
timerelement.classList.add("hidden");
|
||||||
|
}else {
|
||||||
|
imgelement.classList.add("hidden");
|
||||||
|
timerelement.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function blink(id) {
|
||||||
|
document.getElementById(id).classList.add("blink");
|
||||||
|
setTimeout(() => {
|
||||||
|
document.getElementById(id).classList.remove("blink");
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReadyClick() {
|
||||||
|
const message = {
|
||||||
|
tag: "change",
|
||||||
|
payload: {
|
||||||
|
[localVars.nowid]: {
|
||||||
|
"custom:stageready": !localVars.stageready
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
websocket.send(JSON.stringify(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStageButton() {
|
||||||
|
let stagereadybutton = document.getElementById("stageready");
|
||||||
|
if (localVars.stageready) {
|
||||||
|
stagereadybutton.classList.remove("redoutline");
|
||||||
|
stagereadybutton.classList.add("greenoutline");
|
||||||
|
}else {
|
||||||
|
stagereadybutton.classList.add("redoutline");
|
||||||
|
stagereadybutton.classList.remove("greenoutline");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateReady() {
|
||||||
|
let fohreadybutton = document.getElementById("fohready");
|
||||||
|
let container = document.getElementById("container");
|
||||||
|
|
||||||
|
if (localVars.ready) {
|
||||||
|
container.classList.remove("redoutline");
|
||||||
|
container.classList.add("greenoutline");
|
||||||
|
blink("container")
|
||||||
|
fohreadybutton.classList.remove("redoutline");
|
||||||
|
fohreadybutton.classList.add("greenoutline");
|
||||||
|
}else {
|
||||||
|
container.classList.add("redoutline");
|
||||||
|
container.classList.remove("greenoutline");
|
||||||
|
fohreadybutton.classList.add("redoutline");
|
||||||
|
fohreadybutton.classList.remove("greenoutline");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the DOM with a given payload
|
||||||
|
* @param {string} field - The runtime data field
|
||||||
|
* @param {object} payload - The patch object for the field
|
||||||
|
*/
|
||||||
|
function updateDOM(field, payload) {
|
||||||
|
const domElement = document.getElementById(field);
|
||||||
|
if (domElement) {
|
||||||
|
domElement.innerText = payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stringifies an object into a pretty string
|
||||||
|
* @param {object} data - The data object to format
|
||||||
|
* @returns {string} The formatted data string
|
||||||
|
*/
|
||||||
|
function formatObject(data) {
|
||||||
|
return JSON.stringify(data, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to handle a demo deployed in an ontime stage
|
||||||
|
* You can likely ignore this in your app
|
||||||
|
*
|
||||||
|
* an url looks like
|
||||||
|
* https://cloud.getontime.no/stage-hash/external/demo/ -> /stage-hash
|
||||||
|
* @returns {string} - The stage hash if the app is running in an ontime stage
|
||||||
|
*/
|
||||||
|
function getStageHash() {
|
||||||
|
const href = window.location.href;
|
||||||
|
if (!href.includes('getontime.no')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = href.split('/');
|
||||||
|
const stageHash = hash.at(3);
|
||||||
|
return stageHash ? `/${stageHash}` : '';
|
||||||
|
}
|
||||||
32
timeroverlaytouch/index.html
Normal file
32
timeroverlaytouch/index.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||||
|
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||||
|
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||||
|
<!--[if gt IE 8]> <html class="no-js"> <!--<![endif]-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<title></title>
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="app.js" async defer></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!--[if lt IE 7]>
|
||||||
|
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<div id="container" class="container redoutline">
|
||||||
|
<img id="image" class="image hidden" src="" alt="no image">
|
||||||
|
<iframe id="timer" class="timer" src="/timer" frameborder="0"></iframe>
|
||||||
|
<div class="buttoncontainer">
|
||||||
|
<div id="fohready" class="buttonelement redoutline">FOH Ready</div>
|
||||||
|
<div onclick="onReadyClick()" id="stageready" class="buttonelement redoutline">Stage Ready</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
86
timeroverlaytouch/style.css
Normal file
86
timeroverlaytouch/style.css
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-family-override, "Open Sans", "Segoe UI", sans-serif);
|
||||||
|
font-size: 2vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttoncontainer,
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: rgba(255, 255, 255, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttoncontainer {
|
||||||
|
padding: 2%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonelement {
|
||||||
|
display: flex;
|
||||||
|
padding: 1%;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 8%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 100%;
|
||||||
|
color: #cfcfcf;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonelement:active {
|
||||||
|
background-color: #101010;
|
||||||
|
outline-width: 0.5vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.redoutline {
|
||||||
|
outline: 1vw solid #e7625c;
|
||||||
|
outline-offset: -1vw;
|
||||||
|
}
|
||||||
|
.greenoutline {
|
||||||
|
outline: 1vw solid #4fbe4c;
|
||||||
|
outline-offset: -1vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image,
|
||||||
|
.timer {
|
||||||
|
position: absolute;
|
||||||
|
object-fit: contain;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blink {
|
||||||
|
animation: blink 1s ease-in-out 3;
|
||||||
|
}
|
||||||
|
@keyframes blink {
|
||||||
|
0% {
|
||||||
|
outline-color: transparent;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
outline-color: #4fbe4c
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
outline-color: transparent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user