UI improvements

This commit is contained in:
2025-10-04 00:10:23 +02:00
parent 2d0bfe2297
commit a960f4e18b
3 changed files with 181 additions and 11 deletions

View File

@ -1,6 +1,6 @@
import { useState, useRef, useEffect } from 'react'
import { useStore } from '@nanostores/react'
import { Square } from 'lucide-react'
import { Square, Archive, Dices } from 'lucide-react'
import { PlaybackManager } from './services/PlaybackManager'
import { DownloadService } from './services/DownloadService'
import { generateFormulaGrid, generateRandomFormula } from './utils/bytebeatFormulas'
@ -9,6 +9,7 @@ import { EffectsBar } from './components/EffectsBar'
import { EngineControls } from './components/EngineControls'
import { getSampleRateFromIndex } from './config/effects'
import { engineSettings, effectSettings } from './stores/settings'
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
function App() {
const engineValues = useStore(engineSettings)
@ -22,6 +23,7 @@ function App() {
const [regenerating, setRegenerating] = useState<string | null>(null)
const [playbackPosition, setPlaybackPosition] = useState<number>(0)
const [downloading, setDownloading] = useState(false)
const [focusedTile, setFocusedTile] = useState<{ row: number; col: number }>({ row: 0, col: 0 })
const playbackManagerRef = useRef<PlaybackManager | null>(null)
const downloadServiceRef = useRef<DownloadService>(new DownloadService())
const animationFrameRef = useRef<number | null>(null)
@ -82,6 +84,7 @@ function App() {
const handleTileClick = (formula: string, row: number, col: number, isDoubleClick: boolean = false) => {
const id = `${row}-${col}`
setFocusedTile({ row, col })
if (playing === id) {
playbackManagerRef.current?.stop()
@ -179,12 +182,89 @@ function App() {
}
}
const moveFocus = (direction: 'up' | 'down' | 'left' | 'right', step: number = 1) => {
setFocusedTile(prev => {
let { row, col } = prev
const maxRow = formulas.length - 1
const maxCol = (formulas[row]?.length || 1) - 1
switch (direction) {
case 'up':
row = Math.max(0, row - step)
break
case 'down':
row = Math.min(maxRow, row + step)
break
case 'left':
col = Math.max(0, col - step)
break
case 'right':
col = Math.min(maxCol, col + step)
break
}
return { row, col }
})
}
const handleKeyboardSpace = () => {
if (playing) {
handleStop()
} else {
const formula = formulas[focusedTile.row]?.[focusedTile.col]
if (formula) {
handleTileClick(formula, focusedTile.row, focusedTile.col, true)
}
}
}
const handleKeyboardEnter = () => {
const formula = formulas[focusedTile.row]?.[focusedTile.col]
if (formula) {
handleTileClick(formula, focusedTile.row, focusedTile.col, false)
}
}
const handleKeyboardDoubleEnter = () => {
const formula = formulas[focusedTile.row]?.[focusedTile.col]
if (formula) {
handleTileClick(formula, focusedTile.row, focusedTile.col, true)
}
}
const handleKeyboardR = () => {
handleRegenerate(focusedTile.row, focusedTile.col)
}
const handleKeyboardShiftR = () => {
handleRandom()
}
useKeyboardShortcuts({
onSpace: handleKeyboardSpace,
onArrowUp: (shift) => moveFocus('up', shift ? 10 : 1),
onArrowDown: (shift) => moveFocus('down', shift ? 10 : 1),
onArrowLeft: (shift) => moveFocus('left', shift ? 10 : 1),
onArrowRight: (shift) => moveFocus('right', shift ? 10 : 1),
onEnter: handleKeyboardEnter,
onDoubleEnter: handleKeyboardDoubleEnter,
onR: handleKeyboardR,
onShiftR: handleKeyboardShiftR
})
useEffect(() => {
const element = document.querySelector(`[data-tile-id="${focusedTile.row}-${focusedTile.col}"]`)
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
}
}, [focusedTile])
return (
<div className="w-screen h-screen flex flex-col bg-black overflow-hidden">
<header className="bg-black border-b-2 border-white px-6 py-3">
<div className="flex items-center justify-between mb-3">
<h1 className="font-mono text-sm tracking-[0.3em] text-white">BRUITISTE</h1>
<div className="flex gap-4">
<div className="flex items-center justify-between gap-6">
<h1 className="font-mono text-sm tracking-[0.3em] text-white flex-shrink-0">BRUITISTE</h1>
<EngineControls values={engineValues} onChange={handleEngineChange} />
<div className="flex gap-4 flex-shrink-0">
<button
onClick={handleStop}
disabled={!playing}
@ -195,20 +275,21 @@ function App() {
</button>
<button
onClick={handleRandom}
className="px-6 py-2 bg-white text-black font-mono text-[11px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
className="px-6 py-2 bg-white text-black font-mono text-[11px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all flex items-center gap-2"
>
<Dices size={14} strokeWidth={2} />
RANDOM
</button>
<button
onClick={handleDownloadAll}
disabled={downloading}
className="px-6 py-2 bg-black text-white border-2 border-white font-mono text-[11px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed"
className="px-6 py-2 bg-black text-white border-2 border-white font-mono text-[11px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed flex items-center gap-2"
>
<Archive size={14} strokeWidth={2} />
{downloading ? 'DOWNLOADING...' : 'DOWNLOAD ALL'}
</button>
</div>
</div>
<EngineControls values={engineValues} onChange={handleEngineChange} />
</header>
<div className="flex-1 grid grid-cols-2 auto-rows-min gap-[1px] bg-white p-[1px] overflow-auto">
@ -224,6 +305,7 @@ function App() {
isPlaying={playing === id}
isQueued={queued === id}
isRegenerating={regenerating === id}
isFocused={focusedTile.row === i && focusedTile.col === j}
playbackPosition={playing === id ? playbackPosition : 0}
onPlay={handleTileClick}
onDoubleClick={handleTileDoubleClick}