theme and camera cookie
This commit is contained in:
@@ -872,18 +872,25 @@
|
|||||||
try {
|
try {
|
||||||
const devices = await Html5Qrcode.getCameras();
|
const devices = await Html5Qrcode.getCameras();
|
||||||
const select = document.getElementById('camera-select');
|
const select = document.getElementById('camera-select');
|
||||||
select.innerHTML = ''; // Clear "loading" message
|
select.innerHTML = '';
|
||||||
|
|
||||||
if (devices && devices.length) {
|
if (devices && devices.length) {
|
||||||
devices.forEach((device, index) => {
|
devices.forEach((device, index) => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = device.id;
|
option.value = device.id;
|
||||||
|
// Store the index in a data attribute for easier retrieval later
|
||||||
|
option.dataset.index = index;
|
||||||
option.text = device.label || `Cámara ${index + 1}`;
|
option.text = device.label || `Cámara ${index + 1}`;
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Default to the last camera (often the main back camera on mobile)
|
// Retrieve saved index or default to the last camera
|
||||||
currentCameraId = devices[devices.length - 1].id;
|
const savedIndex = getCookie('cameraIndex');
|
||||||
|
const targetIndex = (savedIndex !== null && savedIndex < devices.length)
|
||||||
|
? savedIndex
|
||||||
|
: devices.length - 1;
|
||||||
|
|
||||||
|
currentCameraId = devices[targetIndex].id;
|
||||||
select.value = currentCameraId;
|
select.value = currentCameraId;
|
||||||
|
|
||||||
launchCamera(currentCameraId);
|
launchCamera(currentCameraId);
|
||||||
@@ -924,6 +931,14 @@
|
|||||||
|
|
||||||
function switchCamera(cameraId) {
|
function switchCamera(cameraId) {
|
||||||
if (cameraId) {
|
if (cameraId) {
|
||||||
|
const select = document.getElementById('camera-select');
|
||||||
|
const selectedOption = select.options[select.selectedIndex];
|
||||||
|
|
||||||
|
// Save the index of the selected camera to a cookie
|
||||||
|
if (selectedOption && selectedOption.dataset.index !== undefined) {
|
||||||
|
setCookie('cameraIndex', selectedOption.dataset.index, 365);
|
||||||
|
}
|
||||||
|
|
||||||
currentCameraId = cameraId;
|
currentCameraId = cameraId;
|
||||||
launchCamera(cameraId);
|
launchCamera(cameraId);
|
||||||
}
|
}
|
||||||
@@ -936,6 +951,69 @@
|
|||||||
}).catch(err => console.error("Error stopping scanner", err));
|
}).catch(err => console.error("Error stopping scanner", err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Theme Management ── */
|
||||||
|
|
||||||
|
// Helper to set a cookie
|
||||||
|
function setCookie(name, value, days = 365) {
|
||||||
|
const d = new Date();
|
||||||
|
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
|
let expires = "expires=" + d.toUTCString();
|
||||||
|
document.cookie = name + "=" + value + ";" + expires + ";path=/;SameSite=Lax";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to get a cookie
|
||||||
|
function getCookie(name) {
|
||||||
|
let nameEQ = name + "=";
|
||||||
|
let ca = document.cookie.split(';');
|
||||||
|
for (let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
|
||||||
|
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTheme(t) {
|
||||||
|
document.documentElement.setAttribute('data-theme', t);
|
||||||
|
const isDark = t === 'dark';
|
||||||
|
const themeIcon = document.getElementById('theme-icon');
|
||||||
|
const themeLabel = document.getElementById('theme-label');
|
||||||
|
|
||||||
|
if (themeIcon) themeIcon.className = isDark ? 'bi bi-sun me-2' : 'bi bi-moon-stars me-2';
|
||||||
|
if (themeLabel) themeLabel.innerText = isDark ? 'Modo Claro' : 'Modo Oscuro';
|
||||||
|
|
||||||
|
setCookie('theme', t);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
const current = document.documentElement.getAttribute('data-theme');
|
||||||
|
const next = current === 'dark' ? 'light' : 'dark';
|
||||||
|
applyTheme(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialization Logic
|
||||||
|
function initTheme() {
|
||||||
|
const savedTheme = getCookie('theme');
|
||||||
|
|
||||||
|
if (savedTheme) {
|
||||||
|
// Use user preference if it exists
|
||||||
|
applyTheme(savedTheme);
|
||||||
|
} else {
|
||||||
|
// Otherwise, detect OS preference
|
||||||
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
applyTheme(prefersDark ? 'dark' : 'light');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for OS theme changes in real-time if no cookie is set
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||||
|
if (!getCookie('theme')) {
|
||||||
|
applyTheme(e.matches ? 'dark' : 'light');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
initTheme();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="es" data-theme="light">
|
<html lang="es" data-theme="light">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>SekiPOS Login</title>
|
<title>SekiPOS Login</title>
|
||||||
<link rel="shortcut icon" href="./static/favicon.png" type="image/x-icon">
|
<link rel="shortcut icon" href="./static/favicon.png" type="image/x-icon">
|
||||||
<link
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
|
||||||
rel="stylesheet"
|
|
||||||
>
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg: #5865f2;
|
--bg: #5865f2;
|
||||||
@@ -19,6 +17,7 @@
|
|||||||
--accent-hover: #4752c4;
|
--accent-hover: #4752c4;
|
||||||
--error: #ed4245;
|
--error: #ed4245;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
--bg: #36393f;
|
--bg: #36393f;
|
||||||
--card-bg: #2f3136;
|
--card-bg: #2f3136;
|
||||||
@@ -54,10 +53,14 @@
|
|||||||
color: var(--text) !important;
|
color: var(--text) !important;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control:focus {
|
.form-control:focus {
|
||||||
box-shadow: 0 0 0 2px var(--accent);
|
box-shadow: 0 0 0 2px var(--accent);
|
||||||
}
|
}
|
||||||
.form-control::placeholder { color: #8a8e94; }
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: #8a8e94;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-login {
|
.btn-login {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
@@ -65,6 +68,7 @@
|
|||||||
border: none;
|
border: none;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-login:hover {
|
.btn-login:hover {
|
||||||
background: var(--accent-hover);
|
background: var(--accent-hover);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -78,6 +82,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="login-box text-center">
|
<div class="login-box text-center">
|
||||||
<h2 class="fw-bold mb-1">SekiPOS</h2>
|
<h2 class="fw-bold mb-1">SekiPOS</h2>
|
||||||
@@ -90,21 +95,8 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input
|
<input class="form-control mb-3" type="text" name="username" placeholder="Usuario" required autofocus>
|
||||||
class="form-control mb-3"
|
<input class="form-control mb-3" type="password" name="password" placeholder="Contraseña" required>
|
||||||
type="text"
|
|
||||||
name="username"
|
|
||||||
placeholder="Usuario"
|
|
||||||
required
|
|
||||||
autofocus
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="form-control mb-3"
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
placeholder="Contraseña"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<button type="submit" class="btn btn-login w-100">
|
<button type="submit" class="btn btn-login w-100">
|
||||||
Iniciar Sesión
|
Iniciar Sesión
|
||||||
</button>
|
</button>
|
||||||
@@ -112,8 +104,69 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const t = localStorage.getItem('theme') || 'light';
|
/* ── Theme Management ── */
|
||||||
if (t === 'dark') document.documentElement.setAttribute('data-theme', 'dark');
|
|
||||||
|
// Helper to set a cookie
|
||||||
|
function setCookie(name, value, days = 365) {
|
||||||
|
const d = new Date();
|
||||||
|
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
|
let expires = "expires=" + d.toUTCString();
|
||||||
|
document.cookie = name + "=" + value + ";" + expires + ";path=/;SameSite=Lax";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to get a cookie
|
||||||
|
function getCookie(name) {
|
||||||
|
let nameEQ = name + "=";
|
||||||
|
let ca = document.cookie.split(';');
|
||||||
|
for (let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
|
||||||
|
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTheme(t) {
|
||||||
|
document.documentElement.setAttribute('data-theme', t);
|
||||||
|
const isDark = t === 'dark';
|
||||||
|
const themeIcon = document.getElementById('theme-icon');
|
||||||
|
const themeLabel = document.getElementById('theme-label');
|
||||||
|
|
||||||
|
if (themeIcon) themeIcon.className = isDark ? 'bi bi-sun me-2' : 'bi bi-moon-stars me-2';
|
||||||
|
if (themeLabel) themeLabel.innerText = isDark ? 'Modo Claro' : 'Modo Oscuro';
|
||||||
|
|
||||||
|
setCookie('theme', t);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
const current = document.documentElement.getAttribute('data-theme');
|
||||||
|
const next = current === 'dark' ? 'light' : 'dark';
|
||||||
|
applyTheme(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialization Logic
|
||||||
|
function initTheme() {
|
||||||
|
const savedTheme = getCookie('theme');
|
||||||
|
|
||||||
|
if (savedTheme) {
|
||||||
|
// Use user preference if it exists
|
||||||
|
applyTheme(savedTheme);
|
||||||
|
} else {
|
||||||
|
// Otherwise, detect OS preference
|
||||||
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
applyTheme(prefersDark ? 'dark' : 'light');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for OS theme changes in real-time if no cookie is set
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||||
|
if (!getCookie('theme')) {
|
||||||
|
applyTheme(e.matches ? 'dark' : 'light');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
initTheme();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user