Skip to content

Commit f8e0a1d

Browse files
committed
feat: command palette 5
1 parent 3e50132 commit f8e0a1d

File tree

5 files changed

+220
-1
lines changed

5 files changed

+220
-1
lines changed

src/components/CommandPalette.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import { useToast } from '../hooks/useToast';
77
import { SIDEBAR_KEYS, remove as removeLocalStorageItem } from '../utils/LocalStorageManager';
88
import { version } from '../version'; // Import the version
99
import LiveClock from './LiveClock'; // Import LiveClock
10-
import DigitalRain from './DigitalRain'; // Import DigitalRain
10+
import GenerativeArt from './GenerativeArt'; // Import GenerativeArt
11+
import TextTransformer from './TextTransformer'; // Import TextTransformer
12+
import Stopwatch from './Stopwatch'; // Import Stopwatch
1113
import { filterItems } from '../utils/search'; // Import the search utility
1214

1315
const CommandPalette = ({ isOpen, setIsOpen, openGenericModal, toggleDigitalRain }) => {
@@ -130,6 +132,15 @@ const CommandPalette = ({ isOpen, setIsOpen, openGenericModal, toggleDigitalRain
130132
case 'digitalRain':
131133
toggleDigitalRain();
132134
break;
135+
case 'generateArt':
136+
openGenericModal('Generative Art', <GenerativeArt />);
137+
break;
138+
case 'leetTransformer':
139+
openGenericModal('Leet Speak Transformer', <TextTransformer />);
140+
break;
141+
case 'stopwatch':
142+
openGenericModal('Stopwatch', <Stopwatch />);
143+
break;
133144
default:
134145
break;
135146
}

src/components/GenerativeArt.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React, { useMemo } from 'react';
2+
import ReactDOMServer from 'react-dom/server';
3+
import { DownloadSimple } from '@phosphor-icons/react';
4+
5+
const GenerativeArt = () => {
6+
const art = useMemo(() => {
7+
const SVG_SIZE = 500;
8+
const NUM_SHAPES = 25;
9+
const shapes = [];
10+
11+
const colors = ['#FBBF24', '#A7F3D0', '#F87171', '#60A5FA', '#A78BFA'];
12+
13+
for (let i = 0; i < NUM_SHAPES; i++) {
14+
const x = Math.random() * SVG_SIZE;
15+
const y = Math.random() * SVG_SIZE;
16+
const width = 10 + Math.random() * 150;
17+
const height = 10 + Math.random() * 150;
18+
const fill = colors[Math.floor(Math.random() * colors.length)];
19+
const rotation = Math.random() * 360;
20+
const opacity = 0.5 + Math.random() * 0.5;
21+
22+
shapes.push(
23+
<rect
24+
key={i}
25+
x={x}
26+
y={y}
27+
width={width}
28+
height={height}
29+
fill={fill}
30+
opacity={opacity}
31+
transform={`rotate(${rotation} ${x + width / 2} ${y + height / 2})`}
32+
/>
33+
);
34+
}
35+
36+
return (
37+
<svg width="100%" height="100%" viewBox={`0 0 ${SVG_SIZE} ${SVG_SIZE}`} xmlns="http://www.w3.org/2000/svg">
38+
<defs>
39+
<clipPath id="art-board">
40+
<rect width={SVG_SIZE} height={SVG_SIZE} />
41+
</clipPath>
42+
</defs>
43+
<rect width={SVG_SIZE} height={SVG_SIZE} fill="#1F2937" />
44+
<g clipPath="url(#art-board)">
45+
{shapes}
46+
</g>
47+
</svg>
48+
);
49+
}, []);
50+
51+
const handleDownload = () => {
52+
const svgString = ReactDOMServer.renderToString(art);
53+
const blob = new Blob([svgString], { type: 'image/svg+xml' });
54+
const url = URL.createObjectURL(blob);
55+
const link = document.createElement('a');
56+
link.href = url;
57+
link.download = 'generative-art.svg';
58+
document.body.appendChild(link);
59+
link.click();
60+
document.body.removeChild(link);
61+
URL.revokeObjectURL(url);
62+
};
63+
64+
return (
65+
<div>
66+
<div style={{ width: '100%', height: '60vh', border: '1px solid #374151', borderRadius: '8px', overflow: 'hidden' }}>
67+
{art}
68+
</div>
69+
<div className="flex justify-center mt-4">
70+
<button
71+
onClick={handleDownload}
72+
className="flex items-center gap-2 text-lg font-arvo font-normal px-6 py-2 rounded-md border transition-colors duration-300 ease-in-out border-green-700 bg-green-800/50 text-white hover:bg-green-700/50"
73+
>
74+
<DownloadSimple size={24} />
75+
Download SVG
76+
</button>
77+
</div>
78+
</div>
79+
);
80+
};
81+
82+
export default GenerativeArt;

src/components/Stopwatch.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React, { useState, useEffect, useRef } from 'react';
2+
3+
const Stopwatch = () => {
4+
const [time, setTime] = useState(0);
5+
const [isRunning, setIsRunning] = useState(false);
6+
const intervalRef = useRef(null);
7+
8+
useEffect(() => {
9+
if (isRunning) {
10+
intervalRef.current = setInterval(() => {
11+
setTime(prevTime => prevTime + 10); // Update every 10ms for smoother display
12+
}, 10);
13+
} else {
14+
clearInterval(intervalRef.current);
15+
}
16+
17+
return () => clearInterval(intervalRef.current);
18+
}, [isRunning]);
19+
20+
const handleStart = () => {
21+
setIsRunning(true);
22+
};
23+
24+
const handleStop = () => {
25+
setIsRunning(false);
26+
};
27+
28+
const handleReset = () => {
29+
setIsRunning(false);
30+
setTime(0);
31+
};
32+
33+
const formatTime = () => {
34+
const milliseconds = `0${(time % 1000) / 10}`.slice(-2);
35+
const seconds = `0${Math.floor(time / 1000) % 60}`.slice(-2);
36+
const minutes = `0${Math.floor(time / 60000) % 60}`.slice(-2);
37+
const hours = `0${Math.floor(time / 3600000)}`.slice(-2);
38+
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
39+
};
40+
41+
const buttonStyle = "w-24 px-4 py-2 text-lg font-arvo rounded-md border transition-colors duration-300 ease-in-out";
42+
const activeButtonStyle = "bg-green-800/50 border-green-700 text-white hover:bg-green-700/50";
43+
const inactiveButtonStyle = "bg-gray-700/50 border-gray-600 text-gray-300 hover:bg-gray-600/50";
44+
const stopButtonStyle = "bg-red-800/50 border-red-700 text-white hover:bg-red-700/50";
45+
46+
return (
47+
<div className="flex flex-col items-center gap-6">
48+
<div className="font-mono text-5xl text-gray-100 p-4 rounded-lg bg-gray-900/50 w-full text-center">
49+
{formatTime()}
50+
</div>
51+
<div className="flex gap-4">
52+
{!isRunning ? (
53+
<button onClick={handleStart} className={`${buttonStyle} ${activeButtonStyle}`}>
54+
Start
55+
</button>
56+
) : (
57+
<button onClick={handleStop} className={`${buttonStyle} ${stopButtonStyle}`}>
58+
Stop
59+
</button>
60+
)}
61+
<button onClick={handleReset} className={`${buttonStyle} ${inactiveButtonStyle}`}>
62+
Reset
63+
</button>
64+
</div>
65+
</div>
66+
);
67+
};
68+
69+
export default Stopwatch;

src/components/TextTransformer.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { useState } from 'react';
2+
3+
const TextTransformer = () => {
4+
const [inputText, setInputText] = useState('');
5+
6+
const toLeet = (text) => {
7+
const leetMap = {
8+
'a': '4', 'b': '8', 'e': '3', 'g': '6', 'l': '1',
9+
'o': '0', 's': '5', 't': '7', 'z': '2',
10+
'A': '4', 'B': '8', 'E': '3', 'G': '6', 'L': '1',
11+
'O': '0', 'S': '5', 'T': '7', 'Z': '2'
12+
};
13+
return text.split('').map(char => leetMap[char] || char).join('');
14+
};
15+
16+
const transformedText = toLeet(inputText);
17+
18+
const textAreaStyle = {
19+
width: '100%',
20+
minHeight: '150px',
21+
backgroundColor: '#1F2937',
22+
color: '#E5E7EB',
23+
border: '1px solid #374151',
24+
borderRadius: '8px',
25+
padding: '10px',
26+
fontFamily: 'monospace',
27+
fontSize: '16px',
28+
};
29+
30+
return (
31+
<div className="flex flex-col gap-4">
32+
<div>
33+
<label className="block mb-2 text-lg font-arvo text-gray-300">Your Text</label>
34+
<textarea
35+
style={textAreaStyle}
36+
value={inputText}
37+
onChange={(e) => setInputText(e.target.value)}
38+
placeholder="Type here..."
39+
/>
40+
</div>
41+
<div>
42+
<label className="block mb-2 text-lg font-arvo text-gray-300">1337 5p34k</label>
43+
<textarea
44+
style={textAreaStyle}
45+
value={transformedText}
46+
readOnly
47+
placeholder="...b3c0m3s 7h15"
48+
/>
49+
</div>
50+
</div>
51+
);
52+
};
53+
54+
export default TextTransformer;

src/hooks/useSearchableData.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ const useSearchableData = () => {
7878
{ title: 'Her Daim', type: 'command', commandId: 'herDaim' },
7979
{ title: 'Show Current Time', type: 'command', commandId: 'showTime' },
8080
{ title: 'Toggle Digital Rain', type: 'command', commandId: 'digitalRain' },
81+
{ title: 'Generate Art', type: 'command', commandId: 'generateArt' },
82+
{ title: 'Leet Speak Transformer', type: 'command', commandId: 'leetTransformer' },
83+
{ title: 'Stopwatch', type: 'command', commandId: 'stopwatch' },
8184
];
8285

8386
setItems([...staticRoutes, ...customCommands, ...allPosts, ...allProjects, ...allLogs, ...allApps]);

0 commit comments

Comments
 (0)