diff --git a/images/settings.svg b/images/settings.svg new file mode 100644 index 0000000..0870c30 --- /dev/null +++ b/images/settings.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/index.html b/index.html index cafb356..c8f5b2d 100644 --- a/index.html +++ b/index.html @@ -7,24 +7,33 @@ -

FabAccess Sticker Generator

-
- +
+

FabAccess Sticker Generator

- + -
- - - - +
+ + + + +
+ + + + +
+ +
- - -
+
+ + diff --git a/index.mjs b/index.mjs index 7c9639e..7e91f52 100644 --- a/index.mjs +++ b/index.mjs @@ -1,18 +1,37 @@ import SVGRenderer from '/src/SVGRenderer.mjs' import PNGRenderer from '/src/PNGRenderer.mjs' import PDFRenderer from '/src/PDFRenderer.mjs' +import ColorSettings from '/src/ColorSettings.mjs' // Get references to the custom elements const box = document.querySelector('fabaccess-preview-box') const form = document.querySelector('fabaccess-settings-form') +const colorSettings = document.querySelector('fabaccess-color-settings') +// Event listener for changes in the settings form form.addEventListener('change', (e) => { + // Update the optimizeForPrint option in the SVGRenderer SVGRenderer.optimizeForPrint = e.detail.optimizeForPrint + // Update the attributes of the preview box box.setAttribute('value', e.detail.machineID) box.setAttribute('size', e.detail.size) box.update() }) +// Event listener for changes in the color settings +colorSettings.addEventListener('colors-changed', (e) => { + // Apply the new colors to the document + const colors = e.detail.colors + for (const key in colors) { + document.documentElement.style.setProperty(`--${key}`, colors[key]) + } + // Update the preview box + box.update() +}) + +// Load colors from localStorage and apply them +ColorSettings.applyStoredColors() + // Add click event listeners to the buttons document.querySelector('#download-qr-code-svg').addEventListener('click', () => SVGRenderer.downloadSVG(form.machineID, form.size)) document.querySelector('#download-qr-code-png').addEventListener('click', () => PNGRenderer.downloadPNG(form.machineID, form.size)) @@ -22,3 +41,9 @@ document.querySelector('#download-pdf').addEventListener('click', () => PDFRende // Initialize the preview box box.setAttribute('value', form.machineID) box.setAttribute('size', form.size) + +// Show the settings dialog when the settings button is clicked +const settingsButton = document.querySelector('#settings-button') +settingsButton.addEventListener('click', () => { + colorSettings.show() +}) diff --git a/src/ColorSettings.css b/src/ColorSettings.css new file mode 100644 index 0000000..db8f842 --- /dev/null +++ b/src/ColorSettings.css @@ -0,0 +1,109 @@ +#dialog-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + display: none; + justify-content: center; + align-items: center; + z-index: 1000; +} + +#dialog { + background-color: #fff; + border-radius: 5px; + position: relative; + overflow: hidden; + align-items: center; + justify-content: center; + height: 100%; + max-width: 400px; + width: 90%; + height: auto; + display: flex; + flex-wrap: wrap; + padding: 40px 5px 20px 5px; +} + +#dialog::before { + content: 'Color Settings'; + position: absolute; + top: 0; + left: 0; + font-family: Arial, sans-serif; + text-align: center; + color: var(--primary-color); + font-weight: bold; + font-size: 12px; + height: 30px; + width: 100%; + background-color: var(--secondary-color); + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +.color-field { + padding: 10px; + background-color: white; + border-radius: 10px; + border: 1px solid var(--secondary-color); + height: 100px; + width: 100px; + margin: 5px; +} + +.color-field label { + display: block; + margin-bottom: 5px; +} + +input[type='color'] { + -webkit-appearance: none; + border: none; + width: 100px; + height: 60px; + border-radius: 5px; + background-color: white; +} + +input[type='color']::-webkit-color-swatch-wrapper { + padding: 0; +} + +input[type='color']::-webkit-color-swatch { + border-radius: 5px; + border: 1px solid var(--secondary-color); +} + +input[type='color']::-moz-color-swatch { + border-radius: 5px; + border: 1px solid var(--secondary-color); +} + +#dialog-buttons { + text-align: right; + margin-top: 20px; +} + +#dialog-buttons button { + margin-left: 10px; + background-color: var(--primary-color); + color: white; + border: none; + padding: 10px 20px; + text-decoration: none; + font-size: 16px; + border-radius: 5px; + cursor: pointer; + margin: 0; +} + +#dialog-buttons #cancel-button { + background-color: var(--secondary-color-light); +} diff --git a/src/ColorSettings.mjs b/src/ColorSettings.mjs new file mode 100644 index 0000000..0226565 --- /dev/null +++ b/src/ColorSettings.mjs @@ -0,0 +1,209 @@ +// Create a template element to define the structure of the color settings dialog +const template = document.createElement('template') +template.innerHTML = /* html */ ` + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+` + +// Define a custom ColorSettings class that extends HTMLElement +export default class ColorSettings extends HTMLElement { + #root = null // Private property to store the root node + + /** + * Creates an instance of ColorSettings and initializes the component. + */ + constructor() { + super() + + // Attach a shadow DOM tree to this element + this.attachShadow({ mode: 'open' }) + // Append the cloned template content to the shadow root + this.shadowRoot.append(template.content.cloneNode(true)) + + // Get the root node of the shadow DOM + this.#root = this.shadowRoot.getRootNode() + + // Get elements from the shadow DOM + this.overlay = this.#root.getElementById('dialog-overlay') + this.saveButton = this.#root.getElementById('save-button') + this.cancelButton = this.#root.getElementById('cancel-button') + + // Color input fields + this.stickerBackgroundColorInput = this.#root.getElementById('sticker-background-color') + this.stickerBorderColorInput = this.#root.getElementById('sticker-border-color') + this.iconColorInput = this.#root.getElementById('icon-color') + this.qrCodeBackgroundColorInput = this.#root.getElementById('qr-code-background-color') + this.qrCodeForegroundColorInput = this.#root.getElementById('qr-code-foreground-color') + this.labelColorInput = this.#root.getElementById('label-color') + + // Load colors from localStorage or use default colors + this.colors = ColorSettings.loadColors() + + // Set input values to current colors + this.stickerBackgroundColorInput.value = this.colors['sticker-background-color'] + this.stickerBorderColorInput.value = this.colors['sticker-border-color'] + this.iconColorInput.value = this.colors['icon-color'] + this.qrCodeBackgroundColorInput.value = this.colors['qr-code-background-color'] + this.qrCodeForegroundColorInput.value = this.colors['qr-code-foreground-color'] + this.labelColorInput.value = this.colors['label-color'] + + // Bind event listeners + this.saveButton.addEventListener('click', this.#handleSave.bind(this)) + this.cancelButton.addEventListener('click', this.#handleCancel.bind(this)) + + // Initially hide the dialog + this.hide() + } + + /** + * Shows the color settings dialog. + */ + show() { + this.overlay.style.display = 'flex' + } + + /** + * Hides the color settings dialog. + */ + hide() { + this.overlay.style.display = 'none' + } + + /** + * Loads colors from localStorage or uses default colors. + * @returns {Object} - An object containing the color settings. + */ + static loadColors() { + const colors = {} + for (const key in ColorSettings.defaultColors) { + colors[key] = localStorage.getItem(key) || ColorSettings.defaultColors[key] + } + return colors + } + + /** + * Saves the current colors to localStorage. + */ + saveColors() { + for (const key in this.colors) { + localStorage.setItem(key, this.colors[key]) + } + } + + /** + * Applies stored colors to the document's root element. + * This method retrieves colors from localStorage or uses default colors, + * and sets them as CSS variables on the root element. + */ + static applyStoredColors() { + const colors = ColorSettings.loadColors() + for (const key in colors) { + document.documentElement.style.setProperty(`--${key}`, colors[key]) + } + } + + /** + * Returns the default color values. + * @returns {Object} - An object containing the default color settings. + */ + static get defaultColors() { + const style = getComputedStyle(document.body) + return { + 'sticker-background-color': style.getPropertyValue('--secondary-color'), + 'sticker-border-color': style.getPropertyValue('--primary-color'), + 'icon-color': style.getPropertyValue('--primary-color'), + 'qr-code-background-color': '#ffffff', + 'qr-code-foreground-color': style.getPropertyValue('--secondary-color'), + 'label-color': style.getPropertyValue('--primary-color') + } + } + + /** + * Dispatches a custom event to notify that the colors have changed. + * @private + */ + #dispatchChangeEvent() { + this.dispatchEvent( + new CustomEvent('colors-changed', { + detail: { colors: this.colors }, + composed: true + }) + ) + } + + /** + * Handles the save button click event. + * Updates colors based on user input, saves them, and notifies listeners. + * @private + */ + #handleSave() { + // Update colors from input fields + this.colors['sticker-background-color'] = this.stickerBackgroundColorInput.value + this.colors['sticker-border-color'] = this.stickerBorderColorInput.value + this.colors['icon-color'] = this.iconColorInput.value + this.colors['qr-code-background-color'] = this.qrCodeBackgroundColorInput.value + this.colors['qr-code-foreground-color'] = this.qrCodeForegroundColorInput.value + this.colors['label-color'] = this.labelColorInput.value + + // Save colors to localStorage + this.saveColors() + + // Dispatch change event + this.#dispatchChangeEvent() + + // Hide the dialog + this.hide() + } + + /** + * Handles the cancel button click event. + * Resets input fields to previous colors and hides the dialog. + * @private + */ + #handleCancel() { + // Reset input fields to current colors + this.stickerBackgroundColorInput.value = this.colors['sticker-background-color'] + this.stickerBorderColorInput.value = this.colors['sticker-border-color'] + this.iconColorInput.value = this.colors['icon-color'] + this.qrCodeBackgroundColorInput.value = this.colors['qr-code-background-color'] + this.qrCodeForegroundColorInput.value = this.colors['qr-code-foreground-color'] + this.labelColorInput.value = this.colors['label-color'] + + // Hide the dialog + this.hide() + } +} + +// Define the custom element 'fabaccess-color-settings' associated with the ColorSettings class +customElements.define('fabaccess-color-settings', ColorSettings) diff --git a/src/SVGRenderer.mjs b/src/SVGRenderer.mjs index 91bc818..9446ef0 100644 --- a/src/SVGRenderer.mjs +++ b/src/SVGRenderer.mjs @@ -22,7 +22,7 @@ export default class SVGRenderer { mtx: -1, ecl: machineID?.value > 60 ? 'Q' : 'H', ecb: true, - pal: ['#000', 'transparent'], + pal: ['var(--qr-code-foreground-color)', 'var(--qr-code-background-color)'], vrb: true } @@ -38,27 +38,24 @@ export default class SVGRenderer { let svgCode = ` + fill="var(--sticker-border-color)"/> + fill="var(--sticker-background-color)"/> - - ${QRCode(data).outerHTML.replace(' + + ${QRCode(data).outerHTML.replace(' ` - if (this.optimizeForPrint) { - svgCode = Utils.replace(svgCode, { - 'var(--primary-color)': Utils.getCssValue('--primary-color-print'), - 'var(--secondary-color)': Utils.getCssValue('--secondary-color-print') - }) - } else { - svgCode = Utils.replace(svgCode, { - 'var(--primary-color)': Utils.getCssValue('--primary-color'), - 'var(--secondary-color)': Utils.getCssValue('--secondary-color') - }) - } + svgCode = Utils.replace(svgCode, { + 'var(--sticker-background-color)': Utils.getCssValue('--sticker-background-color'), + 'var(--sticker-border-color)': Utils.getCssValue('--sticker-border-color'), + 'var(--icon-color)': Utils.getCssValue('--icon-color'), + 'var(--qr-code-background-color)': Utils.getCssValue('--qr-code-background-color'), + 'var(--qr-code-foreground-color)': Utils.getCssValue('--qr-code-foreground-color'), + 'var(--label-color)': Utils.getCssValue('--label-color') + }) return svgCode } diff --git a/src/SettingsForm.mjs b/src/SettingsForm.mjs index dc6fcc8..c45f3c3 100644 --- a/src/SettingsForm.mjs +++ b/src/SettingsForm.mjs @@ -23,8 +23,9 @@ template.innerHTML = /* html */ `
- Optimize Colors for Laser printing` - + ` @@ -57,21 +58,25 @@ class SettingsForm extends HTMLElement { // Get form elements from the shadow DOM const machineIdInput = this.#root.getElementById('machineID') - const optimizeForPrint = this.#root.getElementById('optimize-for-laser-printer') - const size = this.#root.getElementById('size') + //const optimizeForPrintCheckbox = this.#root.getElementById('optimize-for-laser-printer') + const sizeSelect = this.#root.getElementById('size') // Add event listener for input changes machineIdInput.addEventListener('keyup', this.#handleInputChange.bind(this), { passive: false }) - size.addEventListener('change', this.#handleSelectChange.bind(this), { + // Add event listener for size selection changes + sizeSelect.addEventListener('change', this.#handleSelectChange.bind(this), { passive: false }) - optimizeForPrint.addEventListener('change', this.#handleCheckboxChange.bind(this), { + // Add event listener for checkbox changes + /* + optimizeForPrintCheckbox.addEventListener('change', this.#handleCheckboxChange.bind(this), { passive: false }) + */ // Set focus to the machine ID input after the element is rendered setTimeout(() => machineIdInput.focus(), 0) @@ -155,6 +160,7 @@ class SettingsForm extends HTMLElement { * @param {Event} e - The event object. * @private */ + /* #handleCheckboxChange(e) { e.stopPropagation() const val = e.detail.checked @@ -172,6 +178,7 @@ class SettingsForm extends HTMLElement { }) ) } + */ } // Define the custom element 'fabaccess-settings-form' associated with the SettingsForm class diff --git a/style.css b/style.css index 8d39b93..d026a32 100644 --- a/style.css +++ b/style.css @@ -5,12 +5,6 @@ --primary-color-light: color-mix(in srgb, var(--primary-color), white 20%); --secondary-color-dark: color-mix(in srgb, var(--secondary-color), black 20%); --secondary-color-light: color-mix(in srgb, var(--secondary-color), white 20%); - /* - --primary-color-print: oklch(from var(--primary-color) calc(l + 0.22) calc(c + 0.04) calc(h - 10)); - --secondary-color-print: oklch(from var(--secondary-color) calc(l + 0.07) c calc(h - 30)); - */ - --primary-color-print: #66ffde; - --secondary-color-print: #303d3e; --inline-start: left; --main-color: rgb(249 249 250); --toolbar-button-print-icon: url(/images/toolbar-button-print.svg); @@ -21,6 +15,13 @@ --toolbar-icon-bg-color: rgb(255 255 255); --toolbar-fg-color: rgb(255 255 255); --button-hover-color: rgb(102 102 103); + + --sticker-background-color: #ffffff; + --sticker-border-color: #000000; + --icon-color: #000000; + --qr-code-background-color: #ffffff; + --qr-code-foreground-color: #000000; + --label-color: #000000; } html { @@ -35,14 +36,23 @@ body { background-color: var(--secondary-color); } -h1 { +main { + display: grid; + grid-template-columns: 1fr auto; + grid-template-rows: auto auto; width: 80%; max-width: 900px; margin: 20px auto 0 auto; +} + +h1 { text-align: left; text-indent: -15px; color: var(--primary-color); font-size: 14px; + grid-column: 1; + grid-row: 1; + margin-left: 20px; } fabaccess-preview-box { @@ -51,10 +61,10 @@ fabaccess-preview-box { } #fabaccess-sticker-generator { + grid-column: 1 / span 2; + grid-row: 2; float: none; - width: auto; - width: 80%; - max-width: 900px; + width: calc(100% - 40px); margin: 5px auto 20px auto; padding: 20px; background-color: white; @@ -63,7 +73,7 @@ fabaccess-preview-box { } .download-buttons { - margin: 20px 0; + margin: 0 0 20px 0; } .download-buttons button { @@ -88,6 +98,58 @@ fabaccess-preview-box { gap: 10px; } +#settings-button { + position: relative; + grid-column: 2; + grid-row: 1; + background: none; + border: none; + font-size: 24px; + cursor: pointer; + outline: none; + font: message-box; + float: var(--inline-start); + min-width: 16px; + margin: 2px 1px; + padding: 2px 6px 0; + border: none; + border-radius: 2px; + color: var(--main-color); + font-size: 12px; + line-height: 14px; + user-select: none; + box-sizing: border-box; + background-color: #3c474d; + width: 28px; + height: 28px; + cursor: pointer; + margin-left: auto; + margin-right: 6px; +} + +#settings-button::before { + opacity: var(--toolbar-icon-opacity); + top: 6px; + left: 6px; + position: absolute; + display: inline-block; + width: 16px; + height: 16px; + content: ''; + background-color: var(--toolbar-icon-bg-color); + -webkit-mask-size: cover; + mask-size: cover; + -webkit-mask-image: url(/images/settings.svg); + mask-image: url(/images/settings.svg); +} + +#settings-button > span { + display: inline-block; + width: 0; + height: 0; + overflow: hidden; +} + @media (min-width: 620px) { fabaccess-preview-box { height: 300px; @@ -110,7 +172,8 @@ fabaccess-preview-box { fabaccess-settings-form { grid-row: 1; grid-column: 2; - margin: 0 0 20px 20px; + margin: 0 0 0 20px; + align-self: end; } .download-buttons {