Added calculator app
This commit is contained in:
parent
5e26c27980
commit
d2784afba0
4 changed files with 288 additions and 16 deletions
|
|
@ -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() {
|
/** @type {import("../../windows/WindowView.jsx").windowProps} */
|
||||||
return (
|
export function Calculator({ active }) {
|
||||||
<div></div>
|
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 (<div className={styles.Container}>
|
||||||
|
<div className={styles.Output}>
|
||||||
|
<p className={styles.Calculation}>{calculation}</p>
|
||||||
|
<p className={styles.Preview}>{input ?? firstNumber}</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.Input}>
|
||||||
|
<div className={styles["Input-row"]}>
|
||||||
|
<Button className={styles.Button} onClick={reset}>C</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("-"); }}>+/-</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("%"); }}>%</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { changeOperation("÷"); }}>÷</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles["Input-row"]}>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("7"); }}>7</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("8"); }}>8</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("9"); }}>9</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { changeOperation("×"); }}>×</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles["Input-row"]}>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("4"); }}>4</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("5"); }}>5</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("6"); }}>6</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { changeOperation("-"); }}>-</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles["Input-row"]}>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("1"); }}>1</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("2"); }}>2</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("3"); }}>3</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { changeOperation("+"); }}>+</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles["Input-row"]}>
|
||||||
|
<Button className={`${styles.Button} ${styles["Button-large"]}`} onClick={() => { addInput("0"); }}>0</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { addInput("."); }}>.</Button>
|
||||||
|
<Button className={styles.Button} onClick={() => { calculate(); }}>=</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,9 @@ export const APPS = {
|
||||||
SETTINGS: "settings",
|
SETTINGS: "settings",
|
||||||
MEDIA_VIEWER: "media-viewer",
|
MEDIA_VIEWER: "media-viewer",
|
||||||
TEXT_EDITOR: "text-editor",
|
TEXT_EDITOR: "text-editor",
|
||||||
FILE_EXPLORER: "file-explorer"
|
FILE_EXPLORER: "file-explorer",
|
||||||
|
CALCULATOR: "calculator",
|
||||||
|
BROWSER: "browser",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const APP_NAMES = {
|
export const APP_NAMES = {
|
||||||
|
|
@ -11,7 +13,9 @@ export const APP_NAMES = {
|
||||||
SETTINGS: "Settings",
|
SETTINGS: "Settings",
|
||||||
MEDIA_VIEWER: "Photos",
|
MEDIA_VIEWER: "Photos",
|
||||||
TEXT_EDITOR: "Notes",
|
TEXT_EDITOR: "Notes",
|
||||||
FILE_EXPLORER: "Files"
|
FILE_EXPLORER: "Files",
|
||||||
|
CALCULATOR: "Maths",
|
||||||
|
BROWSER: "Browser",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const APP_ICONS = {
|
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`,
|
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`,
|
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`,
|
FILE_EXPLORER: `${process.env.PUBLIC_URL}/assets/apps/icons/${APPS.FILE_EXPLORER}.svg`,
|
||||||
|
CALCULATOR: `${process.env.PUBLIC_URL}/assets/apps/icons/${APPS.CALCULATOR}.svg`,
|
||||||
};
|
};
|
||||||
|
|
@ -5,22 +5,22 @@ import { WebView } from "../../components/apps/_utils/web-view/WebView.jsx";
|
||||||
import { Terminal } from "../../components/apps/terminal/Terminal.jsx";
|
import { Terminal } from "../../components/apps/terminal/Terminal.jsx";
|
||||||
import { TextEditor } from "../../components/apps/text-editor/TextEditor.jsx";
|
import { TextEditor } from "../../components/apps/text-editor/TextEditor.jsx";
|
||||||
import { Settings } from "../../components/apps/settings/Settings.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 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 { Browser } from "../../components/apps/browser/Browser.jsx";
|
||||||
import { IMAGE_FORMATS } from "../../config/apps/mediaViewer.config.js";
|
import { IMAGE_FORMATS } from "../../config/apps/mediaViewer.config.js";
|
||||||
|
|
||||||
export default class AppsManager {
|
export default class AppsManager {
|
||||||
static APPS = [
|
static APPS = [
|
||||||
new App("Commands", APPS.TERMINAL, Terminal),
|
new App(APP_NAMES.TERMINAL, APPS.TERMINAL, Terminal),
|
||||||
new App("Settings", APPS.SETTINGS, Settings),
|
new App(APP_NAMES.SETTINGS, APPS.SETTINGS, Settings),
|
||||||
new App("Photos", APPS.MEDIA_VIEWER, MediaViewer),
|
new App(APP_NAMES.MEDIA_VIEWER, APPS.MEDIA_VIEWER, MediaViewer),
|
||||||
// new App("Browser", "browser"),
|
// new App("Browser", "browser"),
|
||||||
// new App("Calculator", "calculator", Calculator, { size: new Vector2(400, 600) }),
|
new App(APP_NAMES.CALCULATOR, APPS.CALCULATOR, Calculator, { size: new Vector2(400, 600) }),
|
||||||
new App("Notes", APPS.TEXT_EDITOR, TextEditor),
|
new App(APP_NAMES.TEXT_EDITOR, APPS.TEXT_EDITOR, TextEditor),
|
||||||
// new App("Code Editor", "code-editor"),
|
// 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, {
|
new App("Wordle", "wordle", WebView, {
|
||||||
source: "https://prozilla.dev/wordle",
|
source: "https://prozilla.dev/wordle",
|
||||||
size: new Vector2(400, 650)
|
size: new Vector2(400, 650)
|
||||||
|
|
@ -33,7 +33,7 @@ export default class AppsManager {
|
||||||
source: "https://prozilla.dev/minesweeper",
|
source: "https://prozilla.dev/minesweeper",
|
||||||
size: new Vector2(500, 580)
|
size: new Vector2(500, 580)
|
||||||
}),
|
}),
|
||||||
new App("Browser", "browser", Browser, {
|
new App(APP_NAMES.BROWSER, APPS.BROWSER, Browser, {
|
||||||
size: new Vector2(700, 500)
|
size: new Vector2(700, 500)
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue