Merge pull request #24 from metacube2/main-aurora

Main aurora
This commit is contained in:
2026-01-15 15:10:46 +01:00
committed by GitHub
3 changed files with 232 additions and 60 deletions
+12 -7
View File
@@ -4,11 +4,11 @@
* Speichert in settings.json, lädt ohne Reload * Speichert in settings.json, lädt ohne Reload
*/ */
class SettingsManager { class SettingsManager {
private $settingsFile = 'settings.json'; private $settingsFile;
private $settings = []; private $settings = [];
public function __construct($file = null) { public function __construct($file = null) {
if ($file) $this->settingsFile = $file; $this->settingsFile = $file ?: (__DIR__ . '/settings.json');
$this->load(); $this->load();
} }
@@ -68,10 +68,12 @@ class SettingsManager {
} }
private function save() { private function save() {
return file_put_contents( $payload = json_encode($this->settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$this->settingsFile, if ($payload === false) {
json_encode($this->settings, JSON_PRETTY_PRINT) return false;
) !== false; }
return file_put_contents($this->settingsFile, $payload, LOCK_EX) !== false;
} }
// Für AJAX-Anfragen // Für AJAX-Anfragen
@@ -98,7 +100,10 @@ class SettingsManager {
if ($key && $this->set($key, $value)) { if ($key && $this->set($key, $value)) {
echo json_encode(['success' => true, 'message' => 'Einstellung gespeichert']); echo json_encode(['success' => true, 'message' => 'Einstellung gespeichert']);
} else { } else {
echo json_encode(['success' => false, 'message' => 'Fehler beim Speichern']); echo json_encode([
'success' => false,
'message' => 'Fehler beim Speichern. Bitte Dateirechte prüfen.'
]);
} }
exit; exit;
} }
+22 -7
View File
@@ -1549,11 +1549,11 @@ nav ul li a:hover { color: #4CAF50; }
margin: 0; margin: 0;
} }
.zoom-slider { .zoom-slider {
width: 150px; width: 220px;
} }
.zoom-value { .zoom-value {
font-weight: 700; font-weight: 700;
min-width: 40px; min-width: 50px;
text-align: center; text-align: center;
color: #333; color: #333;
} }
@@ -1565,13 +1565,29 @@ nav ul li a:hover { color: #4CAF50; }
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; color: white;
font-size: 18px; font-size: 18px;
font-weight: bold;
cursor: pointer; cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s; transition: transform 0.2s, box-shadow 0.2s;
display: flex;
align-items: center;
justify-content: center;
} }
.zoom-btn:hover { .zoom-btn:hover {
transform: scale(1.1); transform: scale(1.1);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
} }
.zoom-btn:active {
transform: scale(0.95);
}
.video-container {
cursor: default;
}
.video-container.zoomed {
cursor: grab;
}
.video-container.zoomed:active {
cursor: grabbing;
}
.tech-stat { justify-self: start; font-family: monospace; color: #555; } .tech-stat { justify-self: start; font-family: monospace; color: #555; }
.bitrate-icon { color: #4CAF50; } .bitrate-icon { color: #4CAF50; }
@@ -2046,12 +2062,11 @@ body.theme-neo footer {
<!-- <!--
CONTROLS --> CONTROLS -->
<div id="zoom-controls" class="zoom-controls" aria-label="Zoom Steuerung"> <div id="zoom-controls" class="zoom-controls" aria-label="Zoom Steuerung">
<button onclick="adjustZoom(-0.5)" class="zoom-btn"></button> <button type="button" onclick="adjustZoom(-0.5)" class="zoom-btn" title="Zoom out"></button>
<label for="zoom-range">Zoom</label>
<input type="range" id="zoom-range" class="zoom-slider" min="1" max="4" value="1" step="0.5"> <input type="range" id="zoom-range" class="zoom-slider" min="1" max="4" value="1" step="0.5">
<span id="zoom-value" class="zoom-value">1x</span> <span id="zoom-value" class="zoom-value">1.0x</span>
<button onclick="adjustZoom(0.5)" class="zoom-btn">+</button> <button type="button" onclick="adjustZoom(0.5)" class="zoom-btn" title="Zoom in">+</button>
<button onclick="resetZoom()" class="zoom-btn">⟲</button> <button type="button" onclick="resetZoom()" class="zoom-btn" title="Reset">⟲</button>
</div> </div>
<!-- VIDEO PLAYER CONTROLS (für Tagesvideos) --> <!-- VIDEO PLAYER CONTROLS (für Tagesvideos) -->
+198 -46
View File
@@ -1,55 +1,207 @@
/** /**
* Video Zoom Controller - Zoom für alle Video-Modi * Video Zoom & Pan Controller
* - Zoom für alle Video-Modi (Live, Timelapse, Tagesvideo)
* - Pan-Funktion: Mit Maus den gezoomten Bereich verschieben
*/ */
let currentZoom = 1; (() => {
function applyZoom(zoomValue) {
const config = window.zoomConfig || { minZoom: 1, maxZoom: 4 };
currentZoom = Math.max(config.minZoom, Math.min(config.maxZoom, zoomValue));
const valueEl = document.getElementById('zoom-value');
const slider = document.getElementById('zoom-range');
if (valueEl) valueEl.textContent = currentZoom + 'x';
if (slider) slider.value = currentZoom;
// Alle Video-Elemente zoomen
const targets = [
document.getElementById('webcam-player'),
document.getElementById('timelapse-image'),
document.getElementById('daily-video')
].filter(Boolean);
targets.forEach((el) => {
el.style.transform = `scale(${currentZoom})`;
el.style.transformOrigin = 'center center';
el.style.transition = 'transform 0.2s ease';
});
}
function adjustZoom(delta) {
applyZoom(currentZoom + delta);
}
function resetZoom() {
applyZoom(1);
}
// Initialisierung
document.addEventListener('DOMContentLoaded', function() {
const config = window.zoomConfig || {}; const config = window.zoomConfig || {};
if (!config.enabled) return; if (!config.enabled) return;
let currentZoom = 1;
let panX = 0;
let panY = 0;
let isDragging = false;
let startX = 0;
let startY = 0;
const minZoom = Number(config.minZoom || 1);
const maxZoom = Number(config.maxZoom || 4);
const defaultZoom = Number(config.defaultZoom || 1);
const slider = document.getElementById('zoom-range'); const slider = document.getElementById('zoom-range');
if (slider) { const valueEl = document.getElementById('zoom-value');
slider.addEventListener('input', (event) => {
applyZoom(Number(event.target.value)); // Finde das aktuell aktive Video-Element
function getActiveTarget() {
const webcam = document.getElementById('webcam-player');
const timelapse = document.getElementById('timelapse-image');
const daily = document.getElementById('daily-video');
const timelapseViewer = document.getElementById('timelapse-viewer');
const dailyPlayer = document.getElementById('daily-video-player');
// Prüfe welches Element sichtbar ist
if (dailyPlayer && dailyPlayer.style.display !== 'none' && daily) {
return daily;
}
if (timelapseViewer && timelapseViewer.style.display !== 'none' && timelapse) {
return timelapse;
}
if (webcam) {
return webcam;
}
return null;
}
// Wende Zoom und Pan auf das aktive Element an
function applyTransform() {
const target = getActiveTarget();
if (!target) return;
// Bei Zoom 1x: Kein Pan erlaubt
if (currentZoom <= 1) {
panX = 0;
panY = 0;
}
// Begrenzen der Pan-Werte basierend auf Zoom
const maxPan = (currentZoom - 1) * 50; // Prozent
panX = Math.max(-maxPan, Math.min(maxPan, panX));
panY = Math.max(-maxPan, Math.min(maxPan, panY));
target.style.transform = `scale(${currentZoom}) translate(${panX}%, ${panY}%)`;
target.style.transformOrigin = 'center center';
target.style.transition = isDragging ? 'none' : 'transform 0.2s ease';
// Update UI
if (valueEl) valueEl.textContent = `${currentZoom.toFixed(1)}x`;
if (slider) slider.value = currentZoom;
}
// Zoom setzen
function setZoom(value) {
currentZoom = Math.max(minZoom, Math.min(maxZoom, value));
applyTransform();
}
// Zoom anpassen
function adjustZoom(delta) {
setZoom(currentZoom + delta);
}
// Zoom zurücksetzen
function resetZoom() {
currentZoom = 1;
panX = 0;
panY = 0;
applyTransform();
}
// Mouse Events für Pan
function setupPanEvents() {
const container = document.querySelector('.video-container');
if (!container) return;
container.addEventListener('mousedown', (e) => {
if (currentZoom <= 1) return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
container.style.cursor = 'grabbing';
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = (e.clientX - startX) / 5; // Sensitivität anpassen
const dy = (e.clientY - startY) / 5;
panX += dx / currentZoom;
panY += dy / currentZoom;
startX = e.clientX;
startY = e.clientY;
applyTransform();
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
const container = document.querySelector('.video-container');
if (container) container.style.cursor = currentZoom > 1 ? 'grab' : 'default';
}
});
// Touch Events für Mobile
container.addEventListener('touchstart', (e) => {
if (currentZoom <= 1 || e.touches.length !== 1) return;
isDragging = true;
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
}, { passive: true });
container.addEventListener('touchmove', (e) => {
if (!isDragging || e.touches.length !== 1) return;
const dx = (e.touches[0].clientX - startX) / 5;
const dy = (e.touches[0].clientY - startY) / 5;
panX += dx / currentZoom;
panY += dy / currentZoom;
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
applyTransform();
}, { passive: true });
container.addEventListener('touchend', () => {
isDragging = false;
});
// Cursor anpassen bei Zoom
container.style.cursor = 'default';
}
// Slider Events
function setupSlider() {
if (!slider) return;
slider.min = minZoom;
slider.max = maxZoom;
slider.step = 0.5;
slider.value = defaultZoom;
slider.addEventListener('input', (e) => {
setZoom(Number(e.target.value));
}); });
} }
// Initial zoom anwenden (ohne transform bei 1x) // Globale Funktionen für Buttons
currentZoom = config.defaultZoom || 1; window.adjustZoom = adjustZoom;
if (currentZoom !== 1) { window.resetZoom = resetZoom;
applyZoom(currentZoom); window.setZoom = setZoom;
}
}); // Initialisierung
document.addEventListener('DOMContentLoaded', () => {
setupSlider();
setupPanEvents();
currentZoom = defaultZoom;
// Warte kurz, damit Video-Elemente geladen sind
setTimeout(() => {
applyTransform();
}, 500);
// Update Cursor bei Zoom-Änderung
const container = document.querySelector('.video-container');
if (container) {
const observer = new MutationObserver(() => {
container.style.cursor = currentZoom > 1 ? 'grab' : 'default';
});
}
});
// Bei Moduswechsel Pan zurücksetzen
window.addEventListener('click', (e) => {
if (e.target.id === 'timelapse-button' ||
e.target.closest('#timelapse-button') ||
e.target.id === 'dvp-back-live' ||
e.target.closest('.play-link')) {
panX = 0;
panY = 0;
setTimeout(applyTransform, 100);
}
});
})();