From 73a5c7fae9ffbe9ada721148c8c454a643aceebe Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sun, 20 Feb 2022 16:11:50 +0100 Subject: chore!: restructure repo I separated public files from the config/dev files. It improves repo readability. I also moved dotenv helper to public/inc directory and extract the Matomo tracker in the same directory. --- .../projects/js-small-apps/color-cycle/README.md | 13 ++ public/projects/js-small-apps/color-cycle/app.js | 208 +++++++++++++++++++++ .../projects/js-small-apps/color-cycle/index.html | 117 ++++++++++++ .../projects/js-small-apps/color-cycle/style.css | 132 +++++++++++++ 4 files changed, 470 insertions(+) create mode 100644 public/projects/js-small-apps/color-cycle/README.md create mode 100644 public/projects/js-small-apps/color-cycle/app.js create mode 100644 public/projects/js-small-apps/color-cycle/index.html create mode 100644 public/projects/js-small-apps/color-cycle/style.css (limited to 'public/projects/js-small-apps/color-cycle') diff --git a/public/projects/js-small-apps/color-cycle/README.md b/public/projects/js-small-apps/color-cycle/README.md new file mode 100644 index 0000000..e9d5970 --- /dev/null +++ b/public/projects/js-small-apps/color-cycle/README.md @@ -0,0 +1,13 @@ +# Color Cycle + +An app to cycle a color value through incremental changes. In other words: play with hexadecimal colors! + +You can find more details about the implementation here: https://github.com/florinpop17/app-ideas/blob/master/Projects/1-Beginner/Color-Cycle-App.md + +## Preview + +You can see a live preview here: https://demo.armandphilippot.com/#color-cycle + +## License + +This project is open-source and available under the [MIT License](../LICENSE). diff --git a/public/projects/js-small-apps/color-cycle/app.js b/public/projects/js-small-apps/color-cycle/app.js new file mode 100644 index 0000000..2ed8e7f --- /dev/null +++ b/public/projects/js-small-apps/color-cycle/app.js @@ -0,0 +1,208 @@ +let isRunning = false; +let intervalId; + +/** + * Check if the provided value is a valid hexadecimal. + * @param {String} value - Two hexadecimal symbols. + * @returns {Boolean} True if value is a valid hexadecimal ; false otherwise. + */ +function isValidHex(value) { + const hexSymbols = "0123456789ABCDEF"; + const hexArray = value.split(""); + if (hexArray.length !== 2) return false; + if (!hexSymbols.includes(hexArray[0].toUpperCase())) return false; + if (!hexSymbols.includes(hexArray[1].toUpperCase())) return false; + return true; +} + +/** + * Print a message to notify user. + * @param {String} msg A message to print. + */ +function notify(msg) { + const body = document.querySelector(".body"); + const notification = document.createElement("div"); + notification.classList.add("notification"); + notification.textContent = msg; + body.appendChild(notification); + + setTimeout(() => { + notification.remove(); + }, 3000); +} + +/** + * Check if colors are set and are valid hexadecimal. + * @param {String} red - Hexadecimal string for red color. + * @param {String} green - Hexadecimal string for green color. + * @param {String} blue - Hexadecimal string for blue color. + * @returns {Boolean} True if colors are ready; false otherwise. + */ +function areColorsReady(red, green, blue) { + if (!red || !green || !blue) return false; + + if (!isValidHex(red)) { + notify("Red is not hexadecimal."); + return false; + } + + if (!isValidHex(green)) { + notify("Green is not hexadecimal."); + return false; + } + + if (!isValidHex(blue)) { + notify("Blue is not hexadecimal."); + return false; + } + + return true; +} + +/** + * Update the preview color with the provided color. + * @param {String} color - Hexadecimal string with a leading hash (CSS format). + */ +function updatePreviewColor(color) { + const preview = document.querySelector(".preview"); + preview.style.backgroundColor = color; +} + +/** + * Initialize the preview with user settings. + * @param {Object} ui - The different element corresponding to the user interface. + */ +function setPreview(ui) { + const red = ui.colors.red.value; + const green = ui.colors.green.value; + const blue = ui.colors.blue.value; + + if (areColorsReady(red, green, blue)) { + const currentColor = `#${red}${green}${blue}`; + updatePreviewColor(currentColor); + } +} + +/** + * Generate a new color. + * @param {Integer} color - An integer corresponding to a RGB value. + * @param {Integer} increment - Add this value to color argument. + */ +function* getColor(color, increment) { + let nextColor = color; + yield nextColor; + + while (true) { + if (nextColor + increment > 255) { + nextColor = nextColor - 255 + increment; + } else { + nextColor = nextColor + increment; + } + + yield nextColor; + } +} + +/** + * Convert an RGB color to hexadecimal format. + * @param {Integer} red - An integer representing red color with RGB format. + * @param {Integer} green - An integer representing green color with RGB format. + * @param {Integer} blue - A value representing blue color with RGB format. + * @returns {String} The color in hexadecimal format with a leading hash. + */ +function getNexHexColor(red, green, blue) { + return `#${red.toString(16)}${green.toString(16)}${blue.toString(16)}`; +} + +/** + * Disable or enable inputs. + * @param {Object} ui - The HTMLElements corresponding to user interface. + */ +function toggleInputs(ui) { + ui.colors.red.disabled = isRunning; + ui.colors.green.disabled = isRunning; + ui.colors.blue.disabled = isRunning; + ui.increments.red.disabled = isRunning; + ui.increments.green.disabled = isRunning; + ui.increments.blue.disabled = isRunning; + ui.interval.disabled = isRunning; +} + +/** + * Start or stop the preview. + * @param {Object} ui - The HTMLElements corresponding to user interface. + */ +function start(ui) { + const red = ui.colors.red.value; + const green = ui.colors.green.value; + const blue = ui.colors.blue.value; + + if (areColorsReady(red, green, blue)) { + isRunning = !isRunning; + ui.button.textContent = isRunning ? "Stop" : "Start"; + } else { + notify("Colors are not correctly set."); + } + + const redIncrement = Number(ui.increments.red.value); + const greenIncrement = Number(ui.increments.green.value); + const blueIncrement = Number(ui.increments.blue.value); + const redGenerator = getColor(parseInt(red, 16), redIncrement); + const greenGenerator = getColor(parseInt(green, 16), greenIncrement); + const blueGenerator = getColor(parseInt(blue, 16), blueIncrement); + const timing = ui.interval.value; + + if (isRunning) { + toggleInputs(ui); + intervalId = setInterval(() => { + const nextRed = redGenerator.next().value; + const nextGreen = greenGenerator.next().value; + const nextBlue = blueGenerator.next().value; + const newColor = getNexHexColor(nextRed, nextGreen, nextBlue); + updatePreviewColor(newColor); + }, timing); + } else { + toggleInputs(ui); + clearInterval(intervalId); + } +} + +/** + * Listen form, buttons and inputs. + * @param {Object} ui - The HTMLElements corresponding to user interface. + */ +function listen(ui) { + ui.form.addEventListener("submit", (e) => { + e.preventDefault(); + }); + ui.button.addEventListener("click", () => start(ui)); + ui.colors.red.addEventListener("change", () => setPreview(ui)); + ui.colors.green.addEventListener("change", () => setPreview(ui)); + ui.colors.blue.addEventListener("change", () => setPreview(ui)); +} + +/** + * Initialize the app. + */ +function init() { + const ui = { + button: document.querySelector(".btn"), + form: document.querySelector(".form"), + colors: { + red: document.getElementById("color-red"), + green: document.getElementById("color-green"), + blue: document.getElementById("color-blue"), + }, + increments: { + red: document.getElementById("increment-red"), + green: document.getElementById("increment-green"), + blue: document.getElementById("increment-blue"), + }, + interval: document.getElementById("time-interval"), + }; + + setPreview(ui); + listen(ui); +} + +init(); diff --git a/public/projects/js-small-apps/color-cycle/index.html b/public/projects/js-small-apps/color-cycle/index.html new file mode 100644 index 0000000..bb99787 --- /dev/null +++ b/public/projects/js-small-apps/color-cycle/index.html @@ -0,0 +1,117 @@ + + + + + + + Color Cycle + + + +
+

Color Cycle

+
+
+
+
+
+ +
+
+
+ Hex Color +
+ +
+
+ +
+
+ +
+
+
+ Increment +
+ +
+
+ +
+
+ +
+
+
+ Time +
+ + +
+
+
+
+
+ + + + diff --git a/public/projects/js-small-apps/color-cycle/style.css b/public/projects/js-small-apps/color-cycle/style.css new file mode 100644 index 0000000..62e0e47 --- /dev/null +++ b/public/projects/js-small-apps/color-cycle/style.css @@ -0,0 +1,132 @@ +*, +*::after, +*::before { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background: #fff; + color: #000; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + font-size: 16px; + line-height: 1.618; + display: flex; + flex-flow: column nowrap; + min-height: 100vh; +} + +.main { + flex: 1; + margin: 3rem; + position: relative; +} + +.header, +.main, +.footer { + width: min(calc(100vw - 2rem), 80ch); + margin-left: auto; + margin-right: auto; +} + +.header, +.footer { + padding: 1rem 0; +} + +.branding { + color: hsl(219, 64%, 35%); + text-align: center; +} + +.copyright { + font-size: 0.9rem; + text-align: center; +} + +.color-cycle { + display: flex; + flex-flow: row wrap; + gap: 2rem; + align-items: center; +} + +.color-cycle__preview, +.color-cycle__settings { + flex: 1 1 min(calc(100vw - 2rem), calc(80ch / 2 - 1rem)); +} + +.preview { + border: 1px solid #ccc; + height: 20rem; + margin-bottom: 2rem; +} + +.form__fieldset { + display: flex; + flex-flow: row wrap; + gap: 1rem; + margin: 1rem 0; + padding: 0.8rem 1rem 1.2rem; +} + +.form__legend { + color: hsl(219, 64%, 35%); + font-size: 0.9rem; + font-weight: 600; + text-transform: uppercase; + padding: 0 0.8rem; +} + +.form__label { + display: block; + cursor: pointer; +} + +.form__input { + padding: 0.2rem 0.5rem; +} + +.btn, +.form__input { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.btn { + background: #fff; + border: 2px solid hsl(219, 64%, 35%); + border-radius: 5px; + color: hsl(219, 64%, 35%); + font-weight: 600; + cursor: pointer; + display: block; + margin: auto; + padding: 0.5rem 1rem; +} + +.btn:hover, +.btn:focus { + background: hsl(219, 64%, 35%); + color: #fff; +} + +.btn:active { + background: hsl(219, 64%, 25%); + color: #fff; +} + +.notification { + background: #fff; + border: 2px solid hsl(0, 64%, 35%); + color: hsl(0, 64%, 35%); + font-weight: 600; + padding: 1rem; + position: absolute; + right: 1rem; + bottom: 1rem; +} -- cgit v1.2.3