Files
touch-programming/client/src/App.jsx
T

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;