192 lines
6.1 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|