123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- import React, { useState, useEffect, useRef } from "react";
- import DataGrid, {Column} from "devextreme-react/data-grid";
- import {numbersToArrOfArr, afterCommaLen, handleKeyDown} from "./helpers.js";
- import "./App.css";
- let imdtRes;
- let imdtResIdx;
- let resArr = [];
- let noOfDigits = 0;
- let commaIsSet = false;
- function Addition() {
- const [input, setInput] = useState(""); // initial user input like 34+56.7
- const [realResult, setRealResult] = useState(0); // real result of calculation
- const [carryArr, setCarryArr] = useState([]); // array of carries ["1","0"]
- const [commaIdx, setCommaIdx] = useState(-1); // int: comma position
- const [numbers, setNumbers] = useState(0); // array of arrays with input numbers
- const [resultsGrid, setResultsGrid] = useState([{id: 1, number: ""}]); // calculated result
- const [stepsGrid, setStepsGrid] = useState([{id: 1, step: ""}]); // one step for every digit
- const [autoMoveComma, setAutoMoveComma] = useState(false); // move the comma automatically
- const [allowStartOver, setAllowStartOver] = useState(false); // show steps and allow restart
- let calculationInput = useRef(null);
- useEffect(()=>{
- let autoMove = window.localStorage.getItem("autoMoveComma");
- if(autoMove !== null){
- setAutoMoveComma(JSON.parse(autoMove));
- console.log("auto move comma:", autoMove);
- }
- let allowStart = window.localStorage.getItem("allowStartOver");
- if(allowStart !== null){
- setAllowStartOver(JSON.parse(allowStart));
- console.log("allow start over:", allowStart);
- }
- // focus the input field
- if(calculationInput.current && calculationInput.current.value === ""){
- calculationInput.current.focus();
- }
- }, []);
- useEffect(()=>{
- if(!autoMoveComma){
- addNumbersToTable([]);
- }
- }, [numbers]);
- const handleInput = (e) => {
- setInput(e.target.value);
- };
- const handleResChange = (e) => {
- if(typeof e === "string"){
- imdtRes = e;
- }else{
- imdtRes = e.target.value;
- }
- // remove deleted input
- if(imdtRes === ""){
- resArr.shift();
- }else{
- // add input number to result array
- resArr.unshift(imdtRes);
- }
- };
- const handleCarryChange = (e, nosLeft) => {
- if(e.keyCode === 13){
- let carryArrCopy = [...carryArr];
- carryArrCopy.unshift(e.target.value);
- setCarryArr(carryArrCopy);
- let noCarry = carryArrCopy[0] === "" || carryArrCopy[0] === "0" || carryArrCopy[0] === undefined;
- if((nosLeft === 1 && noCarry) ||
- // if stop after first iteration, numbers left is undefined at first
- (isNaN(nosLeft) && realResult.toString().length === 1 && noCarry)){
- nosLeft = 0;
- handleResChange(" ");
- }
- if(nosLeft === 0){
- if(allowStartOver){
- showIdmtResults(carryArrCopy);
- addButtonsToImdtSteps(carryArrCopy);
- }else{
- finishCalculation();
- }
- }
- }
- };
- const handleSubmit = (e) => {
- startCalculation();
- e.preventDefault(); // avoid page reload
- };
- const startCalculation = () => {
- if(input.includes("+")){
- let numbers = input.split("+").map(x => parseFloat(x.replace(",",".")));
- let realRes = numbers.reduce((x,y) => x+y, 0);
- let afterComma = Math.max(...numbers.map(x => afterCommaLen(x)));
- realRes = parseFloat(realRes.toFixed(afterComma));
- setRealResult(realRes);
- console.log("real result:", realRes);
- let numbersArr, commaIdx;
- if(!autoMoveComma){
- numbersArr = numbers.map(x => x.toString().split(""));
- setNumbers(numbersArr);
- addNumbersToTable([]);
- startCommaMove();
- }else{
- commaIsSet = true;
- [numbersArr, commaIdx] = numbersToArrOfArr(numbers);
- setNumbers(numbersArr);
- setCommaIdx(commaIdx);
- addNumbersToTable(numbersArr);
- }
- }
- };
- const handleKeyMoveComma = (e) => {
- let pressedKey = e.keyCode;
- let tdId = e.target.id;
- let noId = tdId.split("numbersTd")[1];
- let numbersCopy = [...numbers];
- if(pressedKey === 37){ // left arrow
- // add one space before all numbers to shift this one
- if(numbersCopy[noId][0] !== " "){
- for(let idx in numbersCopy){
- numbersCopy[idx].unshift(" ")
- }
- }
- numbersCopy[noId].shift();
- setNumbers(numbersCopy);
- setTimeout(() => {
- document.getElementById(tdId).focus();
- }, 100);
- }else if(pressedKey === 39){ // right arrow
- numbersCopy[noId].unshift(" ");
- setNumbers(numbersCopy);
- setTimeout(() => {
- document.getElementById(tdId).focus();
- }, 100);
- }else if(pressedKey === 38){ // up
- let nextFocusId = Math.max(noId-1, 0);
- document.getElementById("numbersTd" + nextFocusId).focus();
- }else if(pressedKey === 40){ // down
- let nextFocusId = Math.min(noId+1, numbers.length-1);
- document.getElementById("numbersTd" + nextFocusId).focus();
- }else if(pressedKey === 13){ // enter
- submitCommaMove();
- }
- };
- const addNumbersToTable = (numbersArr) => {
- let nos;
- if(numbersArr.length > 0){
- nos = numbersArr;
- }else{
- nos = numbers;
- }
- let table = document.getElementById("numbersGrid");
- table.innerHTML = "";
- for(let noIdx in nos){
- let row = table.insertRow();
- let cell = row.insertCell();
- let cellText = nos[noIdx].join("");
- cell.innerHTML = cellText;
- cell.tabIndex = "0";
- cell.id = "numbersTd" + noIdx;
- }
- };
- const ResultCarryForm = ({handleResChange, handleCarryChange}) => {
- let resInputField = useRef(null);
- let carryInputField = useRef(null);
- let labelText = "";
- let nosLeft = imdtResIdx + 1;
- let carryText = "Übertrag = ";
- if(commaIsSet && typeof numbers === "object" && imdtResIdx !== -1){
- document.getElementById("commaWarning").innerHTML = "";
- // set the digit index, start with the last digit
- if(typeof imdtResIdx === "undefined" || imdtResIdx === null){
- noOfDigits = Math.min(...numbers.map(n => n.length));
- imdtResIdx = noOfDigits - 1;
- }
- if(typeof nosLeft === "undefined" ){
- nosLeft = imdtResIdx + 1;
- }
- // skip over comma
- if(imdtResIdx === commaIdx){
- if(!resArr.includes(".")){
- handleResChange(".");
- }
- imdtResIdx = imdtResIdx - 1;
- nosLeft = nosLeft - 1;
- }
- // iterate numbers for this digit index
- for(let n in numbers){
- // skip last empty number if no carry
- if(nosLeft === 1 && (carryArr[0] === "0" || carryArr[0] === "")){
- nosLeft = 0;
- imdtResIdx = -1;
- return <></>;
- }
- // create label text like: d + d + .. = input
- let digit = numbers[parseInt(n)][imdtResIdx];
- if([".", "0", " ", ""].includes(digit)){
- digit = 0;
- }
- labelText += digit;
- if(parseInt(n) === numbers.length - 1){
- if(carryArr[0] !== undefined && carryArr[0] > 0){
- labelText += " + " + carryArr[0].toString();
- }
- labelText += " = ";
- }else{
- labelText += " + ";
- }
- }
- imdtResIdx -= 1;
- nosLeft -= 1;
- return (
- <form>
- <label htmlFor="input_result" id="input_result_label">
- {labelText}
- </label>
- <input
- onKeyDown={(e) => handleKeyDown(e)}
- onChange={(e) => handleResChange(e)}
- type="text" id="input_result" size="2" maxLength="1"
- aria-labelledby="input_result_label" aria-required="true"
- ref={resInputField} autoFocus/>
- <br/>
- <label htmlFor="input_carry" id="input_carry_label">
- {carryText}
- </label>
- <input
- onKeyDown={(e) => handleCarryChange(e, nosLeft)}
- type="text" id="input_carry" size="2" maxLength="1"
- aria-labelledby="input_carry_label" aria-required="true"
- ref={carryInputField}/>
- </form>
- );
- }else{
- return (
- <>
- </>
- );
- }
- };
- const addButtonsToImdtSteps = (carryArrCopy) => {
- // workaround to access DOM td after grid values are set
- setTimeout(() => {
- let table = document.getElementById("idmtResultSteps").getElementsByTagName("table")[0];
- let trs = table.getElementsByTagName("tr");
- for(let trIdx in trs){
- let tr = trs[trIdx];
- if(tr instanceof HTMLElement && trIdx < trs.length-1){
- let td = tr.getElementsByTagName("td")[0];
- let btn = document.createElement("button");
- btn.innerHTML = "hier starten";
- btn.classList = "btn btn-secondary btn-sm";
- btn.id = "btn-" + trIdx;
- btn.addEventListener("click", () => startOver(trIdx, carryArrCopy));
- td.appendChild(btn);
- }
- }
- }, 300);
- };
- const showIdmtResults = (carryArrCopy) => {
- let realResArr = realResult.toString().split("");
- while(realResArr.length < resArr.length){
- realResArr.unshift(" "); // add " " before
- }
- let carries = carryArrCopy.map(x => x || "0");
- carries.push("0");
- while(carries.length < resArr.length){
- carries.unshift("0"); // add "0" before
- }
- let foundComma = false;
- let stepsGridCopy = [];
- for(let i=0; i<resArr.length; i++) {
- let text = "";
- let trueNumbers = false;
- let idxNumbers = resArr.length - i - 1;
- let idxCarry = carries.length - i - 1;
- let realSum = 0;
- for(let j=0; j<numbers.length; j++){
- let no = numbers[j][idxNumbers];
- if(no!=="." && no!==" "){
- trueNumbers = true;
- realSum += parseInt(no);
- }
- text += no;
- if(j<numbers.length-1){
- text += " + ";
- }
- }
- // ignore indexes without real digits
- if(resArr[idxNumbers]!==" " || (carries[idxCarry]!==" " & carries[idxCarry]!=="0")){
- trueNumbers = true;
- }
- // carry array has no "."
- if(resArr[idxNumbers] === "."){
- foundComma = true;
- trueNumbers = false;
- }
- if(foundComma){
- idxCarry += 1;
- }
- // add carry only if > 0
- if(carries[idxCarry]!=="0"){
- realSum += parseInt(carries[idxCarry]);
- text += " + " + carries[idxCarry]; // + " Übertrag"
- }
- let realCarry = parseInt(realSum/10).toString();
- realSum = (realSum % 10).toString();
- text += " = ";
- if(trueNumbers){
- text += resArr[idxNumbers];
- text += " mit Übertrag " + carries[idxCarry-1];
- text = text.replace(/ /g, "0");
- text += resArr[idxNumbers]===realSum && carries[idxCarry-1]===realCarry ? ": Richtig " : ": Falsch ";
- stepsGridCopy.push({step: text});
- }
- }
- let paragraph = document.getElementById("stepsParagraph");
- paragraph.innerHTML = "Rechenschritte: ";
- let btnSubmit = document.createElement("button");
- btnSubmit.innerHTML = "Ergebnis abgeben";
- btnSubmit.addEventListener("click", finishCalculation);
- btnSubmit.classList = "btn btn-secondary btn-sm";
- btnSubmit.id = "btnSubmitSteps1";
- document.getElementById("idmtResultSteps").insertBefore(btnSubmit, paragraph);
- setStepsGrid(stepsGridCopy);
- let btnSubmit2 = document.createElement("button");
- btnSubmit2.innerHTML = "Ergebnis abgeben";
- btnSubmit2.addEventListener("click", finishCalculation);
- btnSubmit2.classList = "btn btn-secondary btn-sm";
- btnSubmit2.id = "btnSubmitSteps2";
- document.getElementById("idmtResultSteps").appendChild(btnSubmit2);
- document.getElementById("idmtResultSteps").style.display = "inline-block";
- document.getElementById("idmtResultSteps").tabIndex = 0;
- document.getElementById("idmtResultSteps").focus();
- };
- const startOver = (idx, carryArrCopy) => {
- let invertedIdx = numbers[0].length - idx - 1;
- imdtResIdx = invertedIdx;
- let slicer = invertedIdx;
- if(resArr[0] !== " " && !resArr.includes(".")){
- slicer += 1;
- }else if(resArr[0] === " " && resArr.includes(".")){
- slicer -= 1;
- }
- carryArrCopy = carryArrCopy.slice(slicer);
- // reset result and carries until the chosen step
- let commaPos = resArr.indexOf(".");
- if(resArr.includes(".") && commaPos >= invertedIdx){
- imdtResIdx -= 1;
- resArr = resArr.slice(invertedIdx);
- }else{
- resArr = resArr.slice(invertedIdx + 1);
- }
- setCarryArr(carryArrCopy);
- document.getElementById("btnSubmitSteps1").remove();
- document.getElementById("btnSubmitSteps2").remove();
- document.getElementById("idmtResultSteps").style.display = "none";
- };
- const finishCalculation = () => {
- document.getElementById("idmtResultSteps").innerHTML = "";
- let resCalc = resArr.join("");
- setResultsGrid([{number: resCalc.replace(/ /g, " ")}]);
- resCalc = parseFloat(resCalc.replace(/ /g, ""));
- let message = "";
- if(resCalc === realResult){
- message = "Richtig!";
- }else{
- message = "Das ist leider falsch.";
- }
- message = "<p>" + message + "</p>";
- document.getElementById("finishCalculation").innerHTML = message;
- document.getElementById("finishCalculation").tabIndex = "0";
- document.getElementById("finishCalculation").focus();
- };
- const startCommaMove = () => {
- document.getElementById("commaSubmit").style.display = "inline-block";
- setTimeout(() => {
- document.getElementById("numbersTd0").focus();
- }, 300);
- };
- const submitCommaMove = () => {
- let numbersCopy = [...numbers];
- let commaPositions = [];
- // get all numbers up to the same length
- let numbersLen = Math.max(...numbers.map(n => n.length));
- for(let noIdx in numbersCopy){
- while(numbersCopy[noIdx].length < numbersLen){
- numbersCopy[noIdx].push(" ");
- }
- let commaPos = numbersCopy[noIdx].indexOf(".");
- if(commaPos < 0){
- for(let idx=numbersCopy[noIdx].length-1; idx>0; idx--){
- if(numbersCopy[noIdx][idx-1] !== " " &&
- numbersCopy[noIdx][idx] === " "){
- commaPos = idx;
- break;
- }
- }
- }
- commaPositions.push(commaPos);
- }
- setNumbers(numbersCopy);
- let commaCorrect = false;
- commaPositions = [...new Set(commaPositions)];
- // if no commas in numbers
- if(commaPositions.length === 1 && commaPositions[0] !== numbersLen){
- if(commaPositions[0] < 0){
- setCommaIdx(commaPositions[0]);
- }else{
- setCommaIdx(commaPositions[0] + 1);
- }
- commaCorrect = true;
- }
- if(commaCorrect){
- for(let idx=numbersLen-1; idx>=0; idx--){
- let nos = numbersCopy.map(n => n[idx])
- if(nos.every(n => n === " ")){
- for(let noIdx in numbersCopy){
- numbersCopy[noIdx].splice(idx, 1);
- }
- }
- }
- for(let noIdx in numbersCopy){
- numbersCopy[noIdx].unshift(" ");
- }
- document.getElementById("commaSubmit").style.display = "none";
- commaIsSet = true;
- }else{
- let paragraph = document.getElementById("commaWarning");
- paragraph.innerHTML = "das ist leider falsch, versuche es noch einmal:";
- paragraph.tabIndex = "0";
- paragraph.focus();
- }
- };
- return (
- <main>
- <h1>Addition</h1>
- <form onSubmit={(e) => handleSubmit(e)}>
- <label htmlFor="calculationInput">
- Rechnung:
- </label>
- <input
- type="text"
- id="calculationInput"
- onChange={(e) => handleInput(e)}
- aria-label="Rechnung"
- aria-required="true"
- ref={calculationInput}/>
- <input type="submit" value="berechnen"/>
- </form>
- <div id="finishCalculation"></div>
- <div id="commaWarning"></div>
- <div id="overview">
- <table
- id="numbersGrid"
- onKeyDown={handleKeyMoveComma}
- >
- </table>
- <input type="submit"
- value="Komma bestätigen"
- id="commaSubmit"
- onClick={() => submitCommaMove()}/>
- <hr></hr>
- <DataGrid
- dataSource={resultsGrid}
- focusedRowEnabled={true}
- showBorders={true}
- showColumnHeaders={false}
- >
- <Column dataField="number" />
- </DataGrid>
- </div>
- <div id="calculation">
- <ResultCarryForm
- handleResChange={handleResChange}
- handleCarryChange={handleCarryChange}/>
- </div>
- <div id="idmtResultSteps">
- <p id="stepsParagraph"></p>
- <DataGrid
- dataSource={stepsGrid}
- focusedRowEnabled={true}
- showBorders={true}
- showColumnHeaders={false}
- noDataText=""
- >
- <Column dataField="step" />
- </DataGrid>
- </div>
- </main>
- );
- }
- export default Addition;
|