summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2025-03-05 09:44:03 +0100
committerYuval Adam <_@yuv.al>2025-03-05 09:44:03 +0100
commitd9abe6078499206ac65a2721433f502a7b2432f1 (patch)
tree5ea48bb732da9c98929f98587f9b49a6771f8f05
parent45f78bd2cb9bbef15fae24d4925da095da8c1e05 (diff)
Use cw dependency
-rw-r--r--morsehero/package-lock.json7
-rw-r--r--morsehero/package.json1
-rw-r--r--morsehero/src/pages/HomePage.tsx80
-rw-r--r--morsehero/src/pages/chart/ChartPage.tsx43
4 files changed, 53 insertions, 78 deletions
diff --git a/morsehero/package-lock.json b/morsehero/package-lock.json
index fb0bda6..c7079ca 100644
--- a/morsehero/package-lock.json
+++ b/morsehero/package-lock.json
@@ -8,6 +8,7 @@
"name": "morsehero",
"version": "0.0.0",
"dependencies": {
+ "cw": "^0.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.2.0"
@@ -1875,6 +1876,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cw": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/cw/-/cw-0.3.0.tgz",
+ "integrity": "sha512-fI5FLB6UdeG1log8Gq7IhJl7JJ0ni4OCSHymJAXRdYLprYHnRLtT9JMPtap4liRZ/fQ54a+w/uwXZS+89C6Ybg==",
+ "license": "ISC"
+ },
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
diff --git a/morsehero/package.json b/morsehero/package.json
index 9e69011..4967122 100644
--- a/morsehero/package.json
+++ b/morsehero/package.json
@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "cw": "^0.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.2.0"
diff --git a/morsehero/src/pages/HomePage.tsx b/morsehero/src/pages/HomePage.tsx
index e67a9a8..7ec4e90 100644
--- a/morsehero/src/pages/HomePage.tsx
+++ b/morsehero/src/pages/HomePage.tsx
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router';
+import * as cw from 'cw';
import '../base.css';
import '../styles.css';
@@ -19,7 +20,7 @@ const HomePage = () => {
const [options, setOptions] = useState<string[]>([]);
const [wpm, setWpm] = useState(20);
const [showHints, setShowHints] = useState(false);
-
+
const actxRef = useRef<any>(null);
const optionButtonsRef = useRef<Array<HTMLButtonElement | null>>([null, null, null, null]);
@@ -29,17 +30,13 @@ const HomePage = () => {
const specialChars = '.,?/=';
const allChars = letters + numbers + specialChars;
- // Initialize the game when started
+ // Initialize the game when started - this happens on user interaction (button click)
const startGame = () => {
try {
- if (window.cw) {
- actxRef.current = window.cw.initAudioContext({ tone: 600 });
- setGameStarted(true);
- newRound();
- } else {
- console.error('CW.js not loaded');
- alert('Failed to initialize audio. Please try again.');
- }
+ // Initialize audio context on user interaction
+ actxRef.current = cw.initAudioContext({ tone: 600 });
+ setGameStarted(true);
+ newRound();
} catch (error) {
console.error('Failed to initialize audio context:', error);
alert('Failed to initialize audio. Please try again.');
@@ -58,7 +55,7 @@ const HomePage = () => {
// Generate options (one correct, three random)
let newOptions = [newChar];
-
+
// Add three random unique characters
while (newOptions.length < 4) {
const randomChar = allChars[Math.floor(Math.random() * allChars.length)];
@@ -66,7 +63,7 @@ const HomePage = () => {
newOptions.push(randomChar);
}
}
-
+
// Shuffle options
newOptions = shuffleArray(newOptions);
setOptions(newOptions);
@@ -78,10 +75,9 @@ const HomePage = () => {
// Play Morse code for a character
const playMorseCode = (char: string) => {
if (actxRef.current) {
- window.cw.playText({
- text: char,
+ cw.play(char, {
wpm: wpm,
- audioContext: actxRef.current
+ actx: actxRef.current
});
}
};
@@ -92,7 +88,7 @@ const HomePage = () => {
// Correct answer
setFeedback('Correct!');
setScore(prevScore => prevScore + 1);
-
+
// Add to score tracker
const scoreTracker = document.getElementById('scoreTracker');
if (scoreTracker) {
@@ -100,25 +96,25 @@ const HomePage = () => {
marker.className = 'score-marker correct';
scoreTracker.appendChild(marker);
}
-
+
// Disable all buttons
optionButtonsRef.current.forEach(button => {
if (button) button.disabled = true;
});
-
+
// Highlight the correct button
const correctButtonIndex = options.indexOf(currentChar);
if (correctButtonIndex !== -1 && optionButtonsRef.current[correctButtonIndex]) {
const button = optionButtonsRef.current[correctButtonIndex];
if (button) button.classList.add('correct');
}
-
+
// Move to next round after delay
setTimeout(newRound, 1500);
} else {
// Incorrect answer
setFeedback('Incorrect! The correct answer was ' + currentChar);
-
+
// Add to score tracker
const scoreTracker = document.getElementById('scoreTracker');
if (scoreTracker) {
@@ -126,29 +122,29 @@ const HomePage = () => {
marker.className = 'score-marker incorrect';
scoreTracker.appendChild(marker);
}
-
+
// Disable all buttons
optionButtonsRef.current.forEach(button => {
if (button) button.disabled = true;
});
-
+
// Highlight the incorrect button and show the correct one
const selectedButtonIndex = options.indexOf(selectedChar);
if (selectedButtonIndex !== -1 && optionButtonsRef.current[selectedButtonIndex]) {
const button = optionButtonsRef.current[selectedButtonIndex];
if (button) button.classList.add('incorrect');
}
-
+
const correctButtonIndex = options.indexOf(currentChar);
if (correctButtonIndex !== -1 && optionButtonsRef.current[correctButtonIndex]) {
const button = optionButtonsRef.current[correctButtonIndex];
if (button) button.classList.add('correct');
}
-
+
// Move to next round after delay
setTimeout(newRound, 2000);
}
-
+
setTotalPlayed(prevTotal => prevTotal + 1);
};
@@ -156,9 +152,9 @@ const HomePage = () => {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (!gameStarted) return;
-
+
const key = event.key.toUpperCase();
-
+
// Find if any button has this character
const index = options.findIndex(option => option === key);
if (index !== -1) {
@@ -172,25 +168,13 @@ const HomePage = () => {
}
}
};
-
+
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [gameStarted, options]);
- // Load CW.js script
- useEffect(() => {
- const script = document.createElement('script');
- script.src = 'https://cdn.jsdelivr.net/npm/cw@latest/dist/cw.min.js';
- script.async = true;
- document.body.appendChild(script);
-
- return () => {
- document.body.removeChild(script);
- };
- }, []);
-
// Utility function to shuffle an array
const shuffleArray = (array: string[]) => {
const newArray = [...array];
@@ -203,7 +187,7 @@ const HomePage = () => {
// Get Morse code representation for a character
const getMorseCode = (char: string) => {
- const morseMap: {[key: string]: string} = {
+ const morseMap: { [key: string]: string } = {
'A': '·−', 'B': '−···', 'C': '−·−·', 'D': '−··', 'E': '·',
'F': '··−·', 'G': '−−·', 'H': '····', 'I': '··', 'J': '·−−−',
'K': '−·−', 'L': '·−··', 'M': '−−', 'N': '−·', 'O': '−−−',
@@ -213,7 +197,7 @@ const HomePage = () => {
'4': '····−', '5': '·····', '6': '−····', '7': '−−···', '8': '−−−··',
'9': '−−−−·', '.': '·−·−·−', ',': '−−··−−', '?': '··−−··', '/': '−··−·', '=': '−···−'
};
-
+
return morseMap[char] || '';
};
@@ -246,9 +230,9 @@ const HomePage = () => {
<div className="game-container">
<div className="options">
{options.map((option, index) => (
- <button
+ <button
key={index}
- className="option-button"
+ className="option-button"
ref={el => {
optionButtonsRef.current[index] = el;
}}
@@ -265,7 +249,7 @@ const HomePage = () => {
<div className="settings">
<label htmlFor="wpmSelect">Speed:</label>
- <select
+ <select
id="wpmSelect"
value={wpm}
onChange={(e) => setWpm(parseInt(e.target.value))}
@@ -277,9 +261,9 @@ const HomePage = () => {
<option value="30">30 WPM</option>
</select>
<div className="hint-setting">
- <input
- type="checkbox"
- id="hintMode"
+ <input
+ type="checkbox"
+ id="hintMode"
name="hintMode"
checked={showHints}
onChange={(e) => setShowHints(e.target.checked)}
diff --git a/morsehero/src/pages/chart/ChartPage.tsx b/morsehero/src/pages/chart/ChartPage.tsx
index 9f2b56d..88c1ec2 100644
--- a/morsehero/src/pages/chart/ChartPage.tsx
+++ b/morsehero/src/pages/chart/ChartPage.tsx
@@ -1,5 +1,6 @@
import { useEffect, useRef } from 'react';
import { Link } from 'react-router';
+import * as cw from 'cw';
import '../../base.css';
import './styles.css';
@@ -14,12 +15,10 @@ const ChartPage = () => {
// Initialize audio context
const initAudio = () => {
+ if (actxRef.current) return; // Already initialized
+
try {
- if (window.cw) {
- actxRef.current = window.cw.initAudioContext({ tone: 600 });
- } else {
- console.error('CW.js not loaded');
- }
+ actxRef.current = cw.initAudioContext({ tone: 600 });
} catch (error) {
console.error('Failed to initialize audio context:', error);
}
@@ -27,28 +26,28 @@ const ChartPage = () => {
// Play Morse code for a character
const playCharacter = (character: string) => {
+ // Initialize audio on first user interaction
if (!actxRef.current) {
initAudio();
}
-
+
if (actxRef.current) {
// Remove 'playing' class from all items
document.querySelectorAll('.morse-item').forEach(item => {
item.classList.remove('playing');
});
-
+
// Add 'playing' class to the clicked item
const clickedItem = document.querySelector(`.morse-item[data-char="${character}"]`);
if (clickedItem) {
clickedItem.classList.add('playing');
}
-
- window.cw.playText({
- text: character,
+
+ cw.play(character, {
wpm: 20,
- audioContext: actxRef.current
+ actx: actxRef.current
});
-
+
// Remove the 'playing' class after the audio finishes
setTimeout(() => {
if (clickedItem) {
@@ -58,22 +57,6 @@ const ChartPage = () => {
}
};
- // Load CW.js script
- useEffect(() => {
- const script = document.createElement('script');
- script.src = 'https://cwjs.mastercw.com/cw.min.js';
- script.async = true;
- document.body.appendChild(script);
-
- script.onload = () => {
- initAudio();
- };
-
- return () => {
- document.body.removeChild(script);
- };
- }, []);
-
// Add click event listeners to all morse items
useEffect(() => {
const handleMorseItemClick = (e: Event) => {
@@ -178,7 +161,7 @@ const ChartPage = () => {
// Helper function to get morse code representation
const getMorseCode = (char: string) => {
- const morseMap: {[key: string]: string} = {
+ const morseMap: { [key: string]: string } = {
'A': '·−', 'B': '−···', 'C': '−·−·', 'D': '−··', 'E': '·',
'F': '··−·', 'G': '−−·', 'H': '····', 'I': '··', 'J': '·−−−',
'K': '−·−', 'L': '·−··', 'M': '−−', 'N': '−·', 'O': '−−−',
@@ -188,7 +171,7 @@ const getMorseCode = (char: string) => {
'4': '····−', '5': '·····', '6': '−····', '7': '−−···', '8': '−−−··',
'9': '−−−−·', '.': '·−·−·−', ',': '−−··−−', '?': '··−−··', '/': '−··−·', '=': '−···−'
};
-
+
return morseMap[char] || '';
};