Closed Mischback closed 1 year ago
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Colorizer</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="assets/style.css"> <script src="assets/colorizer.js" defer></script> </head> <body> <header> <h1>Colorizer</h1> <p>A tool to build color palettes while considering contrast values.</p> </header> <div id="ctrl"> <button id="ctrl-toggle">></button> <section id="color-add"> <form> <label for="new-color">New Color:</label> <input id="new-color" type="text" required> <button>Add</button> </form> <div id="color-palette"> <ul></ul> </div> </section> </div> <div id="contrast-grid"></div> </body> </html>
/* This is the actual engine that powers the web-based colorscheme builder. * * It stores colors of a palette in the browser, using IndexedDB. * * The colors of the palette are displayed in a matrix/grid and evaluated * considering their contrast values. * * Resources: * ---------- * - https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage#storing_complex_data_%E2%80%94_indexeddb */ // The database object let db; var color_palette_list = []; // Get DOM elements const DOM_color_palette_list = document.querySelector("#color-palette ul"); const DOM_color_add_form = document.querySelector("#color-add form"); const DOM_color_add_input = document.querySelector("#new-color"); const DOM_contrast_grid = document.querySelector("#contrast-grid"); // open an existing database or create a new one // // FIXME: Probably this should be wrapped in a function to make the site more // responsive, applying progressive enhancement methods const openRequest = window.indexedDB.open("colorizer_palette", 1); /** Error handler */ openRequest.addEventListener("error", () => { console.error("Could not open database"); }); /** The database was successfully opened, now process the content. */ openRequest.addEventListener("success", () => { console.log("Database opened successfully"); // store the handle to the database db = openRequest.result; // TODO: Trigger function to display the palette! updatePalette(); }); /** Initialize the desired database structure. */ openRequest.addEventListener("upgradeneeded", (e) => { db = e.target.result; // setup the objectStore const objectStore = db.createObjectStore("colors", { keyPath: "id", autoIncrement: true, }); objectStore.createIndex("color_hex", "color_hex", { unique: true }); objectStore.createIndex("color_r", "color_r", { unique: false }); objectStore.createIndex("color_g", "color_g", { unique: false }); objectStore.createIndex("color_b", "color_b", { unique: false }); console.log("Database setup completed"); }); function hexToRGB(hex) { let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? [ parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16) ] : null; } function w3Category(contrastValue) { switch(true) { case contrastValue >= 7: return "AAA"; break; case contrastValue >= 4.5: return "AA"; break; case contrastValue >= 3: return "A"; break; default: return "FAIL"; break; } } function luminance(r, g, b) { var a = [r, g, b].map(function(v) { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v+0.055) / 1.055, 2,4); }); return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; } function contrast(rgb1, rgb2) { var lum1 = luminance(rgb1[0], rgb1[1], rgb1[2]); var lum2 = luminance(rgb2[0], rgb2[1], rgb2[2]); var brighter = Math.max(lum1, lum2); var darker = Math.min(lum1, lum2); return (brighter + 0.05) / (darker + 0.05); } function buildContrastGrid(colorList, container) { /* Takes a list of hex colors (provided as strings) and builds a grid of * boxes with their respective contrast values. */ let grid_row; let this_container; let this_container_content; let container_text; // empty the existing palette to prevent duplicates while (container.firstChild) { container.removeChild(container.firstChild); } for (let i=0; i<colorList.length; i++) { console.log("Color: " + color_palette_list[i]); grid_row = document.createElement("div"); grid_row.classList.add("grid-row"); for (var j=0; j<colorList.length; j++) { console.log("Color: " + colorList[i]); this_container = document.createElement("div"); this_container.classList.add("grid-element"); this_container.style.cssText = "background-color: #" + colorList[i] + "; color: #" + colorList[j] + ";"; this_contrast = contrast(hexToRGB(colorList[i]), hexToRGB(colorList[j])) this_container_content = document.createElement("div"); this_container_content.classList.add("w3cat"); container_text = document.createTextNode(w3Category(this_contrast)); this_container_content.appendChild(container_text); this_container.appendChild(this_container_content); this_container_content = document.createElement("div"); this_container_content.classList.add("contrast-value"); container_text = document.createTextNode(this_contrast.toFixed(2)); this_container_content.appendChild(container_text); this_container.appendChild(this_container_content); this_container_content = document.createElement("div"); this_container_content.classList.add("color-value"); container_text = document.createTextNode(colorList[j]); this_container_content.appendChild(container_text); this_container.appendChild(this_container_content); grid_row.appendChild(this_container); } container.appendChild(grid_row); } } function generatePaletteItem(color_id, color_hex) { // create the general list element const listItem = document.createElement("li"); listItem.setAttribute("palette-color-id", color_id); // create a <span> to hold the color code as text const itemColorCode = document.createElement("span"); itemColorCode.textContent = "#" + color_hex; // create an empty <div> to visualize the color const itemColorPreview = document.createElement("div"); itemColorPreview.style.cssText = "background-color: #" + color_hex + ";"; // create a <button> to delete the color from the palette const itemColorDelete = document.createElement("button"); itemColorDelete.textContent = "remove"; itemColorDelete.addEventListener("click", deleteColorFromPalette); listItem.appendChild(itemColorCode); listItem.appendChild(itemColorPreview); listItem.appendChild(itemColorDelete); return listItem; } function deleteColorFromPalette(e) { const color_id = Number(e.target.parentNode.getAttribute("palette-color-id")); const transaction = db.transaction(["colors"], "readwrite"); const objectStore = transaction.objectStore("colors"); const deleteRequest = objectStore.delete(color_id); transaction.addEventListener("complete", () => { e.target.parentNode.parentNode.removeChild(e.target.parentNode); console.log("Color deleted"); // TODO: Trigger function to display the palette! updatePalette(); }); } function updatePalette() { // empty the existing palette to prevent duplicates while (DOM_color_palette_list.firstChild) { DOM_color_palette_list.removeChild(DOM_color_palette_list.firstChild); } color_palette_list = []; const transaction = db.transaction("colors"); transaction.addEventListener("complete", () => { buildContrastGrid(color_palette_list, DOM_contrast_grid); }); const objectStore = transaction.objectStore("colors"); objectStore.openCursor().addEventListener("success", (e) => { const cursor = e.target.result; if (cursor) { DOM_color_palette_list.appendChild(generatePaletteItem(cursor.value.id, cursor.value.color_hex)); color_palette_list.push(cursor.value.color_hex); // iterate to the next item cursor.continue(); } }); } // Hook into the DOM DOM_color_add_form.addEventListener("submit", (e) => { // don't actually submit the form, intercept with this code e.preventDefault(); let color = hexToRGB(DOM_color_add_input.value); if (color === null) // leave the function if the input can not be parsed as hex color code return; const new_item = { color_hex: DOM_color_add_input.value, color_r: color[0], color_g: color[1], color_b: color[2] }; const transaction = db.transaction(["colors"], "readwrite"); const objectStore = transaction.objectStore("colors"); const addRequest = objectStore.add(new_item); addRequest.addEventListener("success", () => { // clear the form's field DOM_color_add_input.value = ""; }); transaction.addEventListener("complete", () => { console.log("Transaction completed, database modification finished"); // TODO: Trigger function to display the palette! updatePalette(); }); transaction.addEventListener("error", () => { console.error("Transaction not opened due to error"); }); }); /**** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***/ // NEW SHIT! const DOM_ctrl_container = document.querySelector("#ctrl"); const DOM_ctrl_toggle = document.querySelector("#ctrl-toggle"); DOM_ctrl_toggle.addEventListener("click", (e) => { e.preventDefault(); if (DOM_ctrl_toggle.textContent === "<") { DOM_ctrl_toggle.textContent = ">"; DOM_ctrl_container.style.cssText = ""; } else { DOM_ctrl_toggle.textContent = "<"; DOM_ctrl_container.style.cssText = "left: 0;"; } });
* { margin: 0; padding: 0; } body { font-family: Verdana, sans-serif; background-color: #868686; color: #000; box-sizing: border-box; } #contrast-grid { display: inline-block; margin: 1em 1em 1em 3rem; padding: 1em; } .grid-element { padding: 0.5em; font-size: 0.7em; display: inline-block; width: 6em; height: 6em; text-align: right; border: 1px solid #000; } .grid-element .w3cat { font-weight: bold; font-size: 2em; } .grid-element .color-value { font-weight: bold; } /* THIS IS NEW SHIT */ header { margin: 1px 0 1rem 0; padding: 0.5rem 1rem 1rem 1rem; position: relative; text-align: right; background: linear-gradient(to top, #868686, rgba(0, 0, 0, 60%) 5px, #999 7px); } #ctrl { position: absolute; top: 1rem; left: -50vw; width: 50vw; height: calc(100vh - 5rem); background-color: #444; color: #fefefe; padding: 1em; box-shadow: inset 0 0 3px 2px rgba(0, 0, 0, 60%); border-top-right-radius: 1.5rem; border-bottom-right-radius: 0.5rem; } #color-add { margin: 0.5em 0 1em 0; } #color-add form label { display: block; font-size: 0.7em; } #color-add form input { border: none; border-bottom: 1px solid #fefefe; background-color: #868686; padding: 0.5em 1em; font-size: 1.2em; border-radius: 5px; } #color-add form button { font-size: 1.2em; padding: 0.5em 1em; } #ctrl-toggle { position: absolute; top: 1.75rem; right: -1rem; font-size: 1.5rem; } button { color: #333; border: none; box-shadow: 0 0 3px 2px rgba(0, 0, 0, 60%); background: linear-gradient(to bottom, #f9f9f9 5%, #e9e9e9 15% 40%, #d0d0d0 50% 90%, #666 95%); border-radius: 5px; font-weight: bold; padding: 0.25em 0.5em; } button:hover { background: linear-gradient(to bottom, #eedd99 5%, #ffcc00 15% 40%, #eeaa00 50% 90%, #666 95%); } #color-palette ul { margin: 3em 0; padding: 0; list-style: none; } #color-palette ul li { display: inline-block; background-color: #f00; color: #000; padding: 0.25em 0.5em; margin: 0.25em 0.5em; border-radius: 5px; background-color: #888; } #color-palette ul li span { display: inline-block; vertical-align: middle; font-family: monospace; font-size: 1.1em; } #color-palette ul li button { vertical-align: middle; } #color-palette ul li div { display: inline-block; vertical-align: middle; width: 1.5em; height: 1.5em; border: 1px solid rgba(0, 0, 0, 60%); border-radius: 5px; margin: 0 0.5em; }
processed...