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 Subtraction() { 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(true); // show steps and allow restart let calculationInput = useRef(null); useEffect(()=>{ let autoMove = window.localStorage.getItem("autoMoveComma"); if(autoMove !== null){ setAutoMoveComma(JSON.parse(autoMove)); } let allowStart = window.localStorage.getItem("allowStartOver"); if(allowStart !== null){ setAllowStartOver(JSON.parse(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[0]; for(let i=1; i 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 numbersCopy[noId].push(" "); setNumbers(numbersCopy); setTimeout(() => { document.getElementById(tdId).focus(); }, 100); }else if(pressedKey === 39 && // right arrow numbersCopy[noId][numbersCopy[noId].length-1] === " "){ numbersCopy[noId].pop(); 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(""); if(noIdx == nos.length-1){ cellText = "- " + cellText; } 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 = "; let firstDigit = 0; let digit = 0; 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){ if(parseInt(n)===0){ firstDigit = numbers[parseInt(n)][imdtResIdx]; }else{ // 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 + (wie viel) = input digit = numbers[parseInt(n)][imdtResIdx]; if(digit === "" || digit === " "){ digit = 0; } labelText += digit; if(parseInt(n) === numbers.length - 1){ if(carryArr[0] !== undefined && carryArr[0] > 0){ labelText += " + " + carryArr[0].toString(); } }else{ labelText += " + "; } } } labelText += " + wie viel = " + firstDigit; labelText = labelText.replace(/ /g, "0"); imdtResIdx = imdtResIdx - 1; nosLeft = nosLeft - 1; return (
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/>
handleCarryChange(e, nosLeft)} type="text" id="input_carry" size="2" maxLength="1" aria-labelledby="input_carry_label" aria-required="true" ref={carryInputField}/>
); }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 0 if(carries[idxCarry]!=="0"){ realDigitRes += parseInt(carries[idxCarry]); text += " + " + carries[idxCarry]; // + " Übertrag" } let realCarry = Math.abs(parseInt(realDigitRes/10)); realDigitRes = ((firstDigit - realDigitRes) % 10); if(realDigitRes < 0){ realDigitRes = realDigitRes + 10; realCarry += 1; } realCarry = realCarry.toString(); realDigitRes = realDigitRes.toString(); text += " und "; if(trueNumbers){ text += resArr[idxNumbers] + " = " + firstDigit; text += " mit Übertrag " + carries[idxCarry-1]; text = text.replace(/ /g, "0"); text += resArr[idxNumbers]===realDigitRes && 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 = ""; // remove zeros at the beginning let res = []; let beginNr = false; for(let r in resArr){ let digit = resArr[r]; if(digit===" "){ continue; } if(digit!=="0" | beginNr | parseInt(r)===resArr.length-1){ beginNr = true; res.push(digit); } } let resCalc = res.join(""); if(resCalc.startsWith(".")){ resCalc = "0" + resCalc; } setResultsGrid([{number: resCalc}]); resCalc = parseFloat(resCalc); let message = ""; if(resCalc === realResult){ message = "Richtig!"; }else{ message = "Das ist leider falsch."; } message = "

" + message + "

"; 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 = []; // reset numbers to calculate new length after error for(let noIdx in numbersCopy){ while(numbersCopy[noIdx][0] === " "){ numbersCopy[noIdx].shift(); } } // 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].unshift(" "); } commaPositions.push(numbersCopy[noIdx].indexOf(".")); } setNumbers(numbersCopy); let commaCorrect = false; commaPositions = [...new Set(commaPositions.filter(c => c >= 0))]; // if no commas in numbers if(commaPositions.length === 0){ setCommaIdx(-1); // check if no number was moved commaCorrect = !numbersCopy.map(n => n[numbersLen] !== " ").includes(false); // correct comma positions }else if(commaPositions.length === 1){ setCommaIdx(commaPositions[0]); commaCorrect = true; // check if numbers without comma are correct too for(let no of numbersCopy){ if(!no.includes(".")){ // digit before comma is a number, no space if(no[commaPositions[0]-1] === " "){ commaCorrect = false; } // only spaces from comma to end for(let digit=commaPositions[0]; digit<=numbersLen; digit++){ if(no[digit] !== " "){ commaCorrect = false; } } } } } if(commaCorrect){ 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 (

Subtraktion

handleSubmit(e)}> handleInput(e)} aria-label="Rechnung" aria-required="true" ref={calculationInput}/>
submitCommaMove()}/>

); } export default Subtraction;