From d2784afba08f4c7245bcfab0985a5c12769a9873 Mon Sep 17 00:00:00 2001 From: Prozilla Date: Sun, 3 Mar 2024 13:59:15 +0100 Subject: [PATCH] Added calculator app --- src/components/apps/calculator/Calculator.jsx | 210 +++++++++++++++++- .../apps/calculator/Calculator.module.css | 67 ++++++ src/config/apps.config.js | 9 +- src/features/apps/appsManager.js | 18 +- 4 files changed, 288 insertions(+), 16 deletions(-) diff --git a/src/components/apps/calculator/Calculator.jsx b/src/components/apps/calculator/Calculator.jsx index 0ef4897..60da984 100644 --- a/src/components/apps/calculator/Calculator.jsx +++ b/src/components/apps/calculator/Calculator.jsx @@ -1,7 +1,207 @@ -// import styles from "./Calculator.module.css"; +import { useCallback, useEffect, useState } from "react"; +import { Button } from "../../_utils/button/Button.jsx"; +import styles from "./Calculator.module.css"; -export function Calculator() { - return ( -
- ); +/** @type {import("../../windows/WindowView.jsx").windowProps} */ +export function Calculator({ active }) { + const [input, setInput] = useState("0"); + const [firstNumber, setFirstNumber] = useState(null); + const [secondNumber, setSecondNumber] = useState(null); + const [operation, setOperation] = useState(null); + const [isIntermediate, setIsIntermediate] = useState(false); + + const log = useCallback(() => { + console.info({ input, firstNumber, secondNumber, operation }); + }, [firstNumber, input, operation, secondNumber]); + + const reset = useCallback(() => { + setInput("0"); + setFirstNumber(null); + setSecondNumber(null); + setOperation(null); + + log(); + }, [log]); + + const addInput = useCallback((string) => { + let hasReset = false; + if (secondNumber != null) { + if (isIntermediate) { + setFirstNumber(input); + setSecondNumber(null); + setInput(null); + } else { + reset(); + } + hasReset = true; + } + + if (string === "." && input?.includes(".")) + return; + + if (string === "-") { + if (input === "0") { + setInput("-0"); + } else { + setInput((parseFloat(input) * -1).toString()); + } + } else if (string === "%") { + setInput((parseFloat(input) / 100).toString()); + } else if (input === "0" || input === "-0" || input == null || hasReset) { + if (string === ".") { + setInput(input === "-0" ? "0." : "0."); + } else { + setInput(input === "-0" ? `-${string}` : string); + } + } else { + setInput(input + string); + } + + log(); + }, [input, isIntermediate, log, reset, secondNumber]); + + const calculate = useCallback((intermediate = false) => { + if (firstNumber == null) { + + } else { + setSecondNumber(input); + + const a = parseFloat(firstNumber); + const b = parseFloat(input); + + let result = 0; + switch (operation) { + case "×": + result = a * b; + break; + case "÷": + result = a / b; + break; + case "+": + result = a + b; + break; + case "-": + result = a - b; + break; + } + + setInput(result.toString()); + } + + setIsIntermediate(intermediate); + + log(); + }, [firstNumber, input, log, operation]); + + const changeOperation = useCallback((operation) => { + if (firstNumber != null && secondNumber == null) { + calculate(true); + } else { + setFirstNumber(input); + setSecondNumber(null); + setInput(null); + } + + setOperation(operation); + + log(); + }, [calculate, firstNumber, input, log, secondNumber]); + + useEffect(() => { + const onKeyDown = (event) => { + if (!active) + return; + + console.log(event.key); + + event.preventDefault(); + + switch (event.key) { + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + addInput(event.key); + break; + case ".": + case ",": + addInput("."); + break; + case "Escape": + reset(); + break; + case "=": + case "Enter": + calculate(); + break; + case "*": + changeOperation("×"); + break; + case "/": + changeOperation("÷"); + break; + case "+": + case "-": + changeOperation(event.key); + break; + case "%": + addInput("%"); + break; + } + }; + + document.addEventListener("keydown", onKeyDown); + + return () => { + document.removeEventListener("keydown", onKeyDown); + }; + }, [active, addInput, calculate, changeOperation, reset]); + + let calculation = ""; + if (operation != null) + calculation = `${firstNumber} ${operation} ${secondNumber != null ? secondNumber + " =" : ""}`; + + return (
+
+

{calculation}

+

{input ?? firstNumber}

+
+
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + +
+
+
); } \ No newline at end of file diff --git a/src/components/apps/calculator/Calculator.module.css b/src/components/apps/calculator/Calculator.module.css index e69de29..c603b11 100644 --- a/src/components/apps/calculator/Calculator.module.css +++ b/src/components/apps/calculator/Calculator.module.css @@ -0,0 +1,67 @@ +.Container { + display: flex; + flex-direction: column; + height: 100%; + + --output-height: 20%; + --button-gap: 0.25rem; +} + +.Output { + display: flex; + gap: 0.25rem; + flex-direction: column; + justify-content: center; + align-items: flex-end; + height: var(--output-height); + padding: 0.5rem; +} + +.Calculation, .Preview { + margin: 0; +} + +.Calculation { + color: var(--foreground-color-c); + height: 1.25rem; +} + +.Preview { + color: var(--foreground-color-a); + font-size: 2.5rem; +} + +.Input { + display: flex; + gap: var(--button-gap); + flex-direction: column; + height: calc(100% - var(--output-height)); + padding: 0.5rem; +} + +.Input-row { + flex: 1; + display: flex; + gap: var(--button-gap); +} + +.Button { + flex: 1; + border-radius: 0.25rem; + font-size: 1.5rem; +} + +.Input-row:first-of-type .Button { + --normal-color: var(--background-color-b); + --hover-color: var(--background-color-ba); +} + +.Input-row .Button:last-of-type { + --text-color: var(--background-color-a); + --normal-color: var(--blue-a); + --hover-color: var(--blue-b); +} + +.Button-large { + min-width: calc(50% - var(--button-gap) / 2); +} \ No newline at end of file diff --git a/src/config/apps.config.js b/src/config/apps.config.js index 172cc69..e0e5062 100644 --- a/src/config/apps.config.js +++ b/src/config/apps.config.js @@ -3,7 +3,9 @@ export const APPS = { SETTINGS: "settings", MEDIA_VIEWER: "media-viewer", TEXT_EDITOR: "text-editor", - FILE_EXPLORER: "file-explorer" + FILE_EXPLORER: "file-explorer", + CALCULATOR: "calculator", + BROWSER: "browser", }; export const APP_NAMES = { @@ -11,7 +13,9 @@ export const APP_NAMES = { SETTINGS: "Settings", MEDIA_VIEWER: "Photos", TEXT_EDITOR: "Notes", - FILE_EXPLORER: "Files" + FILE_EXPLORER: "Files", + CALCULATOR: "Maths", + BROWSER: "Browser", }; export const APP_ICONS = { @@ -20,4 +24,5 @@ export const APP_ICONS = { MEDIA_VIEWER: `${process.env.PUBLIC_URL}/assets/apps/icons/${APPS.MEDIA_VIEWER}.svg`, TEXT_EDITOR: `${process.env.PUBLIC_URL}/assets/apps/icons/${APPS.TEXT_EDITOR}.svg`, FILE_EXPLORER: `${process.env.PUBLIC_URL}/assets/apps/icons/${APPS.FILE_EXPLORER}.svg`, + CALCULATOR: `${process.env.PUBLIC_URL}/assets/apps/icons/${APPS.CALCULATOR}.svg`, }; \ No newline at end of file diff --git a/src/features/apps/appsManager.js b/src/features/apps/appsManager.js index 737aea4..4dafcf4 100644 --- a/src/features/apps/appsManager.js +++ b/src/features/apps/appsManager.js @@ -5,22 +5,22 @@ import { WebView } from "../../components/apps/_utils/web-view/WebView.jsx"; import { Terminal } from "../../components/apps/terminal/Terminal.jsx"; import { TextEditor } from "../../components/apps/text-editor/TextEditor.jsx"; import { Settings } from "../../components/apps/settings/Settings.jsx"; -// import { Calculator } from "../../components/apps/calculator/Calculator.jsx"; +import { Calculator } from "../../components/apps/calculator/Calculator.jsx"; import Vector2 from "../math/vector2.js"; -import { APPS } from "../../config/apps.config.js"; +import { APPS, APP_NAMES } from "../../config/apps.config.js"; import { Browser } from "../../components/apps/browser/Browser.jsx"; import { IMAGE_FORMATS } from "../../config/apps/mediaViewer.config.js"; export default class AppsManager { static APPS = [ - new App("Commands", APPS.TERMINAL, Terminal), - new App("Settings", APPS.SETTINGS, Settings), - new App("Photos", APPS.MEDIA_VIEWER, MediaViewer), + new App(APP_NAMES.TERMINAL, APPS.TERMINAL, Terminal), + new App(APP_NAMES.SETTINGS, APPS.SETTINGS, Settings), + new App(APP_NAMES.MEDIA_VIEWER, APPS.MEDIA_VIEWER, MediaViewer), // new App("Browser", "browser"), - // new App("Calculator", "calculator", Calculator, { size: new Vector2(400, 600) }), - new App("Notes", APPS.TEXT_EDITOR, TextEditor), + new App(APP_NAMES.CALCULATOR, APPS.CALCULATOR, Calculator, { size: new Vector2(400, 600) }), + new App(APP_NAMES.TEXT_EDITOR, APPS.TEXT_EDITOR, TextEditor), // new App("Code Editor", "code-editor"), - new App("Files", APPS.FILE_EXPLORER, FileExplorer), + new App(APP_NAMES.FILE_EXPLORER, APPS.FILE_EXPLORER, FileExplorer), new App("Wordle", "wordle", WebView, { source: "https://prozilla.dev/wordle", size: new Vector2(400, 650) @@ -33,7 +33,7 @@ export default class AppsManager { source: "https://prozilla.dev/minesweeper", size: new Vector2(500, 580) }), - new App("Browser", "browser", Browser, { + new App(APP_NAMES.BROWSER, APPS.BROWSER, Browser, { size: new Vector2(700, 500) }), ];