diff --git a/db/init.sql b/db/init.sql index 9ff0e87..0969941 100644 --- a/db/init.sql +++ b/db/init.sql @@ -89,6 +89,20 @@ CREATE TABLE IF NOT EXISTS `audit_log` ( KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- Fase 3 (priority 2): saved color/style presets per user +CREATE TABLE IF NOT EXISTS `qr_presets` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(25) NOT NULL, + `name` varchar(50) NOT NULL, + `foreground` varchar(10) NOT NULL, + `background` varchar(10) NOT NULL, + `level` varchar(1) NOT NULL DEFAULT 'L', + `size` int(10) unsigned NOT NULL DEFAULT 200, + `created_at` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/db/migrations/005_qr_presets.sql b/db/migrations/005_qr_presets.sql new file mode 100644 index 0000000..9c0ce49 --- /dev/null +++ b/db/migrations/005_qr_presets.sql @@ -0,0 +1,14 @@ +-- Fase 3 (priority 2): saved color/style presets per user. + +CREATE TABLE IF NOT EXISTS `qr_presets` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(25) NOT NULL, + `name` varchar(50) NOT NULL, + `foreground` varchar(10) NOT NULL, + `background` varchar(10) NOT NULL, + `level` varchar(1) NOT NULL DEFAULT 'L', + `size` int(10) unsigned NOT NULL DEFAULT 200, + `created_at` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/src/dist/img/icon-192.png b/src/dist/img/icon-192.png new file mode 100644 index 0000000..ae8e358 Binary files /dev/null and b/src/dist/img/icon-192.png differ diff --git a/src/dist/img/icon-512.png b/src/dist/img/icon-512.png new file mode 100644 index 0000000..7f11c06 Binary files /dev/null and b/src/dist/img/icon-512.png differ diff --git a/src/dist/js/qrcode-style-tools.js b/src/dist/js/qrcode-style-tools.js new file mode 100644 index 0000000..a488750 --- /dev/null +++ b/src/dist/js/qrcode-style-tools.js @@ -0,0 +1,169 @@ +// Preset system + random style button for the qr code generation forms (Fase 3, priority 2). +(function () { + function csrfToken() { + var meta = document.querySelector('meta[name="csrf-token"]'); + return meta ? meta.getAttribute('content') : ''; + } + + function setColor(id, hex) { + var input = document.getElementById(id); + if (!input) { + return; + } + input.value = hex; + input.dispatchEvent(new Event('change')); + try { + // Sync the bootstrap-colorpicker widget/swatch if it was initialized on this input. + if (window.jQuery) { + jQuery(input).colorpicker('setValue', hex); + } + } catch (e) { + // Colorpicker not initialized on this page - the raw value above is still correct. + } + } + + function randomHexColor() { + var value = Math.floor(Math.random() * 0xFFFFFF).toString(16); + return '#' + ('000000' + value).slice(-6); + } + + function loadPresets() { + var select = document.getElementById('preset_select'); + if (!select) { + return; + } + + fetch('presets.php?action=list') + .then(function (response) { return response.json(); }) + .then(function (json) { + if (json.status !== 200) { + return; + } + json.data.forEach(function (preset) { + var option = document.createElement('option'); + option.value = preset.id; + option.textContent = preset.name; + option.dataset.foreground = preset.foreground; + option.dataset.background = preset.background; + option.dataset.level = preset.level; + option.dataset.size = preset.size; + select.appendChild(option); + }); + }) + .catch(function () { /* presets are a nice-to-have, fail silently */ }); + } + + document.addEventListener('DOMContentLoaded', function () { + loadPresets(); + + var randomBtn = document.getElementById('random_style_btn'); + if (randomBtn) { + randomBtn.addEventListener('click', function () { + setColor('foreground', randomHexColor()); + setColor('background', randomHexColor()); + }); + } + + var presetSelect = document.getElementById('preset_select'); + if (presetSelect) { + presetSelect.addEventListener('change', function () { + var option = presetSelect.options[presetSelect.selectedIndex]; + if (!option.value) { + return; + } + + setColor('foreground', option.dataset.foreground); + setColor('background', option.dataset.background); + + var levelSelect = document.querySelector('select[name="level"]'); + if (levelSelect) { + levelSelect.value = option.dataset.level; + } + + var sizeSelect = document.getElementById('size'); + if (sizeSelect) { + sizeSelect.value = option.dataset.size; + } + }); + } + + var saveBtn = document.getElementById('preset_save_btn'); + if (saveBtn) { + saveBtn.addEventListener('click', function () { + var nameInput = document.getElementById('preset_name'); + var name = nameInput.value.trim(); + + if (!name) { + alert('Enter a name for this preset first.'); + return; + } + + var body = new URLSearchParams(); + body.set('action', 'save'); + body.set('name', name); + body.set('foreground', document.getElementById('foreground').value); + body.set('background', document.getElementById('background').value); + body.set('level', document.querySelector('select[name="level"]').value); + body.set('size', document.getElementById('size').value); + + fetch('presets.php', { + method: 'POST', + headers: { 'X-CSRF-Token': csrfToken() }, + body: body + }) + .then(function (response) { return response.json(); }) + .then(function (json) { + if (json.status !== 200) { + alert('Could not save preset: ' + json.data); + return; + } + + var option = document.createElement('option'); + option.value = json.data.id; + option.textContent = json.data.name; + option.dataset.foreground = document.getElementById('foreground').value; + option.dataset.background = document.getElementById('background').value; + option.dataset.level = document.querySelector('select[name="level"]').value; + option.dataset.size = document.getElementById('size').value; + presetSelect.appendChild(option); + presetSelect.value = option.value; + nameInput.value = ''; + }) + .catch(function () { alert('Could not save preset (network error).'); }); + }); + } + + var deleteBtn = document.getElementById('preset_delete_btn'); + if (deleteBtn) { + deleteBtn.addEventListener('click', function () { + var option = presetSelect.options[presetSelect.selectedIndex]; + if (!option.value) { + return; + } + + if (!confirm('Delete preset "' + option.textContent + '"?')) { + return; + } + + var body = new URLSearchParams(); + body.set('action', 'delete'); + body.set('id', option.value); + + fetch('presets.php', { + method: 'POST', + headers: { 'X-CSRF-Token': csrfToken() }, + body: body + }) + .then(function (response) { return response.json(); }) + .then(function (json) { + if (json.status === 200) { + option.remove(); + } else { + alert('Could not delete preset: ' + json.data); + } + }) + .catch(function () { alert('Could not delete preset (network error).'); }); + }); + } + }); +})(); diff --git a/src/forms/form_dynamic_add.php b/src/forms/form_dynamic_add.php index 6a40776..b6e424e 100644 --- a/src/forms/form_dynamic_add.php +++ b/src/forms/form_dynamic_add.php @@ -35,7 +35,7 @@
- @@ -51,13 +51,47 @@
+
+
+
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ + + - +
@@ -94,6 +128,14 @@
+ +
+
+ + + Optional label rendered below the code. Only applies to PNG/JPEG/GIF, not SVG/EPS. +
+
diff --git a/src/forms/qrcode_options.php b/src/forms/qrcode_options.php index 9f8da2d..a9fbbd2 100644 --- a/src/forms/qrcode_options.php +++ b/src/forms/qrcode_options.php @@ -34,7 +34,7 @@
- @@ -54,14 +54,48 @@ if (QRCODE_GENERATOR === "internal-chillerlan.qrcode") {
- + +
+
+
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ + + - +
diff --git a/src/includes/footer.php b/src/includes/footer.php index 8dbb10f..d008efa 100644 --- a/src/includes/footer.php +++ b/src/includes/footer.php @@ -28,4 +28,10 @@ - \ No newline at end of file + + + \ No newline at end of file diff --git a/src/includes/head.php b/src/includes/head.php index 4948d85..054fbd9 100644 --- a/src/includes/head.php +++ b/src/includes/head.php @@ -2,6 +2,9 @@ + + + diff --git a/src/includes/sidebar.php b/src/includes/sidebar.php index 2eec17d..4b8775f 100644 --- a/src/includes/sidebar.php +++ b/src/includes/sidebar.php @@ -32,6 +32,14 @@ Dashboard

+ +