diff options
Diffstat (limited to 'public/projects/js-small-apps/calculator')
| -rw-r--r-- | public/projects/js-small-apps/calculator/README.md | 13 | ||||
| -rw-r--r-- | public/projects/js-small-apps/calculator/app.js | 265 | ||||
| -rw-r--r-- | public/projects/js-small-apps/calculator/index.html | 103 | ||||
| -rw-r--r-- | public/projects/js-small-apps/calculator/style.css | 147 |
4 files changed, 528 insertions, 0 deletions
diff --git a/public/projects/js-small-apps/calculator/README.md b/public/projects/js-small-apps/calculator/README.md new file mode 100644 index 0000000..ed8883b --- /dev/null +++ b/public/projects/js-small-apps/calculator/README.md @@ -0,0 +1,13 @@ +# Calculator + +A calculator implementation using HTML, CSS and JS. + +You can find more details about the implementation here: https://github.com/florinpop17/app-ideas/blob/master/Projects/1-Beginner/Calculator-App.md + +## Preview + +You can see a live preview here: https://demo.armandphilippot.com/#calculator + +## License + +This project is open-source and available under the [MIT License](../LICENSE). diff --git a/public/projects/js-small-apps/calculator/app.js b/public/projects/js-small-apps/calculator/app.js new file mode 100644 index 0000000..683a1e7 --- /dev/null +++ b/public/projects/js-small-apps/calculator/app.js @@ -0,0 +1,265 @@ +const appHistory = ["0"]; + +/** + * Check if a value is numeric. + * @param {String} value - A value to test. + * @returns {Boolean} True if value is numeric; false otherwise. + */ +function isNumeric(value) { + return !isNaN(value); +} + +/** + * Check if a value is an operation (+, -, *, /, =). + * @param {String} value - A value to test. + * @returns {Boolean} True if value is an operation; false otherwise. + */ +function isOperation(value) { + return "+-*/=".includes(value); +} + +/** + * Check if the value exceeds the limit of 8 characters. + * @param {String} value - The value to test. + * @returns True if the length is greater than 8; false otherwise. + */ +function isDigitsLimitReached(value) { + const digitsPart = value.split(".")[0]; + return digitsPart?.length > 8 ? true : false; +} + +/** + * Check if the decimal part exceeds the limit of 3 characters. + * @param {String} value - The value to test. + * @returns True if the decimal part is greater than 3; false otherwise. + */ +function isDecimalLimitReached(value) { + const decimalPart = value.split(".")[1]; + return decimalPart?.length > 3 ? true : false; +} + +/** + * Retrieve the last history value. + * @returns {String} The last history input. + */ +function getLastHistoryInput() { + return appHistory.slice(-1)[0]; +} + +/** + * Update the calculator display. + * @param {String} value - The value to print. + */ +function updateDisplay(value) { + const display = document.querySelector(".calculator__display"); + + if (isDigitsLimitReached(value) || isDecimalLimitReached(value)) { + display.textContent = "ERR"; + } else { + display.textContent = value; + } +} + +/** + * Calculate the result of an operation. + * @param {Number} number1 - The left number of operation. + * @param {Number} number2 - The right number of operation. + * @param {String} operation - An operation (+, -, *, /). + * @returns {Number} The operation result. + */ +function calculate(number1, number2, operation) { + let result; + + switch (operation) { + case "+": + result = number1 + number2; + break; + case "-": + result = number1 - number2; + break; + case "*": + result = number1 * number2; + break; + case "/": + result = number1 / number2; + default: + break; + } + + return result; +} + +/** + * Get the result of an operation. + * @returns {Number} The operation result. + */ +function getResult() { + const historyCopy = appHistory.slice(0); + const number2 = Number(historyCopy.pop()); + const operation = historyCopy.pop(); + const number1 = Number(historyCopy.pop()); + const result = calculate(number1, number2, operation); + + return result; +} + +/** + * Handle digit input. + * @param {String} value - The digit value. + */ +function handleDigits(value) { + const lastInput = getLastHistoryInput(); + const beforeLastInput = appHistory.slice(-2)[0]; + let newInput; + + if (isNaN(lastInput) || beforeLastInput === "=") { + newInput = value; + } else { + appHistory.pop(); + newInput = lastInput === "0" ? value : `${lastInput}${value}`; + } + + if (isDigitsLimitReached(newInput) || isDecimalLimitReached(newInput)) { + newInput = newInput.slice(0, -1); + } + + appHistory.push(newInput); + updateDisplay(newInput); +} + +/** + * Handle operation input. + * @param {String} value - The operation. + * @returns {void} + */ +function handleOperation(value) { + const lastInput = getLastHistoryInput(); + + if (isOperation(lastInput)) return; + + const result = getResult(); + + if (result) { + appHistory.push("="); + appHistory.push(`${result}`); + updateDisplay(`${result}`); + } + + if (value !== "=") appHistory.push(value); +} + +/** + * Handle number sign. + * @returns {void} + */ +function handleNumberSign() { + const lastInput = getLastHistoryInput(); + if (isNaN(lastInput)) return; + + const sign = Math.sign(lastInput); + if (sign === 0) return; + + appHistory.pop(); + let newInput; + + if (sign === 1) { + newInput = -Math.abs(lastInput); + } else if (sign === -1) { + newInput = Math.abs(lastInput); + } + + appHistory.push(`${newInput}`); + updateDisplay(`${newInput}`); +} + +/** + * Handle decimal. + */ +function handleDecimal() { + const lastInput = getLastHistoryInput(); + + if (lastInput.indexOf(".") === -1) { + appHistory.pop(); + const newInput = `${lastInput}.`; + appHistory.push(newInput); + updateDisplay(newInput); + } +} + +/** + * Clear the last input. + */ +function clear() { + appHistory.pop(); + + if (appHistory.length === 0) { + appHistory.push("0"); + updateDisplay("0"); + } else { + const reversedHistory = appHistory.slice(0).reverse(); + const lastNumericInput = reversedHistory.find((input) => isNumeric(input)); + updateDisplay(lastNumericInput); + + let lastInput = getLastHistoryInput(); + + while (lastNumericInput !== lastInput) { + appHistory.pop(); + lastInput = getLastHistoryInput(); + } + } +} + +/** + * Reset the calculator. + */ +function clearAll() { + appHistory.length = 0; + appHistory.push("0"); + updateDisplay("0"); +} + +/** + * Dispatch the event to the right function. + * @param {MouseEvent} e - The click event. + */ +function dispatch(e) { + const id = e.target.id; + const type = id.split("-")[0]; + const value = e.target.textContent.trim(); + + switch (type) { + case "digit": + handleDigits(value); + break; + case "operation": + handleOperation(value); + break; + case "sign": + handleNumberSign(); + break; + case "dot": + handleDecimal(); + break; + case "clear": + clear(); + break; + case "clearall": + clearAll(); + break; + default: + break; + } +} + +/** + * Listen all calculator buttons. + */ +function listen() { + const buttons = document.getElementsByClassName("btn"); + const buttonsArray = Array.from(buttons); + buttonsArray.forEach((btn) => { + btn.addEventListener("click", dispatch); + }); +} + +listen(); diff --git a/public/projects/js-small-apps/calculator/index.html b/public/projects/js-small-apps/calculator/index.html new file mode 100644 index 0000000..a93f0c4 --- /dev/null +++ b/public/projects/js-small-apps/calculator/index.html @@ -0,0 +1,103 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Calculator</title> + <link rel="stylesheet" href="style.css" /> + </head> + <body> + <header class="header"> + <h1 class="branding">Calculator</h1> + </header> + <main class="main"> + <div class="calculator"> + <div class="calculator__display">0</div> + <div class="calculator__entry-pad"> + <div class="calculator__clear"> + <button type="button" id="clear" class="btn btn--clear">C</button> + <button type="button" id="clearall" class="btn btn--clear"> + AC + </button> + </div> + <div class="calculator__digits"> + <button type="button" id="digit-9" class="btn btn--digits"> + 9 + </button> + <button type="button" id="digit-8" class="btn btn--digits"> + 8 + </button> + <button type="button" id="digit-7" class="btn btn--digits"> + 7 + </button> + <button type="button" id="digit-6" class="btn btn--digits"> + 6 + </button> + <button type="button" id="digit-5" class="btn btn--digits"> + 5 + </button> + <button type="button" id="digit-4" class="btn btn--digits"> + 4 + </button> + <button type="button" id="digit-3" class="btn btn--digits"> + 3 + </button> + <button type="button" id="digit-2" class="btn btn--digits"> + 2 + </button> + <button type="button" id="digit-1" class="btn btn--digits"> + 1 + </button> + <button type="button" id="digit-0" class="btn btn--digits"> + 0 + </button> + <button type="button" id="sign" class="btn btn--digits">+/-</button> + <button type="button" id="dot" class="btn btn--digits">.</button> + </div> + <div class="calculator__operations"> + <button + type="button" + id="operation-divide" + class="btn btn--operation" + > + / + </button> + <button + type="button" + id="operation-multiply" + class="btn btn--operation" + > + * + </button> + <button + type="button" + id="operation-minus" + class="btn btn--operation" + > + - + </button> + <button + type="button" + id="operation-plus" + class="btn btn--operation" + > + + + </button> + <button + type="button" + id="operation-equal" + class="btn btn--operation" + > + = + </button> + </div> + </div> + </div> + </main> + <footer class="footer"> + <p class="copyright">Calculator. MIT 2021. Armand Philippot.</p> + </footer> + <script src="app.js"></script> + </body> +</html> diff --git a/public/projects/js-small-apps/calculator/style.css b/public/projects/js-small-apps/calculator/style.css new file mode 100644 index 0000000..89b5b31 --- /dev/null +++ b/public/projects/js-small-apps/calculator/style.css @@ -0,0 +1,147 @@ +*, +*::after, +*::before { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + 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; +} + +.header, +.main, +.footer { + width: min(calc(100vw - 2rem), 350px); + margin-left: auto; + margin-right: auto; +} + +.header, +.main { + border: 3px solid hsl(219, 64%, 35%); + border-radius: 5px; + box-shadow: 2px 2px 0 0 hsl(219, 64%, 35%), 2px 2px 2px 0 hsl(219, 64%, 30%), + 3px 3px 3px 0 hsla(219, 64%, 25%, 0.65); +} + +.header { + background: hsl(219, 64%, 35%); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + color: hsl(0, 0%, 100%); + margin-top: 2rem; + padding: 0.5rem 1rem; +} + +.branding { + font-size: 1.5rem; + font-variant: small-caps; + letter-spacing: 1px; + text-align: center; + text-shadow: 1px 1px 0 hsl(0, 0%, 65%), 2px 2px 2px hsl(0, 0%, 0%); +} + +.main { + background: hsl(0, 0%, 97%); + border-top-left-radius: 0; + border-top-right-radius: 0; + + margin-bottom: 1rem; +} + +.footer { + font-size: 0.9rem; + text-align: center; + margin-top: auto; + padding: 1rem 0; +} + +.calculator { + padding: 1rem; +} + +.calculator__display { + background: hsl(0, 0%, 100%); + border: 1px solid hsl(0, 0%, 60%); + border-radius: 2px; + box-shadow: inset 0 0 2px 0 hsl(0, 0%, 70%), 0 0 0 1px hsl(0, 0%, 75%); + font-size: clamp(2rem, 3vw, 2.5rem); + font-weight: 600; + text-align: right; + width: 100%; + margin-bottom: 1rem; + padding: 0.2rem 1rem; +} + +.calculator__entry-pad { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: 1fr 6fr; + gap: 1rem; + justify-items: end; +} + +.calculator__clear { + grid-column: 3 / 5; + grid-row: 1; + display: flex; + flex-flow: row nowrap; + justify-content: flex-end; + gap: 1rem; +} + +.calculator__digits { + grid-column: 1 / 4; + grid-row: 2; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1rem; +} + +.calculator__operations { + grid-column: 4; + grid-row: 2; + display: flex; + flex-flow: column nowrap; + gap: 1rem; +} + +.btn { + display: block; + padding: clamp(0.1rem, 5vw, 0.3rem) clamp(1rem, 6vw, 1.5rem); + background: hsl(0, 0%, 95%); + border: 1px solid hsl(0, 0%, 85%); + border-radius: 3px; + box-shadow: 0 0 2px hsl(0, 0%, 80%), 0 0 0 2px hsl(0, 0%, 60%), + 1px 1px 0 3px hsl(0, 0%, 50%); + font-family: inherit; + font-size: inherit; + line-height: inherit; + cursor: pointer; + transition: all 0.15s ease-in-out 0s; +} + +.btn:hover, +.btn:focus { + background: hsl(0, 0%, 100%); +} + +.btn:focus { + outline: 3px solid hsl(219, 64%, 35%); +} + +.btn:active { + background: hsl(0, 0%, 90%); + box-shadow: 0 0 0 hsl(0, 0%, 80%), 0 0 0 2px hsl(0, 0%, 80%), + 0 0 0 3px hsl(0, 0%, 50%); + outline: none; + transform: translateX(2px) translateY(2px) scale(0.96); +} |
