Files
bitfielder/src/components/MobileMenu.tsx
2025-07-07 15:29:41 +00:00

192 lines
6.1 KiB
TypeScript

import { useStore } from '@nanostores/react';
import { uiState, closeMobileMenu, showHelp } from '../stores/ui';
import { $appSettings, updateAppSettings } from '../stores/appSettings';
import { VALUE_MODES, ValueMode } from '../utils/constants';
import { $input } from '../stores/input';
import { LucideIcon } from '../hooks/useLucideIcon';
import { useAudio } from '../hooks/useAudio';
function getValueModeLabel(mode: string): string {
const labels: Record<string, string> = {
integer: 'Integer (0-255)',
float: 'Float (0.0-1.0)',
polar: 'Polar (angle-based)',
distance: 'Distance (radial)',
wave: 'Wave (ripple)',
fractal: 'Fractal (recursive)',
cellular: 'Cellular (automata)',
noise: 'Noise (perlin-like)',
warp: 'Warp (space deformation)',
flow: 'Flow (fluid dynamics)',
};
return labels[mode] || mode;
}
export function MobileMenu() {
const ui = useStore(uiState);
const settings = useStore($appSettings);
const input = useStore($input);
const { setupAudio, disableAudio } = useAudio();
const handleFullscreen = () => {
closeMobileMenu();
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
};
const handleShare = () => {
closeMobileMenu();
// Implement share functionality
};
const handleExportPNG = () => {
closeMobileMenu();
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
if (canvas) {
const link = document.createElement('a');
link.download = `bitfielder-${Date.now()}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
}
};
const handleAudioToggle = async () => {
closeMobileMenu();
if (input.audioEnabled) {
disableAudio();
} else {
await setupAudio();
}
};
const handleHelp = () => {
closeMobileMenu();
showHelp();
};
return (
<>
<div
id="mobile-menu-overlay"
className={ui.mobileMenuOpen ? 'open' : ''}
onClick={closeMobileMenu}
/>
<div id="mobile-menu" className={ui.mobileMenuOpen ? 'open' : ''}>
<div className="mobile-menu-section">
<div className="mobile-menu-item">
<label>Resolution</label>
<select
value={settings.resolution}
onChange={(e) =>
updateAppSettings({ resolution: parseInt(e.target.value) })
}
>
<option value="1">Full (1x)</option>
<option value="2">Half (2x)</option>
<option value="4">Quarter (4x)</option>
<option value="8">Eighth (8x)</option>
<option value="16">Sixteenth (16x)</option>
<option value="32">Thirty-second (32x)</option>
</select>
</div>
<div className="mobile-menu-item">
<label>FPS</label>
<select
value={settings.fps}
onChange={(e) =>
updateAppSettings({ fps: parseInt(e.target.value) })
}
>
<option value="15">15 FPS</option>
<option value="30">30 FPS</option>
<option value="60">60 FPS</option>
</select>
</div>
<div className="mobile-menu-item">
<label>Value Mode</label>
<select
value={settings.valueMode}
onChange={(e) => updateAppSettings({ valueMode: e.target.value as ValueMode })}
>
{VALUE_MODES.map((mode) => (
<option key={mode} value={mode}>
{getValueModeLabel(mode)}
</option>
))}
</select>
</div>
<div className="mobile-menu-item">
<label>Render Mode</label>
<select
value={settings.renderMode}
onChange={(e) =>
updateAppSettings({ renderMode: e.target.value })
}
>
<option value="classic">Classic</option>
<option value="grayscale">Grayscale</option>
<option value="red">Red Channel</option>
<option value="green">Green Channel</option>
<option value="blue">Blue Channel</option>
<option value="rainbow">Rainbow</option>
<option value="thermal">Thermal</option>
<option value="neon">Neon</option>
<option value="sunset">Sunset</option>
<option value="ocean">Ocean</option>
<option value="forest">Forest</option>
<option value="copper">Copper</option>
<option value="dithered">Dithered</option>
<option value="palette">Palette</option>
</select>
</div>
<div className="mobile-menu-item">
<label>
UI Opacity: {Math.round((settings.uiOpacity ?? 0.3) * 100)}%
</label>
<input
type="range"
min="10"
max="100"
value={Math.round((settings.uiOpacity ?? 0.3) * 100)}
onChange={(e) =>
updateAppSettings({ uiOpacity: parseInt(e.target.value) / 100 })
}
/>
</div>
</div>
<div className="mobile-menu-section">
<div className="mobile-menu-buttons">
<button onClick={handleHelp}>
<LucideIcon name="help" /> Help
</button>
<button onClick={handleFullscreen}>
<LucideIcon name="fullscreen" /> Fullscreen
</button>
<button onClick={handleAudioToggle}>
<LucideIcon
name={input.audioEnabled ? 'microphone' : 'microphone-off'}
/>
{input.audioEnabled ? 'Disable Audio' : 'Enable Audio'}
</button>
<button onClick={handleShare}>
<LucideIcon name="share" /> Share
</button>
<button onClick={handleExportPNG}>
<LucideIcon name="export" /> Export PNG
</button>
</div>
</div>
</div>
</>
);
}