mirror of
https://github.com/genemecija/learn-morse-code.git
synced 2026-01-09 01:59:56 +01:00
Mode switching functionality
This commit is contained in:
parent
cad5575ec5
commit
f863d9cc05
171
src/App.js
171
src/App.js
|
|
@ -1,172 +1,33 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, {useContext, useEffect} from 'react';
|
||||
import './css/App.css';
|
||||
import MorseDisplay from './components/MorseDisplay'
|
||||
import MorseBufferDisplay from './components/MorseBufferDisplay'
|
||||
import MorseButton from './components/MorseButton'
|
||||
import ModePicker from './components/ModePicker'
|
||||
// import LettersDisplay from './components/LettersDisplay'
|
||||
// import MorseDisplay from './components/MorseDisplay'
|
||||
// import MorseBufferDisplay from './components/MorseBufferDisplay'
|
||||
// import GameClock from "./components/GameClock"
|
||||
// import ChallengeWord from "./components/ChallengeWord"
|
||||
import {GameModeContext} from "./gameContext"
|
||||
import PracticeMode from './app-modes/PracticeMode';
|
||||
import TimedMode from './app-modes/TimedMode'
|
||||
import ChallengeMode from './app-modes/ChallengeMode'
|
||||
|
||||
function App() {
|
||||
|
||||
const [morseCharBuffer, setMorseCharBuffer] = useState('') // e.g. '-..'
|
||||
const [morseWords, setMorseWords] = useState([]) // e.g. [['-..','.','-,'], ['...','---','...']]
|
||||
|
||||
let charTimer = 0
|
||||
let charTime = 0
|
||||
let gapTimer = 0
|
||||
let gapTime = 0
|
||||
|
||||
const timingUnit = 15 // default: 25
|
||||
|
||||
const ditMaxTime = 5 // default: 3
|
||||
const letterGapMinTime = ditMaxTime*3
|
||||
const wordGapMaxTime = ditMaxTime*7
|
||||
const morseHistorySize = 5
|
||||
|
||||
// Tone Setup
|
||||
let AudioContext = window.AudioContext || window.webkitAudioContext || false
|
||||
let context
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
if (AudioContext) {
|
||||
context = new AudioContext()
|
||||
} else {
|
||||
context = null
|
||||
}
|
||||
|
||||
let o
|
||||
let frequency = 550.0
|
||||
|
||||
|
||||
function handleInputStart(event) {
|
||||
event.preventDefault()
|
||||
if ((event.keyCode !== 32 && event.target.id !== "morseButton") ||
|
||||
(event.repeat)) {
|
||||
return
|
||||
}
|
||||
if (context.state === 'interrupted') {
|
||||
context.resume()
|
||||
}
|
||||
|
||||
o = context.createOscillator()
|
||||
o.frequency.value = frequency
|
||||
o.type = "sine"
|
||||
|
||||
let g = context.createGain()
|
||||
g.gain.exponentialRampToValueAtTime(0.08, context.currentTime)
|
||||
o.connect(g)
|
||||
g.connect(context.destination)
|
||||
o.start()
|
||||
|
||||
// if (gapTimer===0) { setMorseLettersBuffer('') }
|
||||
checkGapBetweenInputs()
|
||||
clearInterval(gapTimer)
|
||||
|
||||
|
||||
startCharTimer()
|
||||
}
|
||||
function startCharTimer() {
|
||||
// Reset character time
|
||||
charTime = 0
|
||||
// Start Character Timer
|
||||
charTimer = setInterval(() => {
|
||||
charTime += 1
|
||||
}, timingUnit);
|
||||
}
|
||||
|
||||
function handleInputEnd(event) {
|
||||
event.preventDefault()
|
||||
if ((event.keyCode !== 32 && event.target.id !== "morseButton") ||
|
||||
(event.repeat)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (charTime <= ditMaxTime) {
|
||||
setMorseCharBuffer(prev => prev + '.')
|
||||
} else {
|
||||
setMorseCharBuffer(prev => prev + '-')
|
||||
}
|
||||
|
||||
stopCharTimer()
|
||||
startGapTimer()
|
||||
o.stop()
|
||||
}
|
||||
|
||||
function stopCharTimer() {
|
||||
clearInterval(charTimer)
|
||||
charTimer = 0
|
||||
}
|
||||
|
||||
function startGapTimer() {
|
||||
gapTime = 0
|
||||
gapTimer = setInterval(() => {
|
||||
gapTime += 1
|
||||
|
||||
// Gap between words
|
||||
if (gapTime >= wordGapMaxTime) {
|
||||
setMorseCharBuffer(prev => prev + '/')
|
||||
clearInterval(gapTimer)
|
||||
gapTimer = 0
|
||||
gapTime = 0
|
||||
}
|
||||
}, timingUnit);
|
||||
}
|
||||
|
||||
function checkGapBetweenInputs() {
|
||||
// Check Gap between letters
|
||||
if ((gapTime >= letterGapMinTime) && (gapTime < wordGapMaxTime)) {
|
||||
// setMorseLettersBuffer(prev => [...prev, morseCharBuffer])
|
||||
setMorseCharBuffer(prev => prev + ' ')
|
||||
// morseLettersBuffer = [...morseLettersBuffer, morseCharBuffer]
|
||||
// setMorseCharBuffer('')
|
||||
|
||||
clearInterval(gapTimer)
|
||||
gapTimer = 0
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleInputStart)
|
||||
document.addEventListener('keyup', handleInputEnd)
|
||||
document.getElementById('morseButton').addEventListener('mousedown', handleInputStart)
|
||||
document.getElementById('morseButton').addEventListener('touchstart', handleInputStart)
|
||||
document.getElementById('morseButton').addEventListener('mouseup', handleInputEnd)
|
||||
document.getElementById('morseButton').addEventListener('touchend', handleInputEnd)
|
||||
// eslint-disable-next-line
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (morseCharBuffer.slice(-1) === ' ') {
|
||||
// setMorseLettersBuffer([morseCharBuffer])
|
||||
// setMorseCharBuffer('')
|
||||
}
|
||||
if (morseCharBuffer.slice(-1) === '/') {
|
||||
// Remove forward slash
|
||||
let val = morseCharBuffer.slice(0,morseCharBuffer.length-1)
|
||||
console.log('val: ', val);
|
||||
|
||||
setMorseWords(prev => [val, ...prev])
|
||||
|
||||
if (morseWords.length >= morseHistorySize) {
|
||||
setMorseWords(prev => prev.slice(0,prev.length-1))
|
||||
}
|
||||
|
||||
setMorseCharBuffer('')
|
||||
}
|
||||
|
||||
// setMorseLettersBuffer(prev => [...prev, morseCharBuffer])
|
||||
// eslint-disable-next-line
|
||||
}, [morseCharBuffer])
|
||||
console.log('App.js rendered')
|
||||
const {gameMode} = useContext(GameModeContext)
|
||||
|
||||
return (
|
||||
<div id='main-content'>
|
||||
<ModePicker />
|
||||
morseCharBuffer:<br/>
|
||||
<MorseBufferDisplay buffer={morseCharBuffer} /><br/>
|
||||
<MorseDisplay morseWords={morseWords}/>
|
||||
|
||||
{gameMode === 'practice' && <PracticeMode />}
|
||||
{gameMode === 'timed' && <TimedMode />}
|
||||
{gameMode === 'challenge' && <ChallengeMode />}
|
||||
|
||||
<MorseButton />
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default React.memo(App);
|
||||
|
|
|
|||
22
src/app-modes/ChallengeMode.js
Normal file
22
src/app-modes/ChallengeMode.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import React from 'react';
|
||||
import '../css/App.css';
|
||||
import useTelegraph from '../hooks/useTelegraph';
|
||||
import ChallengeWord from '../components/ChallengeWord'
|
||||
import MorseBufferDisplay from '../components/MorseBufferDisplay'
|
||||
|
||||
function ChallengeMode() {
|
||||
|
||||
const {morseCharBuffer} = useTelegraph('challenge')
|
||||
|
||||
console.log('ChallengeMode.js rendered')
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChallengeWord word="challenge"/>
|
||||
<MorseBufferDisplay buffer={morseCharBuffer} /><br/>
|
||||
</>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default React.memo(ChallengeMode);
|
||||
23
src/app-modes/PracticeMode.js
Normal file
23
src/app-modes/PracticeMode.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import React, {useEffect} from 'react';
|
||||
import '../css/App.css';
|
||||
import useTelegraph from '../hooks/useTelegraph';
|
||||
import MorseBufferDisplay from '../components/MorseBufferDisplay'
|
||||
import MorseDisplay from '../components/MorseDisplay'
|
||||
|
||||
function PracticeMode() {
|
||||
|
||||
const {morseCharBuffer, morseWords, clearHistory} = useTelegraph()
|
||||
|
||||
console.log('PracticeMode.js rendered')
|
||||
|
||||
return (
|
||||
<>
|
||||
<MorseBufferDisplay buffer={morseCharBuffer} /><br/>
|
||||
<MorseDisplay morseWords={morseWords} /><br/>
|
||||
<button onClick={clearHistory}>Clear Morse History</button><br/>
|
||||
</>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default React.memo(PracticeMode);
|
||||
24
src/app-modes/TimedMode.js
Normal file
24
src/app-modes/TimedMode.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import '../css/App.css';
|
||||
import useTelegraph from '../hooks/useTelegraph';
|
||||
import GameClock from '../components/GameClock'
|
||||
import MorseBufferDisplay from '../components/MorseBufferDisplay'
|
||||
import MorseDisplay from '../components/MorseDisplay'
|
||||
|
||||
function TimedMode() {
|
||||
|
||||
const {morseCharBuffer, morseWords} = useTelegraph()
|
||||
|
||||
console.log('TimedMode.js rendered')
|
||||
|
||||
return (
|
||||
<>
|
||||
<GameClock time={30} />
|
||||
<MorseBufferDisplay buffer={morseCharBuffer} /><br/>
|
||||
<MorseDisplay morseWords={morseWords} />
|
||||
</>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default React.memo(TimedMode);
|
||||
13
src/components/ChallengeWord.js
Normal file
13
src/components/ChallengeWord.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import React from "react"
|
||||
|
||||
function ChallengeWord(props) {
|
||||
console.log('ChallengeWord rendered');
|
||||
|
||||
let spannedWord = props.word.split('').map((letter,index) => <span key={index} className="cLetter" id={index}>{letter}</span>)
|
||||
|
||||
return (
|
||||
<div id="challengeWord">{spannedWord}</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ChallengeWord)
|
||||
15
src/components/GameClock.js
Normal file
15
src/components/GameClock.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import React, {useState} from "react"
|
||||
|
||||
function GameClock(props) {
|
||||
console.log('GameClock rendered');
|
||||
|
||||
// const [clockTime, setClockTime] = useState(30)
|
||||
|
||||
// setClockTime(props.time)
|
||||
|
||||
return (
|
||||
<div id="gameclock">{props.time}</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GameClock
|
||||
|
|
@ -1,18 +1,27 @@
|
|||
import React from "react"
|
||||
import React, {useContext} from "react"
|
||||
import {GameModeContext} from "../gameContext"
|
||||
|
||||
function ModePicker() {
|
||||
|
||||
const {switchGameModeTo} = useContext(GameModeContext)
|
||||
|
||||
function handleClick(e) {
|
||||
switchGameModeTo(e.target.id)
|
||||
console.log("Switched to " + e.target.id + " mode.");
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="mode-picker">
|
||||
<div id="practice">
|
||||
Practice
|
||||
<div id="mode-picker">
|
||||
<button id="practice" onClick={handleClick}>
|
||||
Practice
|
||||
</button>
|
||||
<button id="timed" onClick={handleClick}>
|
||||
Timed Mode
|
||||
</button>
|
||||
<button id="challenge" onClick={handleClick}>
|
||||
Challenge Mode
|
||||
</button>
|
||||
</div>
|
||||
<div id="timed">
|
||||
Timed Mode
|
||||
</div>
|
||||
<div id="challenge">
|
||||
Challenge Mode
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ function MorseButton() {
|
|||
)
|
||||
}
|
||||
|
||||
export default MorseButton
|
||||
export default React.memo(MorseButton)
|
||||
|
|
@ -26,15 +26,18 @@ html, body {
|
|||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-item-align: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#mode-picker div {
|
||||
#mode-picker button {
|
||||
background: #fcfcfc;
|
||||
margin: 15px;
|
||||
padding: 5px;
|
||||
-webkit-box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
border: 0px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
#main-content {
|
||||
|
|
@ -60,6 +63,33 @@ html, body {
|
|||
align-self: center;
|
||||
}
|
||||
|
||||
#challengeWord {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
margin-bottom: 20px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
font-size: 40px;
|
||||
font-family: 'Courier';
|
||||
font-weight: bold;
|
||||
background: #EEE;
|
||||
text-transform: uppercase;
|
||||
-ms-flex-item-align: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#morseBufferDisplay {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
|
|
@ -93,7 +123,7 @@ html, body {
|
|||
height: 40px;
|
||||
width: 30px !important;
|
||||
margin-left: 3px;
|
||||
border-radius: 5px;
|
||||
border-radius: 0px;
|
||||
-webkit-box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.2);
|
||||
display: -webkit-box;
|
||||
|
|
@ -197,7 +227,7 @@ html, body {
|
|||
margin: 3px;
|
||||
background: #EEE;
|
||||
white-space: nowrap;
|
||||
border-radius: 5px;
|
||||
border-radius: 0px;
|
||||
-webkit-box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.2);
|
||||
line-height: 2rem;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"version": 3,
|
||||
"mappings": "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",
|
||||
"mappings": "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",
|
||||
"sources": [
|
||||
"../scss/App.scss"
|
||||
],
|
||||
|
|
|
|||
23
src/gameContext.js
Normal file
23
src/gameContext.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import React, {Component} from "react"
|
||||
const GameModeContext = React.createContext()
|
||||
|
||||
class GameModeContextProvider extends Component {
|
||||
state = {
|
||||
gameMode: "practice"
|
||||
}
|
||||
|
||||
switchGameModeTo = (mode = "practice") => {
|
||||
this.setState({gameMode: mode})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<GameModeContext.Provider value={{gameMode: this.state.gameMode, switchGameModeTo: this.switchGameModeTo}}>
|
||||
{this.props.children}
|
||||
</GameModeContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export {GameModeContextProvider, GameModeContext}
|
||||
30
src/hooks/usePracticeMode.js
Normal file
30
src/hooks/usePracticeMode.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import {useEffect} from "react"
|
||||
import useTelegraph from './hooks/useTelegraph'
|
||||
|
||||
|
||||
function usePracticeMode() {
|
||||
const {morseCharBuffer, setMorseWords, morseWords, setMorseCharBuffer} = useTelegraph()
|
||||
|
||||
const morseHistorySize = 5
|
||||
|
||||
useEffect(() => {
|
||||
if (morseCharBuffer.slice(-1) === '/') {
|
||||
// Remove forward slash
|
||||
let val = morseCharBuffer.slice(0,morseCharBuffer.length-1)
|
||||
console.log('val: ', val);
|
||||
|
||||
setMorseWords(prev => [val, ...prev])
|
||||
|
||||
if (morseWords.length >= morseHistorySize) {
|
||||
setMorseWords(prev => prev.slice(0,prev.length-1))
|
||||
}
|
||||
|
||||
setMorseCharBuffer('')
|
||||
}
|
||||
|
||||
// setMorseLettersBuffer(prev => [...prev, morseCharBuffer])
|
||||
// eslint-disable-next-line
|
||||
}, [morseCharBuffer])
|
||||
}
|
||||
|
||||
export default usePracticeMode
|
||||
157
src/hooks/useTelegraph.js
Normal file
157
src/hooks/useTelegraph.js
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import {useState, useEffect} from 'react'
|
||||
|
||||
function useTelegraph(mode = 'practice') {
|
||||
|
||||
const [morseCharBuffer, setMorseCharBuffer] = useState('') // e.g. '-..'
|
||||
const [morseWords, setMorseWords] = useState([]) // e.g. [['-..','.','-,'], ['...','---','...']]
|
||||
|
||||
let charTimer = 0
|
||||
let charTime = 0
|
||||
let gapTimer = 0
|
||||
let gapTime = 0
|
||||
|
||||
const timingUnit = 15 // default: 25
|
||||
|
||||
const ditMaxTime = 5 // default: 3
|
||||
const letterGapMinTime = ditMaxTime*3
|
||||
const wordGapMaxTime = ditMaxTime*7
|
||||
const morseHistorySize = 5
|
||||
|
||||
// Tone Setup
|
||||
let AudioContext = window.AudioContext || window.webkitAudioContext || false
|
||||
let context
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
if (AudioContext) {
|
||||
context = new AudioContext()
|
||||
} else {
|
||||
context = null
|
||||
}
|
||||
|
||||
let o
|
||||
let frequency = 550.0
|
||||
|
||||
function clearHistory() {
|
||||
setMorseWords([])
|
||||
}
|
||||
|
||||
function handleInputStart(event) {
|
||||
|
||||
event.preventDefault()
|
||||
if ((event.keyCode !== 32 && event.target.id !== "morseButton") ||
|
||||
(event.repeat)) {
|
||||
return
|
||||
}
|
||||
if (context.state === 'interrupted') {
|
||||
context.resume()
|
||||
}
|
||||
|
||||
o = context.createOscillator()
|
||||
o.frequency.value = frequency
|
||||
o.type = "sine"
|
||||
|
||||
let g = context.createGain()
|
||||
g.gain.exponentialRampToValueAtTime(0.08, context.currentTime)
|
||||
o.connect(g)
|
||||
g.connect(context.destination)
|
||||
o.start()
|
||||
|
||||
checkGapBetweenInputs()
|
||||
clearInterval(gapTimer)
|
||||
|
||||
startCharTimer()
|
||||
}
|
||||
function startCharTimer() {
|
||||
// Reset character time
|
||||
charTime = 0
|
||||
// Start Character Timer
|
||||
charTimer = setInterval(() => {
|
||||
charTime += 1
|
||||
}, timingUnit);
|
||||
}
|
||||
|
||||
function handleInputEnd(event) {
|
||||
event.preventDefault()
|
||||
if ((event.keyCode !== 32 && event.target.id !== "morseButton") ||
|
||||
(event.repeat)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (charTime <= ditMaxTime) {
|
||||
setMorseCharBuffer(prev => prev + '.')
|
||||
} else {
|
||||
setMorseCharBuffer(prev => prev + '-')
|
||||
}
|
||||
|
||||
stopCharTimer()
|
||||
startGapTimer()
|
||||
o.stop()
|
||||
}
|
||||
|
||||
function stopCharTimer() {
|
||||
clearInterval(charTimer)
|
||||
charTimer = 0
|
||||
}
|
||||
|
||||
function startGapTimer() {
|
||||
gapTime = 0
|
||||
gapTimer = setInterval(() => {
|
||||
gapTime += 1
|
||||
|
||||
// Gap between words
|
||||
if (gapTime >= wordGapMaxTime && mode === 'practice') {
|
||||
setMorseCharBuffer(prev => prev + '/')
|
||||
clearInterval(gapTimer)
|
||||
gapTimer = 0
|
||||
gapTime = 0
|
||||
}
|
||||
}, timingUnit);
|
||||
}
|
||||
|
||||
function checkGapBetweenInputs() {
|
||||
// Check Gap between letters
|
||||
if ((gapTime >= letterGapMinTime) && (gapTime < wordGapMaxTime)) {
|
||||
setMorseCharBuffer(prev => prev + ' ')
|
||||
clearInterval(gapTimer)
|
||||
gapTimer = 0
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleInputStart)
|
||||
document.addEventListener('keyup', handleInputEnd)
|
||||
|
||||
const morseButton = document.getElementById('morseButton')
|
||||
morseButton.addEventListener('mousedown', handleInputStart)
|
||||
morseButton.addEventListener('touchstart', handleInputStart)
|
||||
morseButton.addEventListener('mouseup', handleInputEnd)
|
||||
morseButton.addEventListener('touchend', handleInputEnd)
|
||||
|
||||
return function cleanup() {
|
||||
document.removeEventListener('keydown', handleInputStart)
|
||||
document.removeEventListener('keyup', handleInputEnd)
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (morseCharBuffer.slice(-1) === '/') {
|
||||
// Remove forward slash
|
||||
let val = morseCharBuffer.slice(0,morseCharBuffer.length-1)
|
||||
console.log('val: ', val);
|
||||
|
||||
setMorseWords(prev => [val, ...prev])
|
||||
|
||||
if (morseWords.length >= morseHistorySize) {
|
||||
setMorseWords(prev => prev.slice(0,prev.length-1))
|
||||
}
|
||||
|
||||
setMorseCharBuffer('')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
}, [morseCharBuffer])
|
||||
|
||||
return {morseCharBuffer, morseWords, clearHistory, setMorseCharBuffer, setMorseWords}
|
||||
}
|
||||
|
||||
export default useTelegraph
|
||||
|
|
@ -3,8 +3,13 @@ import ReactDOM from 'react-dom';
|
|||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import {GameModeContextProvider} from "./gameContext"
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
ReactDOM.render(
|
||||
<GameModeContextProvider>
|
||||
<App />
|
||||
</GameModeContextProvider>
|
||||
, document.getElementById('root'));
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
$main-bg-color: #FFF;
|
||||
$main-box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.2);
|
||||
$main-border-radius: 5px;
|
||||
$main-border-radius: 0px;
|
||||
// $border-radius-neumorphic: 0px -6px 10px rgba(255, 255, 255, 1), 0px 4px 15px rgba(0, 0, 0, 0.15);
|
||||
|
||||
html, body {
|
||||
|
|
@ -20,11 +20,13 @@ html, body {
|
|||
}
|
||||
#mode-picker {
|
||||
display: flex;
|
||||
div {
|
||||
align-self: center;
|
||||
button {
|
||||
background: #fcfcfc;
|
||||
margin: 15px;
|
||||
padding: 5px;
|
||||
box-shadow: $main-box-shadow;
|
||||
border: 0px;
|
||||
border-radius: $main-border-radius;
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +45,24 @@ html, body {
|
|||
border-radius: 50%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#challengeWord {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// border: 1px solid green;
|
||||
height: 50px;
|
||||
margin-bottom: 20px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
font-size: 40px;
|
||||
font-family: 'Courier';
|
||||
font-weight: bold;
|
||||
background: #EEE;
|
||||
text-transform: uppercase;
|
||||
align-self: center;
|
||||
}
|
||||
#morseBufferDisplay {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
|
|||
Loading…
Reference in a new issue