CoolSoup is a React + TypeScript + Vite application that generates visual patterns and converts them to audio through spectral synthesis. Features multiple image generators (Tixy expressions, geometric tiles, external APIs) and an advanced audio synthesis engine that treats images as spectrograms.
134 lines
5.7 KiB
TypeScript
134 lines
5.7 KiB
TypeScript
import { useStore } from '@nanostores/react'
|
|
import { appSettings, helpPopupOpen, generatedImages, type GeneratorType } from '../stores'
|
|
import { useEffect } from 'react'
|
|
import Tooltip from './Tooltip'
|
|
|
|
interface GeneratorSelectorProps {
|
|
onGenerate: () => void
|
|
isGenerating: boolean
|
|
}
|
|
|
|
export default function GeneratorSelector({ onGenerate, isGenerating }: GeneratorSelectorProps) {
|
|
console.log('GeneratorSelector rendering with props:', { onGenerate, isGenerating })
|
|
|
|
const settings = useStore(appSettings)
|
|
console.log('GeneratorSelector settings:', settings)
|
|
|
|
const handleGeneratorChange = (generator: GeneratorType) => {
|
|
console.log('Changing generator to:', generator)
|
|
appSettings.set({ ...settings, selectedGenerator: generator })
|
|
|
|
// Clear the grid when switching to photo or webcam modes to show drag/drop zones
|
|
if (generator === 'from-photo' || generator === 'webcam') {
|
|
generatedImages.set([])
|
|
}
|
|
}
|
|
|
|
const handleGenerateClick = () => {
|
|
console.log('Generate button clicked in GeneratorSelector!')
|
|
console.log('onGenerate function:', onGenerate)
|
|
onGenerate()
|
|
}
|
|
|
|
const generators = [
|
|
{ id: 'tixy' as const, name: 'Tixy', description: 'Math expressions' },
|
|
{ id: 'waveform' as const, name: 'Waveform', description: 'Random waveforms' },
|
|
{ id: 'partials' as const, name: 'Partials', description: 'Horizontal lines' },
|
|
{ id: 'slides' as const, name: 'Slides', description: 'Vertical strokes' },
|
|
{ id: 'shapes' as const, name: 'Shapes', description: 'Geometric shapes' },
|
|
{ id: 'bands' as const, name: 'Bands', description: 'Horizontal band layers' },
|
|
{ id: 'dust' as const, name: 'Dust', description: 'Point clouds' },
|
|
{ id: 'harmonics' as const, name: 'Harmonics', description: 'Musical harmonic series' },
|
|
{ id: 'geopattern' as const, name: 'Geopattern', description: 'Geometric patterns' },
|
|
{ id: 'from-photo' as const, name: 'Photo', description: 'Upload your image' },
|
|
{ id: 'webcam' as const, name: 'Webcam', description: 'Capture from camera' },
|
|
{ id: 'art-institute' as const, name: 'Artworks', description: 'Famous artworks' },
|
|
{ id: 'picsum' as const, name: 'Picsum', description: 'Random photos' }
|
|
]
|
|
|
|
// Automatically split generators into two rows
|
|
const generatorsPerRow = Math.ceil(generators.length / 2)
|
|
const firstRowGenerators = generators.slice(0, generatorsPerRow)
|
|
const secondRowGenerators = generators.slice(generatorsPerRow)
|
|
|
|
useEffect(() => {
|
|
const handleKeyPress = (event: KeyboardEvent) => {
|
|
if (event.key.toLowerCase() === 'g' && !isGenerating) {
|
|
// Don't trigger if user is typing in an input field
|
|
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
|
|
return
|
|
}
|
|
event.preventDefault()
|
|
handleGenerateClick()
|
|
}
|
|
}
|
|
|
|
window.addEventListener('keydown', handleKeyPress)
|
|
return () => window.removeEventListener('keydown', handleKeyPress)
|
|
}, [isGenerating, onGenerate])
|
|
|
|
return (
|
|
<div className="border-b border-gray-800 p-6">
|
|
<div className="flex space-x-6">
|
|
{/* First container: 15% width - Title and Generate button */}
|
|
<div className="w-[15%] flex flex-col space-y-2">
|
|
<div className="flex items-center justify-center h-10">
|
|
<h1
|
|
className="text-2xl font-bold text-white cursor-pointer hover:text-gray-300"
|
|
onClick={() => helpPopupOpen.set(true)}
|
|
>
|
|
CoolSoup
|
|
</h1>
|
|
</div>
|
|
<div className="flex items-center justify-center h-10">
|
|
{settings.selectedGenerator !== 'from-photo' && settings.selectedGenerator !== 'webcam' && (
|
|
<button
|
|
onClick={handleGenerateClick}
|
|
disabled={isGenerating}
|
|
className="bg-white text-black px-4 py-2 font-medium hover:bg-gray-200 disabled:bg-gray-600 disabled:text-gray-400 disabled:cursor-not-allowed"
|
|
>
|
|
<div>{isGenerating ? 'Generating...' : 'Generate (G)'}</div>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Second container: 85% width - Generator modes in two lines */}
|
|
<div className="w-[85%] flex flex-col space-y-2">
|
|
<div className="flex items-center space-x-4 h-10">
|
|
{firstRowGenerators.map((generator) => (
|
|
<Tooltip key={generator.id} content={generator.description}>
|
|
<button
|
|
onClick={() => handleGeneratorChange(generator.id)}
|
|
className={`px-4 py-2 border ${
|
|
settings.selectedGenerator === generator.id
|
|
? 'border-white bg-white text-black'
|
|
: 'border-gray-600 text-gray-300 hover:border-gray-400'
|
|
}`}
|
|
>
|
|
<div className="font-medium">{generator.name}</div>
|
|
</button>
|
|
</Tooltip>
|
|
))}
|
|
</div>
|
|
<div className="flex items-center space-x-4 h-10">
|
|
{secondRowGenerators.map((generator) => (
|
|
<Tooltip key={generator.id} content={generator.description}>
|
|
<button
|
|
onClick={() => handleGeneratorChange(generator.id)}
|
|
className={`px-4 py-2 border ${
|
|
settings.selectedGenerator === generator.id
|
|
? 'border-white bg-white text-black'
|
|
: 'border-gray-600 text-gray-300 hover:border-gray-400'
|
|
}`}
|
|
>
|
|
<div className="font-medium">{generator.name}</div>
|
|
</button>
|
|
</Tooltip>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
} |