Skip to content

Instantly share code, notes, and snippets.

@programminghoch10
Last active February 26, 2022 12:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save programminghoch10/54f6a7b6b629b2da198f0343c1dfca25 to your computer and use it in GitHub Desktop.
Save programminghoch10/54f6a7b6b629b2da198f0343c1dfca25 to your computer and use it in GitHub Desktop.
TrainController AdvancedSplitView
<!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>
@programminghoch10
Copy link
Author

programminghoch10 commented Feb 9, 2022

AdvancedSplitView

AdvancedSplitView ist eine von Grund auf neu aufgebaute SplitView.
Sie ermöglicht vor dem Laden der TrainController Fenster eine Aufteilung zu wählen,
und ein einfaches Erstellen einer eigenen Aufteilung.

Installation

Klick oben auf RAW, die Datei AdvancedSplitView.html wird heruntergeladen.

Wenn du eine eigene Aufteilung erstellen möchtest, bearbeite die Datei.
Sie enthält eine Anleitung zum einfachen Erstellen einer eigenen Aufteilung.

Zum installieren in den TrainController, verschiebe/kopiere die Datei in den Ordner
C:\Program Files (x86)\Railroad & Co.90\SmartHandCustom.
Dann im TrainController, gehe in die +SmartHand Einstellungen,
dann auf Anschluss und dann bei Verbindungseinstellungen - Mobile / Webserver auf Erweitert.
Dort setze die Startseite auf AdvancedSplitView.

Ressourcen

Erste Veröffentlichung in der TrainController Tauschbörse (danke Dirk ;) )
Der Wunsch einer erweiterten SplitView im TrainController Forum
Blog Beitrag auf digitrain.co (französisch)

Spenden

Wenn dir die AdvancedSplitView gefällt, kannst du mir gerne etwas über Paypal spenden

@programminghoch10
Copy link
Author

Reserved

@programminghoch10
Copy link
Author

Reserved

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment