mirror of
https://github.com/hazemKrimi/touch-programming.git
synced 2026-05-01 18:20:26 +00:00
107 lines
2.8 KiB
React
107 lines
2.8 KiB
React
import { useEffect, useState } from 'react';
|
|
import './App.css';
|
|
|
|
const KEYS_TO_DISABLE = ['Backspace', 'Shift', 'Alt', 'Control', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'Escape', 'Delete', 'PageDown', 'PageUp', 'Home', 'End', 'Insert', 'WakeUp', 'Pause', 'ScrollLock', 'ContextMenu', 'BrowserForward', 'BrowserBack', 'CapsLock', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
|
|
|
|
function App() {
|
|
// TODO: Cleanup the file and create utils for spacing and trimming the code properly.
|
|
const [code, setCode] = useState('');
|
|
const [loaded, setLoaded] = useState(false);
|
|
const [characters, setCharacters] = useState([]);
|
|
|
|
useEffect(() => {
|
|
(async function() {
|
|
setCode('');
|
|
|
|
const response = await fetch(`${import.meta.env.VITE_API_URL}/generate?lang=go&lines=10`);
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
|
|
while(true) {
|
|
const {value, done} = await reader.read();
|
|
|
|
setCode(prev => prev + decoder.decode(value));
|
|
|
|
if (done) {
|
|
setLoaded(true);
|
|
setCode(prev => prev.trim());
|
|
break;
|
|
}
|
|
}
|
|
})();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
function handleKeyPress(event) {
|
|
event.preventDefault();
|
|
|
|
if (!loaded) return;
|
|
if (characters.length === code.length) return;
|
|
if (KEYS_TO_DISABLE.includes(event.key)) return;
|
|
|
|
const char = code[characters.length];
|
|
|
|
if (/^\n$/.test(char))
|
|
return setCharacters(
|
|
characters.concat(event.key === 'Enter')
|
|
);
|
|
if (/^(\s|\t)$/.test(char))
|
|
return setCharacters(
|
|
characters.concat(['Space', 'Tab'].includes(event.key))
|
|
);
|
|
|
|
setCharacters(
|
|
characters.concat(event.key === char)
|
|
);
|
|
}
|
|
|
|
window.addEventListener('keydown', handleKeyPress);
|
|
|
|
return () => window.removeEventListener('keydown', handleKeyPress);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [loaded, characters]);
|
|
|
|
function renderCharacterClassName(index) {
|
|
const typed = characters[index];
|
|
|
|
if (loaded && index === characters.length) return 'highlight';
|
|
if (typeof typed === 'undefined') return 'pending';
|
|
if (!typed) return 'incorrect';
|
|
|
|
return 'correct';
|
|
}
|
|
|
|
function renderSpacingCharacter(char) {
|
|
if (/^\n$/.test(char)) return '\n';
|
|
if (/^(\s|\t)$/.test(char)) return '\u00A0'
|
|
|
|
return char;
|
|
}
|
|
|
|
function renderClassName(index) {
|
|
return `${renderCharacterClassName(index)}`.trim();
|
|
}
|
|
|
|
function renderCharacter(char, index) {
|
|
const rendered = renderSpacingCharacter(char);
|
|
|
|
if (/^\n$/.test(rendered)) {
|
|
return <span className={renderClassName(index)} key={char + index}><br /></span>
|
|
}
|
|
|
|
return (
|
|
<span className={renderClassName(index)} key={char + index}>
|
|
{rendered}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className='container'>
|
|
{code.split('').map((char, index) => renderCharacter(char, index))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App;
|