-
-
Save programminghoch10/54f6a7b6b629b2da198f0343c1dfca25 to your computer and use it in GitHub Desktop.
TrainController AdvancedSplitView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<!-- | |
TrainController Advanced SplitView | |
von Jonas <technik@modellbahn65.de> | |
(C) 2022 | |
https://www.paypal.com/donate/?cmd=_donations&business=silentvortex10@gmail.com | |
Diese Datei ermöglicht einfaches Konfigureren von SplitViews in TrainController. | |
Innerhalb der Dokumentation werden an mehreren Stellen Platzhalter in Beispielen mit <> begrenzt. | |
Wenn also da steht | |
name="<beliebiger text>" | |
bedeutet das, dass nach dem Einsetzen da | |
name="Dies ist ein langweiliger Text" | |
steht. | |
Zum Testen des Layouts, | |
gehe ein wenig im Code herunter und setze die Variable TEST_MODE auf true. | |
Dann werden statt TrainController Ansichten nur zufällige Farben angezeigt, | |
welche das Einrichten eines neuen Layouts einfach machen. | |
Nach Konfiguration muss dieses Dokument in den Ordner | |
C:\Program Files (x86)\Railroad & Co.90\SmartHandCustom | |
platziert werden, | |
und im TrainController in der SmartHand Mobile Webserver Konfiguration | |
AdvancedSplitView (oder nach Umbenennen der neue Name der Datei) | |
ausgewählt werden. | |
V1 (1.2.2022) - Erste Version | |
V2 (4.2.2022) - Einrichten von Overviews ist jetzt noch einfacher, Knöpfe werden nun komplett automatisch erstellt | |
V3 (8.2.2022) - Weitere voreingestelle Overviews, coole Previews zum Auswählen und performantere Farben | |
V4 (11.2.2022) - Mehrere kleine Bugfixes | |
V5 (18.2.2022) - Merken einer Ansicht pro Gerät mit Cookies möglich, konfigurierbare Farben, schönere Aufteilung der Auswahl | |
V6 (18.2.2022) - Wesentliche Verbesserung in Ladezeit, Design und Adaptivität | |
V7 (22.2.2022) - Namen von iFrames entfernt, "Ansicht merken" standardmäßig versteckt, Zoom hinzugefügt | |
--> | |
<html lang="de"> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style> | |
:root { | |
/* Zoom Level der Auswahl */ | |
--zoom-level: 1.0 | |
} | |
body { | |
background-color: black; | |
color: white; | |
} | |
.overview { | |
position: fixed; | |
top: 0px; | |
left: 0px; | |
width: 100%; | |
height: 100%; | |
display: none; | |
} | |
.overview.show { | |
display: unset; | |
} | |
iframe { | |
width: 100%; | |
height: 100%; | |
margin: 0; | |
overflow: hidden; | |
border: hidden; | |
} | |
.overview div, .preoverview div { | |
display: flex; | |
width: 100%; | |
height: 100%; | |
} | |
.overview .horizontal, .preoverview div.horizontal { | |
flex-direction: row; | |
} | |
.overview .vertical, .preoverview div.vertical { | |
flex-direction: column; | |
} | |
div#selector { | |
margin-top: 2%; | |
user-select: none; | |
} | |
div#selector h1 { | |
margin-block: 0; | |
margin-top: 5%; | |
margin-bottom: 1%; | |
text-align: center; | |
position: sticky; | |
top: 0; | |
width: 100%; | |
background-color: #000a; | |
} | |
#previewbuttons { | |
width: 100%; | |
height: 100%; | |
display: flex; | |
flex-wrap: wrap; | |
justify-content: space-evenly; | |
flex-direction: row; | |
margin-bottom: 1rem; | |
} | |
.preview { | |
margin: 1%; | |
border: 1px solid white; | |
height: 100%; | |
width: calc(25vw * var(--zoom-level)); | |
min-width: calc(25vh * var(--zoom-level)); | |
} | |
.preoverview { | |
position: relative; | |
width: 100%; | |
height: calc(25vh * var(--zoom-level)); | |
min-height: calc(10vw * var(--zoom-level)); | |
z-index: -1; | |
border-bottom: 1px solid #333; | |
} | |
.preview p { | |
margin: 0; | |
text-align: center; | |
} | |
.preview .clickableoverlay { | |
position: absolute; | |
width: calc(25vw * var(--zoom-level)); | |
height: calc(25vh * var(--zoom-level)); | |
} | |
div#selector div#rememberdiv { | |
margin-inline: 2%; | |
text-align: center; | |
} | |
div#selector div#credit { | |
width: 100%; | |
position: fixed; | |
bottom: 0; | |
color: grey; | |
} | |
div#selector div#credit p { | |
text-align: center; | |
margin: 0; | |
background-color: #000a; | |
} | |
label#rememberwarning { | |
color: red | |
} | |
.noscript * { | |
margin: 0; | |
text-align: center; | |
} | |
.noscript h1 { | |
color: red; | |
} | |
</style> | |
<script> | |
// wenn der TEST_MODE auf true gesetzt wird, dann wird statt TrainController in jedem iframe eine zufällige Farbe angezeigt | |
var TEST_MODE = false | |
// wenn HIDE_CREDIT auf true gesetzt wird, dann wird die Info Zeile ganz unten ausgeblendet | |
const HIDE_CREDIT = false | |
// wenn ENABLE_REMEMBER auf true gesetzt wird, dann wird die Option zum merken einer Ansicht angezeigt | |
const ENABLE_REMEMBER = false | |
// der Titel der Auswahl | |
const SELECT_TITLE = "AdvancedSplitView" | |
// wie lange die cookies gespeichert werden, also wie lange ein "ansicht merken" erhalten bleibt, in Tagen | |
const cookiesavedays = 365 | |
// welche Farben für die Vorschau benutzt werden, wenn leer werden zufällige Farben benutzt, nur eines auswählen! | |
// es sollten mindestens so viele Farben da sein wie die größte Overview Unterteilungen hat | |
//const colors = ['#f87171', '#fbbf24', '#a3e635', '#34d399', '#22d3ee', '#60a5fa', '#a78bfa', '#e879f9', '#fb7185'] //tailwind 400 (hell), 9 abstufungen | |
//const colors = ['#dc2626', '#d97706', '#65a30d', '#059669', '#0891b2', '#2563eb', '#7c3aed', '#c026d3', '#e11d48'] //tailwind 600 (dunkel), 9 abstufungen | |
//const colors = ['#f87171', '#facc15', '#34d399', '#38bdf8', '#a78bfa', '#f472b6'] //tailwind 400 (hell), 6 abstufungen | |
//const colors = ['#dc2626', '#ca8a04', '#059669', '#0284c7', '#7c3aed', '#db2777'] //tailwind 600 (dunkel), 6 abstufungen | |
//const colors = ['#ef4444', '#eab308', '#10b981', '#0ea5e9', '#8b5cf6', '#ec4899'] //tailwind 500 (kräftig), 6 abstufungen | |
const colors = ['#ef4444', '#f59e0b', '#84cc16', '#10b981', '#3b82f6', '#8b5cf6'] //tailwind 500 (kräftig), 6/9 abstufungen (selektiert) | |
//const colors = ['#f87171', '#facc15', '#34d399', '#38bdf8', '#a78bfa', '#b91c1c', '#a16207', '#047857', '#0369a1', '#6d28d9', '#be185d'] //tailwind 400/700, 11/12 abstufungen (selektiert) | |
//const colors = ["#F00", "#0F0", "#00F", "#FF0", "#0FF", "#F0F"] //starke farben | |
//const colors = ["#EEE", "#CCC", "#999", "#666", "#333", "#111"] //grauskala | |
//const colors = [] //zufällige farben | |
const remembercookiename = "rememberoverview" | |
const selectoverviewsearchparam = "view" | |
const resetremembercookiesearchparam = "select" | |
function getURL(viewId, iFrame) { | |
if (!iFrame) throw `iframe ${iFrame} is invalid` | |
if (TEST_MODE) return getColorURI() | |
let iFrameList = getIFramesInView(viewId) | |
let iFrameCounter = Object.values(iFrameList).findIndex(i => i === iFrame) + 1 | |
let config = getAttributes(iFrame) | |
if (config["src"]) return config["src"] | |
let u = config["u"] ? `&u=${config["u"]}` : "" | |
// supply t (transfer) if this is a stellwerk and there is a train frame elsewhere | |
let t = config["i"] === "s" && Object.values(iFrameList).find(i => i.getAttribute("i") == "t") ? "&t=1" : "" | |
let i = config["i"] ? `&i=${config["i"]}` : "" | |
return `index.htm?c=0${i}&w=${iFrameCounter}${t}${u}` | |
} | |
function query() {return document.querySelector(...arguments)} | |
function queryAll() {return document.querySelectorAll(...arguments)} | |
function getURLSearchParam(param) {return new URLSearchParams(document.location.search).get(param)} | |
function getIFramesInView(viewId) {return queryAll(`div.overview#${viewId} iframe`)} | |
//function getIFrameByName(viewId, iFrameName) {return query(`div.overview#${viewId} iframe[name='${iFrameName}']`)} | |
function getAttributes(htmlElement) { | |
let result = {} | |
Object.values(htmlElement.attributes).forEach(value => result[value.nodeName] = value.nodeValue) | |
return result | |
} | |
function setAttributes(htmlElement, attributes) { | |
Object.keys(attributes).forEach(key => { | |
htmlElement.setAttribute(key, attributes[key]) | |
}) | |
} | |
// cookie scripts imported from https://github.com/programminghoch10/tictactoesquared/blob/master/docs/scripts/cookie.js | |
function getCookie(name) { | |
var name = name + "=" | |
var decodedCookie = decodeURIComponent(document.cookie) | |
var ca = decodedCookie.split(";") | |
for (var i = 0; i < ca.length; i++) { | |
var c = ca[i] | |
while (c.charAt(0) == " ") { | |
c = c.substring(1) | |
} | |
if (c.indexOf(name) == 0) { | |
return c.substring(name.length, c.length) | |
} | |
} | |
return "" | |
} | |
function setCookie(name, value) { | |
var d = new Date() | |
d.setTime(d.getTime() + cookiesavedays * 24 * 60 * 60 * 1000) | |
var expires = "expires=" + d.toUTCString() | |
document.cookie = name + "=" + value + ";" + expires + ";path=/" | |
} | |
function deleteCookie(name) { setCookie(name, "") } | |
function getAutoSelectOverview() { | |
if (getURLSearchParam(selectoverviewsearchparam)) return getURLSearchParam(selectoverviewsearchparam) | |
if (getURLSearchParam(resetremembercookiesearchparam)) deleteCookie(remembercookiename) | |
if (!ENABLE_REMEMBER) deleteCookie(remembercookiename) | |
if (getCookie(remembercookiename)) return getCookie(remembercookiename) | |
} | |
function onBodyLoad() { | |
document.title = SELECT_TITLE | |
if (getURLSearchParam("colors")) TEST_MODE = true | |
if (HIDE_CREDIT) query("div#credit").style.display = "none" | |
setupIFrames() | |
if (getAutoSelectOverview()) { | |
selectOverView(getAutoSelectOverview()) | |
} else { | |
setupButtons() | |
query("div#selector").style.display = "" | |
} | |
} | |
function selectOverView(viewId) { | |
unloadall() | |
saveRememberOverView(viewId) | |
let view = query(`div.overview#${viewId}`) | |
if (!view) throw `overview ${viewId} not found` | |
let iFrameList = getIFramesInView(viewId) | |
iFrameList.forEach(iFrame => { | |
iFrame.src = getURL(viewId, iFrame) | |
}) | |
view.classList.add("show") | |
query("div#selector").style.display = "none" | |
document.title = view.title ?? view.id | |
} | |
function saveRememberOverView(viewId) { | |
if (!ENABLE_REMEMBER) return | |
let remember = query("input#remember").checked | |
if (!remember) return | |
setCookie(remembercookiename, viewId) | |
} | |
function unloadall() { | |
queryAll("iframe").forEach(iFrame => iFrame.src = "") | |
} | |
function setupIFrames() { | |
let overviewCounter = 1 | |
queryAll("div.overview").forEach(overview => { | |
let config = getAttributes(overview) | |
config.title = config.title ?? "Fenster " + overviewCounter | |
config.id = "overview" + overviewCounter | |
config.button = config.button ?? config.title | |
config.scrolling = "no" | |
setAttributes(overview, config) | |
overviewCounter++ | |
}) | |
} | |
function setupButtons() { | |
let previewbuttondiv = query("div#selector div#previewbuttons") | |
queryAll("div.overview").forEach(overview => { | |
let config = getAttributes(overview) | |
let previewdiv = document.createElement("div") | |
previewdiv.classList.add("preview") | |
previewdiv.onclick = () => { selectOverView(config.id) } | |
let clickablediv = document.createElement("div") | |
clickablediv.classList.add("clickableoverlay") | |
previewdiv.appendChild(clickablediv) | |
let previewframe = overview.cloneNode(true) | |
previewframe.classList.remove("overview") | |
previewframe.classList.add("preoverview") | |
previewframe.querySelectorAll("iframe").forEach(iFrame => { | |
iFrame.src = getColorURI(config.id) | |
iFrame.title = "Preview " + config.title | |
}) | |
previewdiv.appendChild(previewframe) | |
let previewtext = document.createElement("p") | |
previewtext.innerText = config.button | |
previewdiv.appendChild(previewtext) | |
previewbuttondiv.appendChild(previewdiv) | |
}) | |
if (!ENABLE_REMEMBER) query("div#rememberdiv").style.display = "none" | |
query("h1#selecttitle").innerText = SELECT_TITLE | |
} | |
function getColorURI(scope) { | |
let color = getColor(scope) | |
let svg = `<svg xmlns="http://www.w3.org/2000/svg" style="background: ${color}"></svg>` | |
return `data:image/svg+xml;base64,${btoa(svg)}` | |
} | |
var usedColors = {} | |
function getColor(scope) { | |
if (colors.length == 0) return getRandomColor() | |
if (!usedColors[scope]) usedColors[scope] = [] | |
let availableColors = colors.filter(c => !usedColors[scope].includes(c)) | |
if (availableColors.length == 0) availableColors = colors | |
let randomSelection = Math.floor(Math.random() * availableColors.length) | |
usedColors[scope].push(availableColors[randomSelection]) | |
return availableColors[randomSelection] | |
} | |
function getRandomColor() { | |
//const letters = '0123456789ABCDEF' | |
const letters = '3579BD' | |
var color = '#' | |
for (var i = 0; i < 6; i++) { | |
color += letters[Math.floor(Math.random() * letters.length)] | |
} | |
console.log("random generated color", color) | |
return color | |
} | |
function updateRememberWarning() { | |
let rememberwarning = query("label#rememberwarning") | |
let rememberbutton = query("input#remember") | |
let showWarning = rememberbutton.checked | |
rememberwarning.style.display = showWarning ? '' : 'none' | |
} | |
</script> | |
</head> | |
<body onload="onBodyLoad()"> | |
<noscript> | |
<div class="noscript"> | |
<h1>Du brauchst JavaScript!</h1> | |
<p>AdvancedSplitView funktioniert nicht ohne JavaScript.</p> | |
<p>Du brauchst das aber für TrainController sowieso...</p> | |
<p>Warum ist es also nicht aktiviert?</p> | |
</div> | |
</noscript> | |
<div id="selector" style="display: none;"> | |
<h1 id="selecttitle"></h1> | |
<div id="rememberdiv"> | |
<input type="checkbox" id="remember" onchange="updateRememberWarning()"> | |
<label for="remember">Gewählte Ansicht merken</label> | |
<label for="remember" id="rememberwarning" style="display: none;"> | |
<br> | |
Hiermit wird die Auswahl in Zukunft überspungen und die ausgewählte Ansicht automatisch ausgewählt! | |
</label> | |
</div> | |
<div id="previewbuttons"></div> | |
<div id="credit"> | |
<p>AdvancedSplitView - (C) Jonas 2022</p> | |
</div> | |
</div> | |
<!-- | |
Hier die verschiendenen Ansichten (Overviews) konfigureren. | |
Das Hauptelement einer Overview braucht class="overview" | |
sowie title="<beliebiger Text>" welcher als Seitentitel benutzt wird. | |
Auf dem Knopf steht standardmäßig der Seitentitel, sollte dies nicht gewünscht sein, | |
kann man diesen mit button="<beliebiger Text>" überschreiben | |
Ein div einer Overview sieht also am Ende so aus: | |
<div class="overview" id="<id>" title="<Seitentitel>" button="<Beschriftung des Knopfes>"> | |
Jedes iframe in einer Overview benötigt | |
i="s" für Stellwerke | |
oder | |
i="t" für Führerstände | |
Es kann ein Benutzer mit u="<name des benutzers>" angegeben werden. | |
Die Parameter i und u funktionieren so wie in der TrainController Anleitung beschrieben. | |
Alle anderen Parameter werden automatisch verwaltet, diese müssen nicht angegeben werden. | |
Im Notfall kann man immernoch selber ein src="<url>" angeben, davon rate ich aber ab. | |
Ein iframe sieht also am Ende so aus: | |
<iframe i="<s oder t>" u="<Benutzername>"></iframe> | |
Zum anordnen von iframes mit divs stehen einige Tools zur Verfügung: | |
Horizontal: | |
<div class="horizontal"> | |
<div style="width: 50%">...</div> | |
<div style="width: 50%">...</div> | |
</div> | |
Vertikal: | |
<div class="vertical"> | |
<div style="height: 60%">...</div> | |
<div style="height: 20%">...</div> | |
<div style="height: 20%">...</div> | |
</div> | |
Die Overview hat immer eine Vertikale Anordnung, | |
sollte direkt eine nur horizontale Teilung gewünscht sein, | |
dann muss ein <div class="horizontal">...</div> eingefügt werden. | |
Die Dimensionen sollten so gewählt sein, dass am Ende alles auf 100% herausläuft. | |
Mehr als 100% resultieren in einer Scollbar und weniger als 100% in leeren schwarzen Löchern. | |
Eine Overview kann beim öffnen der Website "automatisch" ausgewählt werden durch Aufruf mit dem Parameter "view": | |
http://<ip adresse>/?view=<id der overview> | |
Die ID setzt sich zusammen aus overview + aufsteigende Nummer der Overviews. | |
Wenn ENABLE_REMEMBER aktiviert ist, kann man ein "Ansicht merken" zurücksetzen, indem man einfach die Website einmal mit dem Parameter "select" aufruft: | |
http://<ip adresse>/?select=1 | |
Durch deaktivieren von ENABLE_REMEMBER wird die Auswahl beim nächsten Laden ebenfalls zurückgesetzt. | |
Diese Datei kommt mit einer schon existierenden Konfiguration. | |
Am meisten lernt man durch analysieren, verstehen und geschicktes kopieren. | |
Viel Glück! | |
--> | |
<div class="overview" title="2 Loks + Stellwerk"> | |
<div class="horizontal"> | |
<div style="width: 25%;" class="vertical"> | |
<div style="height: 50%;"> | |
<iframe i="t"></iframe> | |
</div> | |
<div style="height: 50%;"> | |
<iframe i="t"></iframe> | |
</div> | |
</div> | |
<div style="width: 75%;"> | |
<iframe i="s"></iframe> | |
</div> | |
</div> | |
</div> | |
<div class="overview" title="5 Loks + Stellwerk"> | |
<div class="vertical"> | |
<div style="height: 40%;" class="horizontal"> | |
<div style="width: 20%;"> | |
<iframe i="t"></iframe> | |
</div> | |
<div style="width: 20%;"> | |
<iframe i="t"></iframe> | |
</div> | |
<div style="width: 20%;"> | |
<iframe i="t"></iframe> | |
</div> | |
<div style="width: 20%;"> | |
<iframe i="t"></iframe> | |
</div> | |
<div style="width: 20%;"> | |
<iframe i="t"></iframe> | |
</div> | |
</div> | |
<div style="height: 60%;"> | |
<iframe i="s"></iframe> | |
</div> | |
</div> | |
</div> | |
<div class="overview" title="2 Stellwerke Horizontal"> | |
<div class="horizontal"> | |
<div style="width: 50%;"> | |
<iframe i="s"></iframe> | |
</div> | |
<div style="width: 50%;"> | |
<iframe i="s"></iframe> | |
</div> | |
</div> | |
</div> | |
<div class="overview" title="2 Stellwerke Vertikal"> | |
<div class="vertical"> | |
<div style="height: 50%;"> | |
<iframe i="s"></iframe> | |
</div> | |
<div style="height: 50%;"> | |
<iframe i="s"></iframe> | |
</div> | |
</div> | |
</div> | |
<div class="overview" title="Vollbild Stellwerk"> | |
<iframe i="s"></iframe> | |
</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Reserved