diff --git a/public/mirror/index.html b/public/mirror/index.html
index ef60a19..02e99cf 100644
--- a/public/mirror/index.html
+++ b/public/mirror/index.html
@@ -22,6 +22,7 @@
refreshTimer: null,
renderedAt: 0,
ui: {},
+ wakeLock: null,
showStatus: function (message) {
trmnl.ui.img.style.display = "none";
@@ -40,6 +41,8 @@
trmnl.ui.apiKeyInput.value = data.api_key || "";
trmnl.ui.baseURLInput.value = data.base_url || "";
trmnl.ui.displayModeSelect.value = data.display_mode || "";
+ trmnl.ui.fullscreenToggle.checked = !!data.fullscreen;
+ trmnl.ui.wakeLockToggle.checked = !!trmnl.wakeLock || !!data.wake_lock;
trmnl.ui.setup.style.display = "flex";
},
@@ -50,6 +53,8 @@
var apiKey = trmnl.ui.apiKeyInput.value;
var baseURL = trmnl.ui.baseURLInput.value;
var displayMode = trmnl.ui.displayModeSelect.value;
+ var fullscreenEnabled = trmnl.ui.fullscreenToggle.checked;
+ var wakeLockEnabled = trmnl.ui.wakeLockToggle.checked;
if (!apiKey) {
return;
@@ -58,9 +63,26 @@
trmnl.saveSettings({
api_key: apiKey,
base_url: baseURL,
- display_mode: displayMode
+ display_mode: displayMode,
+ fullscreen: fullscreenEnabled,
+ wake_lock: wakeLockEnabled
});
+ if (wakeLockEnabled) {
+ trmnl.acquireWakeLock().then(function () {
+ trmnl.ui.wakeLockToggle.checked = !!trmnl.wakeLock;
+ }).catch(function (err) {
+ console.warn("Wake Lock request failed:", err);
+ trmnl.ui.wakeLockToggle.checked = false;
+ });
+ } else {
+ trmnl.releaseWakeLock().then(function () {
+ trmnl.ui.wakeLockToggle.checked = false;
+ }).catch(function (err) {
+ console.warn("Wake Lock release failed:", err);
+ });
+ }
+
trmnl.fetchDisplay();
},
@@ -68,6 +90,144 @@
trmnl.ui.setup.style.display = "none";
},
+ isFullscreenSupported: function () {
+ return !!(
+ document.fullscreenEnabled ||
+ document.webkitFullscreenEnabled ||
+ document.msFullscreenEnabled
+ );
+ },
+
+ isFullscreenActive: function () {
+ return !!(
+ document.fullscreenElement ||
+ document.webkitFullscreenElement ||
+ document.msFullscreenElement
+ );
+ },
+
+ enterFullscreen: function () {
+ if (!trmnl.isFullscreenSupported()) return;
+
+ var el = document.documentElement;
+ var promise;
+
+ if (el.requestFullscreen) {
+ promise = el.requestFullscreen();
+ } else if (el.webkitRequestFullscreen) {
+ promise = el.webkitRequestFullscreen();
+ } else if (el.msRequestFullscreen) {
+ promise = el.msRequestFullscreen();
+ }
+
+ if (promise && promise.catch) {
+ promise.catch(function (err) {
+ console.warn("Enter fullscreen failed:", err);
+ });
+ }
+ },
+
+ exitFullscreen: function () {
+ if (!trmnl.isFullscreenSupported()) return;
+ if (!trmnl.isFullscreenActive()) return;
+
+ var promise;
+
+ if (document.exitFullscreen) {
+ promise = document.exitFullscreen();
+ } else if (document.webkitExitFullscreen) {
+ promise = document.webkitExitFullscreen();
+ } else if (document.msExitFullscreen) {
+ promise = document.msExitFullscreen();
+ }
+
+ if (promise && promise.catch) {
+ promise.catch(function (err) {
+ console.warn("Exit fullscreen failed:", err);
+ });
+ }
+ },
+
+ syncFullscreenToggle: function () {
+ var active = trmnl.isFullscreenActive();
+ trmnl.ui.fullscreenToggle.checked = active;
+ },
+
+ isWakeLockSupported: function () {
+ return (
+ window.isSecureContext &&
+ navigator.wakeLock &&
+ typeof navigator.wakeLock.request === "function"
+ );
+ },
+
+ acquireWakeLock: function () {
+
+ if (!trmnl.isWakeLockSupported()) {
+ return {
+ then: function () { return this; },
+ catch: function () { return this; }
+ };
+ }
+
+ if (trmnl.wakeLock) {
+ return Promise.resolve();
+ }
+
+ return navigator.wakeLock.request("screen")
+ .then(function (sentinel) {
+
+ trmnl.wakeLock = sentinel;
+
+ sentinel.addEventListener("release", function () {
+ trmnl.wakeLock = null;
+ trmnl.ui.wakeLockToggle.checked = false;
+ });
+
+ console.log("Wake Lock attivo");
+
+ })
+ .catch(function (err) {
+ console.warn("Wake Lock failed:", err);
+ trmnl.wakeLock = null;
+ trmnl.ui.wakeLockToggle.checked = false;
+ });
+ },
+
+
+ releaseWakeLock: function () {
+
+ if (!trmnl.wakeLock) {
+ return Promise.resolve();
+ }
+
+ return trmnl.wakeLock.release()
+ .then(function () {
+ trmnl.wakeLock = null;
+ console.log("Wake Lock rilasciato");
+ })
+ .catch(function (err) {
+ console.warn("Release failed:", err);
+ trmnl.wakeLock = null;
+ });
+ },
+
+
+ toggleWakeLock: function () {
+ if (!trmnl.isWakeLockSupported()) return;
+ if (trmnl.wakeLock) {
+ trmnl.releaseWakeLock().then(function () {
+ trmnl.ui.wakeLockToggle.checked = false;
+ });
+ } else {
+ trmnl.acquireWakeLock().then(function () {
+ trmnl.ui.wakeLockToggle.checked = !!trmnl.wakeLock;
+ });
+ }
+ },
+
+
+
fetchDisplay: function (opts) {
opts = opts || {};
clearTimeout(trmnl.refreshTimer);
@@ -132,8 +292,12 @@
trmnl.showStatus("Error processing response: " + e.message);
}
} else {
+ var msg = xhr.statusText
+ if (xhr.status == 404) {
+ msg = "Maybe wrong API key";
+ }
trmnl.showStatus(
- "Failed to fetch screen: " + xhr.status + " " + xhr.statusText
+ "Failed to fetch screen: " + xhr.status + " " + msg
);
}
};
@@ -266,15 +430,110 @@
trmnl.ui.apiKeyInput = document.getElementById("api_key");
trmnl.ui.baseURLInput = document.getElementById("base_url");
trmnl.ui.displayModeSelect = document.getElementById("display_mode");
+ trmnl.ui.fullscreenToggle = document.getElementById("fullscreenToggle");
+ trmnl.ui.wakeLockToggle = document.getElementById("wakeLockToggle");
trmnl.ui.setup = document.getElementById("setup");
+ // Sync fullscreen state
+ document.addEventListener("fullscreenchange", trmnl.syncFullscreenToggle);
+ document.addEventListener("webkitfullscreenchange", trmnl.syncFullscreenToggle);
+ document.addEventListener("msfullscreenchange", trmnl.syncFullscreenToggle);
+
+ // Fullscreen toggle
+ if (!trmnl.isFullscreenSupported()) {
+ trmnl.ui.fullscreenToggle.disabled = true;
+ trmnl.ui.fullscreenToggle.parentElement.style.opacity = "0.5";
+ trmnl.ui.fullscreenToggle.parentElement.style.cursor = "not-allowed";
+ } else {
+ trmnl.ui.fullscreenToggle.addEventListener("change", function (e) {
+ e.stopPropagation();
+
+ if (e.target.checked) {
+ trmnl.enterFullscreen();
+ } else {
+ trmnl.exitFullscreen();
+ }
+ });
+ }
+
+ var wakeLockHint = document.getElementById("wakeLockHint");
+
+ // Wake Lock toggle
+ if (trmnl.isWakeLockSupported()) {
+
+ trmnl.ui.wakeLockToggle.disabled = false;
+ trmnl.ui.wakeLockToggle.parentElement.style.opacity = "1";
+ trmnl.ui.wakeLockToggle.parentElement.style.cursor = "pointer";
+
+ if (wakeLockHint) wakeLockHint.style.display = "none";
+
+ trmnl.ui.wakeLockToggle.addEventListener("change", function () {
+ trmnl.toggleWakeLock();
+ });
+
+ document.addEventListener("visibilitychange", function () {
+ if (
+ document.visibilityState === "visible" &&
+ trmnl.ui.wakeLockToggle.checked
+ ) {
+ trmnl.acquireWakeLock();
+ }
+ });
+
+ } else {
+
+ // unsupported (HTTP or old browser)
+ trmnl.ui.wakeLockToggle.disabled = true;
+ trmnl.ui.wakeLockToggle.checked = false;
+ trmnl.ui.wakeLockToggle.parentElement.style.opacity = "0.5";
+ trmnl.ui.wakeLockToggle.parentElement.style.cursor = "not-allowed";
+
+ if (!window.isSecureContext && wakeLockHint) {
+ wakeLockHint.style.display = "block";
+ }
+
+ }
+
+ // get settings from localstorage
var settings = trmnl.getSettings();
+
+ // show setup form if missing apikey
if (!settings || !settings.api_key) {
trmnl.showSetupForm();
} else {
trmnl.fetchDisplay();
}
- }
+
+ // Auto fullscreen at first click/touch if option enabled
+ if (settings.fullscreen && trmnl.isFullscreenSupported()) {
+ var activateFullscreenOnce = function () {
+ trmnl.enterFullscreen();
+ document.removeEventListener("click", activateFullscreenOnce);
+ document.removeEventListener("touchstart", activateFullscreenOnce);
+ };
+ document.addEventListener("click", activateFullscreenOnce, { once: true });
+ document.addEventListener("touchstart", activateFullscreenOnce, { once: true });
+ }
+
+ // Auto Wake Lock at first click/touch if option enabled
+ if (settings.wake_lock && trmnl.isWakeLockSupported()) {
+ var acquireWakeLockOnce = function () {
+ trmnl.acquireWakeLock().then(function () {
+ trmnl.ui.wakeLockToggle.checked = !!trmnl.wakeLock;
+ }).catch(function (err) {
+ console.warn("Wake Lock request failed:", err);
+ trmnl.ui.wakeLockToggle.checked = false;
+ });
+ document.removeEventListener("click", acquireWakeLockOnce);
+ document.removeEventListener("touchstart", acquireWakeLockOnce);
+ };
+ document.addEventListener("click", acquireWakeLockOnce, { once: true });
+ document.addEventListener("touchstart", acquireWakeLockOnce, { once: true });
+ }
+
+ trmnl.syncFullscreenToggle();
+ } //init end
+
};
document.addEventListener("DOMContentLoaded", function () {
@@ -393,8 +652,7 @@
display: block;
}
- label,
- summary {
+ label {
font-size: 1.25em;
margin-bottom: 0.5em;
cursor: pointer;
@@ -423,6 +681,10 @@
width: 100%;
}
+ .btn-secondary {
+ background-color: #777;
+ }
+
.btn-clear {
margin-top: 1em;
background-color: #777;
@@ -447,8 +709,127 @@
background-color: #ffffff;
}
- #unsupported {
- color: red;
+ .setting-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1em;
+ flex-wrap: nowrap;
+ }
+
+ .setting-row label,
+ .setting-row .toggle-label {
+ font-size: 1.25em;
+ margin: 0;
+ cursor: default;
+ }
+
+ .setting-row select,
+ .setting-row .switch {
+ width: auto;
+ min-width: 52px;
+ height: 28px;
+ }
+
+ .switch {
+ position: relative;
+ display: inline-block;
+ width: 52px;
+ height: 28px;
+ }
+
+ .switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+ }
+
+ .slider {
+ position: absolute;
+ cursor: pointer;
+ inset: 0;
+ background-color: #ccc;
+ border-radius: 28px;
+ transition: background-color 0.2s ease;
+ }
+
+ .slider::before {
+ content: "";
+ position: absolute;
+ height: 22px;
+ width: 22px;
+ left: 3px;
+ top: 3px;
+ background-color: white;
+ border-radius: 50%;
+ transition: transform 0.2s ease;
+ }
+
+ .switch input:checked+.slider {
+ background-color: #f54900;
+ }
+
+ .switch input:checked+.slider::before {
+ transform: translateX(24px);
+ }
+
+ .switch input:disabled+.slider {
+ background-color: #ccc;
+ cursor: not-allowed;
+ }
+
+ .switch input:disabled+.slider::before {
+ background-color: #eee;
+ }
+
+ .form-select-small {
+ width: 6em;
+ font-size: 1em;
+ padding: 0.4em 0.5em;
+ border: 1px solid #ccc;
+ border-radius: 0.5em;
+ background-color: #ffffff;
+ }
+
+ .toggle-label {
+ font-size: 1.25em;
+ margin: 0;
+ cursor: default;
+ pointer-events: auto;
+ }
+
+ .setting-hint {
+ font-size: 0.75em;
+ color: #f41414;
+ margin-top: 0.2em;
+ margin-left: 0.5em;
+ }
+
+ /* Fallback for iOS 9 */
+ @media screen and (max-width: 1024px) and (-webkit-min-device-pixel-ratio: 1) {
+ .setting-row {
+ display: block;
+ overflow: hidden;
+ }
+
+ .setting-row label,
+ .setting-row .toggle-label {
+ float: left;
+ line-height: 28px;
+ margin-right: 0.5em;
+ }
+
+ .setting-row select,
+ .setting-row .switch {
+ float: right;
+ width: auto;
+ min-width: 52px;
+ height: 28px;
+ }
+
+ .setting-hint {
+ display: none !important;
+ }
}
@@ -459,18 +840,6 @@