Skip to content

Commit ab27c95

Browse files
committed
app: morse code translator
1 parent 5a465cc commit ab27c95

File tree

4 files changed

+168
-0
lines changed

4 files changed

+168
-0
lines changed

public/apps/apps.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,13 @@
195195
"title": "CSS Unit Converter",
196196
"description": "Convert between px, em, rem, vw, vh, and % units.",
197197
"icon": "RulerIcon"
198+
},
199+
{
200+
"slug": "morse-code-translator",
201+
"to": "/apps/morse-code-translator",
202+
"title": "Morse Code Translator",
203+
"description": "Translate text to Morse code and vice-versa, with audio playback.",
204+
"icon": "TranslateIcon"
198205
}
199206
]
200207
},

src/components/AnimatedRoutes.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import ConnectFourPage from '../pages/apps/ConnectFourPage'; // Import ConnectFo
5353
import ImageCompressorPage from '../pages/apps/ImageCompressorPage'; // Import ImageCompressorPage
5454
import StopwatchAppPage from '../pages/StopwatchAppPage'; // Import StopwatchAppPage
5555
import PomodoroTimerPage from '../pages/apps/PomodoroTimerPage';
56+
import MorseCodeTranslatorPage from '../pages/apps/MorseCodeTranslatorPage';
5657
import SettingsPage from '../pages/SettingsPage';
5758

5859
import UsefulLinksPage from '../pages/UsefulLinksPage';
@@ -534,6 +535,10 @@ function AnimatedRoutes() {
534535
path="/apps::pomodoro"
535536
element={<Navigate to="/apps/pomodoro-timer" replace />}
536537
/>
538+
<Route
539+
path="/apps::mct"
540+
element={<Navigate to="/apps/morse-code-translator" replace />}
541+
/>
537542
{/* End of hardcoded redirects */}
538543
<Route
539544
path="/apps/ip"
@@ -1026,6 +1031,20 @@ function AnimatedRoutes() {
10261031
</motion.div>
10271032
}
10281033
/>
1034+
<Route
1035+
path="/apps/morse-code-translator"
1036+
element={
1037+
<motion.div
1038+
initial="initial"
1039+
animate="in"
1040+
exit="out"
1041+
variants={pageVariants}
1042+
transition={pageTransition}
1043+
>
1044+
<MorseCodeTranslatorPage />
1045+
</motion.div>
1046+
}
1047+
/>
10291048
{/* D&D specific 404 page */}
10301049
<Route
10311050
path="/stories/*"
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import React, { useState } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { ArrowLeftIcon, SpeakerSimpleHighIcon } from '@phosphor-icons/react';
4+
import useSeo from '../../hooks/useSeo';
5+
6+
const MORSE_CODE_MAP = {
7+
'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', 'G': '--.', 'H': '....', 'I': '..', 'J': '.---',
8+
'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-',
9+
'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-', 'Y': '-.--', 'Z': '--..',
10+
'1': '.----', '2': '..---', '3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...', '8': '---..', '9': '----.', '0': '-----',
11+
' ': '/', ',': '--..--', '.': '.-.-.-', '?': '..--..', '/': '-..-.', '-': '-....-', '(': '-.--.', ')': '-.--.-'
12+
};
13+
14+
const REVERSE_MORSE_CODE_MAP = Object.fromEntries(Object.entries(MORSE_CODE_MAP).map(([key, value]) => [value, key]));
15+
16+
const MorseCodeTranslatorPage = () => {
17+
useSeo({
18+
title: 'Morse Code Translator | Fezcodex',
19+
description: 'Translate text to Morse code and vice-versa, with audio playback.',
20+
keywords: ['Fezcodex', 'morse code', 'translator', 'converter'],
21+
ogTitle: 'Morse Code Translator | Fezcodex',
22+
ogDescription: 'Translate text to Morse code and vice-versa, with audio playback.',
23+
ogImage: 'https://fezcode.github.io/logo512.png',
24+
twitterCard: 'summary_large_image',
25+
twitterTitle: 'Morse Code Translator | Fezcodex',
26+
twitterDescription: 'Translate text to Morse code and vice-versa, with audio playback.',
27+
twitterImage: 'https://fezcode.github.io/logo512.png',
28+
});
29+
30+
const [text, setText] = useState('');
31+
const [morseCode, setMorseCode] = useState('');
32+
33+
const handleTextChange = (e) => {
34+
const newText = e.target.value.toUpperCase();
35+
setText(newText);
36+
const newMorseCode = newText.split('').map(char => MORSE_CODE_MAP[char] || '').join(' ');
37+
setMorseCode(newMorseCode);
38+
};
39+
40+
const handleMorseChange = (e) => {
41+
const newMorseCode = e.target.value;
42+
setMorseCode(newMorseCode);
43+
const newText = newMorseCode.split(' ').map(code => REVERSE_MORSE_CODE_MAP[code] || '').join('');
44+
setText(newText);
45+
};
46+
47+
const playMorseCode = () => {
48+
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
49+
const dotDuration = 80;
50+
const dashDuration = dotDuration * 3;
51+
const spaceDuration = dotDuration;
52+
let time = audioContext.currentTime;
53+
54+
const playTone = (duration) => {
55+
const oscillator = audioContext.createOscillator();
56+
const gainNode = audioContext.createGain();
57+
58+
oscillator.connect(gainNode);
59+
gainNode.connect(audioContext.destination);
60+
61+
oscillator.frequency.value = 600; // A suitable frequency for the tone
62+
gainNode.gain.setValueAtTime(0, time);
63+
gainNode.gain.linearRampToValueAtTime(1, time + 0.01);
64+
gainNode.gain.linearRampToValueAtTime(0, time + duration);
65+
66+
oscillator.start(time);
67+
oscillator.stop(time + duration);
68+
};
69+
70+
for (const char of morseCode) {
71+
if (char === '.') {
72+
playTone(dotDuration / 1000);
73+
time += (dotDuration + spaceDuration) / 1000;
74+
} else if (char === '-') {
75+
playTone(dashDuration / 1000);
76+
time += (dashDuration + spaceDuration) / 1000;
77+
} else if (char === ' ') {
78+
time += (dotDuration * 3) / 1000;
79+
} else if (char === '/') {
80+
time += (dotDuration * 7) / 1000;
81+
}
82+
}
83+
};
84+
85+
return (
86+
<div className="py-16 sm:py-24">
87+
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-gray-300">
88+
<Link to="/apps" className="text-article hover:underline flex items-center justify-center gap-2 text-lg mb-4">
89+
<ArrowLeftIcon size={24} /> Back to Apps
90+
</Link>
91+
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl mb-4 flex items-center">
92+
<span className="codex-color">fc</span>
93+
<span className="separator-color">::</span>
94+
<span className="apps-color">apps</span>
95+
<span className="separator-color">::</span>
96+
<span className="single-app-color">mct</span>
97+
</h1>
98+
<hr className="border-gray-700" />
99+
<div className="flex justify-center items-center mt-16">
100+
<div className="bg-app-alpha-10 border-app-alpha-50 text-app hover:bg-app/15 group border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform transition-all duration-300 ease-in-out scale-105 overflow-hidden h-full w-full max-w-4xl">
101+
<div className="absolute top-0 left-0 w-full h-full opacity-10" style={{ backgroundImage: 'radial-gradient(circle, white 1px, transparent 1px)', backgroundSize: '10px 10px' }}></div>
102+
<div className="relative z-10 p-4">
103+
<h1 className="text-3xl font-arvo font-normal mb-4 text-app">Morse Code Translator</h1>
104+
<hr className="border-gray-700 mb-6" />
105+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
106+
<div>
107+
<label htmlFor="text-input" className="block text-lg font-semibold text-app mb-2">Text</label>
108+
<textarea
109+
id="text-input"
110+
className="w-full h-48 p-4 bg-gray-900/50 font-mono resize-none border rounded-md focus:ring-0 text-app-light border-app-alpha-50"
111+
value={text}
112+
onChange={handleTextChange}
113+
placeholder="Enter text to translate..."
114+
/>
115+
</div>
116+
<div>
117+
<label htmlFor="morse-input" className="block text-lg font-semibold text-app mb-2">Morse Code</label>
118+
<textarea
119+
id="morse-input"
120+
className="w-full h-48 p-4 bg-gray-900/50 font-mono resize-none border rounded-md focus:ring-0 text-app-light border-app-alpha-50"
121+
value={morseCode}
122+
onChange={handleMorseChange}
123+
placeholder="Enter morse code to translate..."
124+
/>
125+
</div>
126+
</div>
127+
<div className="flex justify-center mt-6">
128+
<button onClick={playMorseCode} className="p-4 rounded-full bg-tb text-app border-app-alpha-50 hover:bg-app/15 border flex items-center gap-2">
129+
<SpeakerSimpleHighIcon size={32} /> Play Sound
130+
</button>
131+
</div>
132+
</div>
133+
</div>
134+
</div>
135+
</div>
136+
</div>
137+
);
138+
};
139+
140+
export default MorseCodeTranslatorPage;

src/utils/appIcons.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
XCircleIcon,
3232
ArrowsInLineHorizontalIcon,
3333
TimerIcon,
34+
TranslateIcon,
3435
} from '@phosphor-icons/react';
3536

3637
export const appIcons = {
@@ -66,4 +67,5 @@ export const appIcons = {
6667
XCircleIcon,
6768
ArrowsInLineHorizontalIcon,
6869
TimerIcon,
70+
TranslateIcon,
6971
};

0 commit comments

Comments
 (0)