v6.8: Stopp-Button Fix, persistente Freitext-Lieferant-Speicherung, SW-Update
- Stopp-Button: overflow:hidden auf Video-Container, z-index auf Controls, pointer-events:none auf Quagga-Canvas damit der Button klickbar bleibt - Freitext-Lieferant wird persistent in localStorage gespeichert (hbs_config) und bei Dropdown-Aenderung sofort aktualisiert - Service Worker: Network-first fuer eigene Assets (JS/CSS), Cache-first nur noch fuer CDN-Libraries - Letzte Bestell-ID (lastOrderId) ebenfalls persistent - Migration alter localStorage-Keys in neue zentrale Config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
08221a660d
commit
d81f215f59
4 changed files with 107 additions and 21 deletions
|
|
@ -76,7 +76,7 @@ class modHandyBarcodeScanner extends DolibarrModules
|
||||||
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@handybarcodescanner'
|
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@handybarcodescanner'
|
||||||
|
|
||||||
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||||
$this->version = '6.0';
|
$this->version = '6.8';
|
||||||
// Url to the file with your last numberversion of this module
|
// Url to the file with your last numberversion of this module
|
||||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,40 @@
|
||||||
let allSuppliers = [];
|
let allSuppliers = [];
|
||||||
let quaggaInitialized = false;
|
let quaggaInitialized = false;
|
||||||
let openDialogCount = 0; // Zaehlt offene Dialoge
|
let openDialogCount = 0; // Zaehlt offene Dialoge
|
||||||
let lastFreetextSupplierId = null; // Zuletzt verwendeter Freitext-Lieferant
|
|
||||||
|
|
||||||
// Order overview state
|
// Persistente Einstellungen - eine zentrale Config statt einzelner Keys
|
||||||
let lastOrderId = null;
|
const STORAGE_KEY = 'hbs_config';
|
||||||
|
function loadConfig() {
|
||||||
|
try {
|
||||||
|
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function saveConfig(key, value) {
|
||||||
|
const cfg = loadConfig();
|
||||||
|
cfg[key] = value;
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(cfg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migration: alte einzelne Keys -> neue zentrale Config
|
||||||
|
(function migrateOldKeys() {
|
||||||
|
const oldSupplier = localStorage.getItem('hbs_lastFreetextSupplierId');
|
||||||
|
const oldOrder = localStorage.getItem('hbs_lastOrderId');
|
||||||
|
if (oldSupplier) {
|
||||||
|
saveConfig('lastFreetextSupplierId', oldSupplier);
|
||||||
|
localStorage.removeItem('hbs_lastFreetextSupplierId');
|
||||||
|
}
|
||||||
|
if (oldOrder) {
|
||||||
|
saveConfig('lastOrderId', parseInt(oldOrder));
|
||||||
|
localStorage.removeItem('hbs_lastOrderId');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
let lastFreetextSupplierId = loadConfig().lastFreetextSupplierId || null;
|
||||||
|
|
||||||
|
// Order overview state (persistent über App-Neustarts)
|
||||||
|
let lastOrderId = loadConfig().lastOrderId || null;
|
||||||
let cachedOrders = [];
|
let cachedOrders = [];
|
||||||
let currentOrderDetail = null;
|
let currentOrderDetail = null;
|
||||||
|
|
||||||
|
|
@ -277,10 +307,15 @@
|
||||||
Quagga.start();
|
Quagga.start();
|
||||||
quaggaInitialized = true;
|
quaggaInitialized = true;
|
||||||
isScanning = true;
|
isScanning = true;
|
||||||
|
elements.startBtn.disabled = false;
|
||||||
elements.startBtn.classList.add('hidden');
|
elements.startBtn.classList.add('hidden');
|
||||||
elements.stopBtn.classList.remove('hidden');
|
elements.stopBtn.classList.remove('hidden');
|
||||||
elements.videoContainer.classList.add('scanning');
|
elements.videoContainer.classList.add('scanning');
|
||||||
|
|
||||||
|
// Quagga-Canvas darf keine Klicks abfangen
|
||||||
|
const canvases = elements.videoContainer.querySelectorAll('canvas');
|
||||||
|
canvases.forEach(function(c) { c.style.pointerEvents = 'none'; });
|
||||||
|
|
||||||
// Register detection handler
|
// Register detection handler
|
||||||
Quagga.onDetected(onBarcodeDetected);
|
Quagga.onDetected(onBarcodeDetected);
|
||||||
|
|
||||||
|
|
@ -728,6 +763,7 @@
|
||||||
showToast(`${CONFIG.lang.added}: ${productName} (${qty}x)`, 'success');
|
showToast(`${CONFIG.lang.added}: ${productName} (${qty}x)`, 'success');
|
||||||
// Save last order ID for auto-open in order view
|
// Save last order ID for auto-open in order view
|
||||||
lastOrderId = data.order_id;
|
lastOrderId = data.order_id;
|
||||||
|
saveConfig('lastOrderId', data.order_id);
|
||||||
hideResult();
|
hideResult();
|
||||||
} else {
|
} else {
|
||||||
showToast(data.error || CONFIG.lang.error, 'error');
|
showToast(data.error || CONFIG.lang.error, 'error');
|
||||||
|
|
@ -856,6 +892,8 @@
|
||||||
|
|
||||||
pauseScanner();
|
pauseScanner();
|
||||||
|
|
||||||
|
const savedSupplierId = loadConfig().lastFreetextSupplierId;
|
||||||
|
|
||||||
const modal = document.createElement('div');
|
const modal = document.createElement('div');
|
||||||
modal.className = 'freetext-modal';
|
modal.className = 'freetext-modal';
|
||||||
modal.id = 'freetext-modal';
|
modal.id = 'freetext-modal';
|
||||||
|
|
@ -866,7 +904,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label>Lieferant</label>
|
<label>Lieferant</label>
|
||||||
<select id="freetext-supplier">
|
<select id="freetext-supplier">
|
||||||
${allSuppliers.map(s => `<option value="${s.id}" ${s.id == lastFreetextSupplierId ? 'selected' : ''}>${escapeHtml(s.name)}</option>`).join('')}
|
${allSuppliers.map(s => `<option value="${s.id}">${escapeHtml(s.name)}</option>`).join('')}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -892,6 +930,17 @@
|
||||||
`;
|
`;
|
||||||
document.body.appendChild(modal);
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
|
// Gespeicherten Lieferant per JS setzen
|
||||||
|
const supplierSelect = document.getElementById('freetext-supplier');
|
||||||
|
if (savedSupplierId) {
|
||||||
|
supplierSelect.value = savedSupplierId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bei jeder Aenderung sofort speichern
|
||||||
|
supplierSelect.addEventListener('change', function() {
|
||||||
|
saveConfig('lastFreetextSupplierId', this.value);
|
||||||
|
});
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
modal.remove();
|
modal.remove();
|
||||||
resumeScanner();
|
resumeScanner();
|
||||||
|
|
@ -923,8 +972,9 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lieferant merken für nächstes Mal
|
// Lieferant merken für nächstes Mal (auch über Neustarts hinweg)
|
||||||
lastFreetextSupplierId = supplierId;
|
lastFreetextSupplierId = supplierId;
|
||||||
|
saveConfig('lastFreetextSupplierId', supplierId);
|
||||||
|
|
||||||
showLoading();
|
showLoading();
|
||||||
|
|
||||||
|
|
@ -942,6 +992,7 @@
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
showToast(`Hinzugefügt: ${description} (${qty}x)`, 'success');
|
showToast(`Hinzugefügt: ${description} (${qty}x)`, 'success');
|
||||||
lastOrderId = data.order_id;
|
lastOrderId = data.order_id;
|
||||||
|
saveConfig('lastOrderId', data.order_id);
|
||||||
if (closeModalCallback) closeModalCallback();
|
if (closeModalCallback) closeModalCallback();
|
||||||
} else {
|
} else {
|
||||||
showToast(data.error || CONFIG.lang.error, 'error');
|
showToast(data.error || CONFIG.lang.error, 'error');
|
||||||
|
|
|
||||||
33
pwa.php
33
pwa.php
|
|
@ -53,7 +53,7 @@ $colormain = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#0077b3');
|
||||||
<title>Barcode Scanner</title>
|
<title>Barcode Scanner</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link rel="apple-touch-icon" href="img/icon-192.png">
|
<link rel="apple-touch-icon" href="img/icon-192.png">
|
||||||
<link rel="stylesheet" href="css/scanner.css?v=60">
|
<link rel="stylesheet" href="css/scanner.css?v=61">
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
@ -314,6 +314,7 @@ $colormain = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#0077b3');
|
||||||
aspect-ratio: 4/3;
|
aspect-ratio: 4/3;
|
||||||
max-height: 35vh;
|
max-height: 35vh;
|
||||||
background: #000;
|
background: #000;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#scanner-video {
|
#scanner-video {
|
||||||
|
|
@ -351,26 +352,44 @@ $colormain = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#0077b3');
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scan-btn {
|
.scan-btn {
|
||||||
padding: 10px 20px;
|
padding: 12px 28px;
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
filter: brightness(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: wait;
|
||||||
|
transform: none;
|
||||||
|
filter: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scan-btn.start {
|
.scan-btn.start {
|
||||||
background: var(--primary);
|
background: var(--primary);
|
||||||
color: white;
|
color: white;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 119, 179, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scan-btn.stop {
|
.scan-btn.stop {
|
||||||
background: var(--danger);
|
background: #e53935;
|
||||||
color: white;
|
color: white;
|
||||||
|
box-shadow: 0 2px 8px rgba(229, 57, 53, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tool buttons next to scan button */
|
/* Tool buttons next to scan button */
|
||||||
|
|
@ -1280,6 +1299,6 @@ $colormain = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#0077b3');
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.8.4/dist/quagga.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.8.4/dist/quagga.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.6/dist/JsBarcode.all.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.6/dist/JsBarcode.all.min.js"></script>
|
||||||
<script src="<?php echo dol_buildpath('/handybarcodescanner/js/scanner.js', 1); ?>?v=60"></script>
|
<script src="<?php echo dol_buildpath('/handybarcodescanner/js/scanner.js', 1); ?>?v=61"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
20
sw.js
20
sw.js
|
|
@ -1,5 +1,5 @@
|
||||||
// Service Worker for HandyBarcodeScanner PWA
|
// Service Worker for HandyBarcodeScanner PWA
|
||||||
const CACHE_NAME = 'scanner-v6.0';
|
const CACHE_NAME = 'scanner-v6.1';
|
||||||
const ASSETS = [
|
const ASSETS = [
|
||||||
'pwa.php',
|
'pwa.php',
|
||||||
'css/scanner.css',
|
'css/scanner.css',
|
||||||
|
|
@ -60,7 +60,11 @@ self.addEventListener('fetch', event => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache first for static assets
|
// Eigene Assets: Network first, Cache als Fallback (damit Updates sofort ankommen)
|
||||||
|
// CDN-Assets: Cache first (aendern sich nie dank versionierter URL)
|
||||||
|
const isCDN = url.hostname !== location.hostname;
|
||||||
|
|
||||||
|
if (isCDN) {
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
caches.match(event.request).then(cached => {
|
caches.match(event.request).then(cached => {
|
||||||
return cached || fetch(event.request).then(response => {
|
return cached || fetch(event.request).then(response => {
|
||||||
|
|
@ -72,6 +76,18 @@ self.addEventListener('fetch', event => {
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// Network first fuer eigene JS/CSS/Bilder
|
||||||
|
event.respondWith(
|
||||||
|
fetch(event.request).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
const clone = response.clone();
|
||||||
|
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}).catch(() => caches.match(event.request))
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for messages from main app
|
// Listen for messages from main app
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue