Initial commit
This commit is contained in:
101
public/App.tsx
Normal file
101
public/App.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
import React, { useEffect, useCallback, useState, useRef } from 'react';
|
||||
import { Sequencer } from './components/Sequencer';
|
||||
import { useDrumMachine } from './hooks/useDrumMachine';
|
||||
import { useSession } from './hooks/useSession';
|
||||
import { useWebSocket } from './hooks/useWebSocket';
|
||||
import { useCursors } from './hooks/useCursors';
|
||||
import { ShareIcon, CursorIcon } from './components/icons';
|
||||
|
||||
function App(): React.ReactNode {
|
||||
const sessionId = useSession();
|
||||
const { lastMessage, sendMessage, isConnected, isSynchronized, clientId } = useWebSocket(sessionId);
|
||||
const drumMachine = useDrumMachine(lastMessage, sendMessage);
|
||||
const mainRef = useRef<HTMLElement>(null);
|
||||
const cursors = useCursors(sendMessage, lastMessage, clientId, mainRef);
|
||||
const { isPlaying, startPlayback, stopPlayback } = drumMachine;
|
||||
|
||||
const [showCopyMessage, setShowCopyMessage] = useState(false);
|
||||
|
||||
const handleKeyDown = useCallback((event: KeyboardEvent) => {
|
||||
if (event.code === 'Space' && isSynchronized) {
|
||||
event.preventDefault();
|
||||
if (isPlaying) {
|
||||
stopPlayback();
|
||||
} else {
|
||||
startPlayback();
|
||||
}
|
||||
}
|
||||
}, [isPlaying, startPlayback, stopPlayback, isSynchronized]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [handleKeyDown]);
|
||||
|
||||
const handleShareSession = () => {
|
||||
navigator.clipboard.writeText(window.location.href).then(() => {
|
||||
setShowCopyMessage(true);
|
||||
setTimeout(() => setShowCopyMessage(false), 3000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col items-center justify-center p-4 font-sans">
|
||||
<div className="w-full max-w-6xl mx-auto">
|
||||
<header className="mb-6 text-center relative z-10">
|
||||
<h1 className="text-4xl font-bold text-slate-800 tracking-tight">AG Beats</h1>
|
||||
<p className="text-slate-500 mt-2">Craft your beats and bass lines with this interactive step sequencer.</p>
|
||||
<div className="absolute top-1/2 right-0 transform -translate-y-1/2 flex flex-col items-start gap-2">
|
||||
<div className={`text-xs font-semibold ${isConnected ? 'text-green-500' : 'text-red-500'}`}>
|
||||
{isConnected ? '● Connected' : '● Disconnected'}
|
||||
</div>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={handleShareSession}
|
||||
className="flex items-center justify-center gap-2 px-2 lg:px-3 py-1 lg:py-2 text-xs lg:text-sm font-semibold text-slate-700 bg-white border border-slate-300 rounded-md shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-orange-500"
|
||||
>
|
||||
<ShareIcon className="w-5 h-5" />
|
||||
<span className="hidden lg:inline">Share Session</span>
|
||||
</button>
|
||||
{showCopyMessage && (
|
||||
<div className="absolute top-full right-0 mt-2 p-2 text-xs text-white bg-slate-800 rounded-md shadow-lg transition-opacity duration-300 ease-in-out z-50">
|
||||
Session link copied. Send it to your friends!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main ref={mainRef} className="relative">
|
||||
{Object.values(cursors).map((cursor: any) => (
|
||||
<div
|
||||
key={cursor.id}
|
||||
className="absolute z-50"
|
||||
style={{
|
||||
left: `${cursor.x}px`,
|
||||
top: `${cursor.y}px`,
|
||||
transition: 'left 0.1s linear, top 0.1s linear'
|
||||
}}
|
||||
>
|
||||
<CursorIcon className="w-10 h-10" style={{ color: cursor.color }} />
|
||||
</div>
|
||||
))}
|
||||
{isSynchronized ? (
|
||||
<Sequencer {...drumMachine} />
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-96 bg-slate-100 rounded-lg">
|
||||
<p className="text-slate-500">Synchronizing session state...</p>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
<footer className="text-center mt-8 text-sm text-slate-400">
|
||||
<p>Built with React, TypeScript, and Tailwind CSS. Powered by the Web Audio API and WebSockets.</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user