diff --git a/public/worklets/fm-processor.js b/public/worklets/fm-processor.js index 70afd974..0b17450f 100644 --- a/public/worklets/fm-processor.js +++ b/public/worklets/fm-processor.js @@ -30,6 +30,11 @@ class FMProcessor extends AudioWorkletProcessor { this.lfoRate4 = 0.43 this.lfoDepth = 0.3 + this.pitchLFOPhase = 0 + this.pitchLFOWaveform = 0 + this.pitchLFODepth = 0.1 + this.pitchLFOBaseRate = 2.0 + this.port.onmessage = (event) => { const { type, value } = event.data switch (type) { @@ -42,6 +47,11 @@ class FMProcessor extends AudioWorkletProcessor { this.lfoRate3 = value.lfoRates[2] this.lfoRate4 = value.lfoRates[3] } + if (value.pitchLFO) { + this.pitchLFOWaveform = value.pitchLFO.waveform + this.pitchLFODepth = value.pitchLFO.depth + this.pitchLFOBaseRate = value.pitchLFO.baseRate + } break case 'operatorLevels': this.opLevel1 = value.a @@ -73,14 +83,41 @@ class FMProcessor extends AudioWorkletProcessor { } } + generatePitchLFO(phase, waveform) { + const TWO_PI = Math.PI * 2 + const normalizedPhase = phase / TWO_PI + + switch (waveform) { + case 0: // sine + return Math.sin(phase) + case 1: // triangle + return 2 * Math.abs(2 * (normalizedPhase % 1 - 0.5)) - 1 + case 2: // square + return normalizedPhase % 1 < 0.5 ? 1 : -1 + case 3: // sawtooth + return 2 * (normalizedPhase % 1) - 1 + default: + return 0 + } + } + synthesize(algorithm) { const TWO_PI = Math.PI * 2 const sampleRate = 44100 - const freq1 = (this.baseFreq * this.frequencyRatios[0] * TWO_PI) / sampleRate - const freq2 = (this.baseFreq * this.frequencyRatios[1] * TWO_PI) / sampleRate - const freq3 = (this.baseFreq * this.frequencyRatios[2] * TWO_PI) / sampleRate - const freq4 = (this.baseFreq * this.frequencyRatios[3] * TWO_PI) / sampleRate + const avgDiff = (Math.abs(this.opLevel1 - this.opLevel3) + Math.abs(this.opLevel2 - this.opLevel4)) / (2 * 255) + const pitchLFORate = this.pitchLFOBaseRate * (0.3 + avgDiff * 1.4) + const pitchLFOValue = this.generatePitchLFO(this.pitchLFOPhase, this.pitchLFOWaveform) + const pitchMod = 1 + pitchLFOValue * this.pitchLFODepth + const modulatedBaseFreq = this.baseFreq * pitchMod + + this.pitchLFOPhase += (pitchLFORate * TWO_PI) / sampleRate + if (this.pitchLFOPhase > TWO_PI) this.pitchLFOPhase -= TWO_PI + + const freq1 = (modulatedBaseFreq * this.frequencyRatios[0] * TWO_PI) / sampleRate + const freq2 = (modulatedBaseFreq * this.frequencyRatios[1] * TWO_PI) / sampleRate + const freq3 = (modulatedBaseFreq * this.frequencyRatios[2] * TWO_PI) / sampleRate + const freq4 = (modulatedBaseFreq * this.frequencyRatios[3] * TWO_PI) / sampleRate const lfo1 = Math.sin(this.lfoPhase1) const lfo2 = Math.sin(this.lfoPhase2) diff --git a/src/App.tsx b/src/App.tsx index d1131ade..53a48d63 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,200 +1,88 @@ -import { useState, useRef, useEffect } from 'react' +import { useState, useRef } from 'react' import { useStore } from '@nanostores/react' import { Square, Archive, Dices, Sparkles } from 'lucide-react' -import { PlaybackManager } from './services/PlaybackManager' import { DownloadService } from './services/DownloadService' -import { generateTileGrid, generateRandomFormula } from './utils/bytebeatFormulas' -import { generateFMTileGrid, generateRandomFMPatch, createFMTileState } from './utils/fmPatches' -import { BytebeatTile } from './components/BytebeatTile' -import { EffectsBar } from './components/EffectsBar' -import { EngineControls } from './components/EngineControls' -import { FormulaEditor } from './components/FormulaEditor' -import { LFOPanel } from './components/LFOPanel' -import { AudioContextWarning } from './components/AudioContextWarning' -import { HelpModal } from './components/HelpModal' -import { getSampleRateFromIndex } from './config/effects' -import { engineSettings, effectSettings, lfoSettings, type LFOConfig } from './stores/settings' -import { synthesisMode, setSynthesisMode } from './stores/synthesisMode' +import { generateRandomFormula } from './utils/bytebeatFormulas' +import { BytebeatTile } from './components/tile/BytebeatTile' +import { EffectsBar } from './components/controls/EffectsBar' +import { EngineControls } from './components/controls/EngineControls' +import { FormulaEditor } from './components/tile/FormulaEditor' +import { LFOPanel } from './components/controls/LFOPanel' +import { AudioContextWarning } from './components/modals/AudioContextWarning' +import { HelpModal } from './components/modals/HelpModal' +import { engineSettings, effectSettings } from './stores/settings' import { exitMappingMode } from './stores/mappingMode' import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts' -import { useTileParams } from './hooks/useTileParams' +import { useTileGrid } from './hooks/useTileGrid' +import { usePlaybackControl } from './hooks/usePlaybackControl' +import { useFocusNavigation } from './hooks/useFocusNavigation' +import { useParameterSync } from './hooks/useParameterSync' +import { useLFOMapping } from './hooks/useLFOMapping' import type { TileState } from './types/tiles' -import { createTileStateFromCurrent, loadTileParams, randomizeTileParams } from './utils/tileState' -import { DEFAULT_VARIABLES, PLAYBACK_ID, TILE_GRID, DEFAULT_DOWNLOAD_OPTIONS, LOOP_DURATION } from './constants/defaults' -import { getTileId, getTileFromGrid, type FocusedTile } from './utils/tileHelpers' +import { createTileStateFromCurrent } from './utils/tileState' +import { DEFAULT_DOWNLOAD_OPTIONS, PLAYBACK_ID } from './constants/defaults' +import { getTileId, getTileFromGrid } from './utils/tileHelpers' function App() { const engineValues = useStore(engineSettings) const effectValues = useStore(effectSettings) - const mode = useStore(synthesisMode) - const [tiles, setTiles] = useState(() => - mode === 'fm' - ? generateFMTileGrid(TILE_GRID.SIZE, TILE_GRID.COLUMNS, engineValues.complexity) - : generateTileGrid(TILE_GRID.SIZE, TILE_GRID.COLUMNS, engineValues.complexity) - ) - const [playing, setPlaying] = useState(null) - const [queued, setQueued] = useState(null) - const [playbackPosition, setPlaybackPosition] = useState(0) const [downloading, setDownloading] = useState(false) - const [focusedTile, setFocusedTile] = useState({ row: 0, col: 0 }) const [customTile, setCustomTile] = useState(() => createTileStateFromCurrent('t*(8&t>>9)')) const [showWarning, setShowWarning] = useState(true) const [showHelp, setShowHelp] = useState(false) const [mobileHeaderTab, setMobileHeaderTab] = useState<'global' | 'options' | 'modulate'>('global') - const playbackManagerRef = useRef(null) const downloadServiceRef = useRef(new DownloadService()) - const switchTimerRef = useRef(null) - const { saveCurrentTileParams } = useTileParams({ tiles, setTiles, customTile, setCustomTile, focusedTile }) + const { tiles, setTiles, mode, regenerateAll, regenerateTile, switchMode } = useTileGrid() - useEffect(() => { - if (playbackManagerRef.current) { - playbackManagerRef.current.setPlaybackPositionCallback(setPlaybackPosition) - } - }, []) + const { playing, queued, playbackPosition, playbackManager, play, stop, queue, cancelQueue, updateMode } = + usePlaybackControl({ mode }) - useEffect(() => { - effectSettings.setKey('masterVolume', engineValues.masterVolume) - }, [engineValues.masterVolume]) - - const clearSwitchTimer = () => { - if (switchTimerRef.current !== null) { - clearTimeout(switchTimerRef.current) - switchTimerRef.current = null - } - } - - const startSwitchTimer = (queuedId: string) => { - clearSwitchTimer() - - switchTimerRef.current = window.setTimeout(() => { - const [rowStr, colStr] = queuedId.split('-') - const row = parseInt(rowStr, 10) - const col = parseInt(colStr, 10) - const tile = getTileFromGrid(tiles, row, col) - - if (tile) { - playFormula(tile.formula, queuedId) + const { focusedTile, setFocus, moveFocus } = useFocusNavigation({ + tiles, + onFocusChange: (tile) => { + if (tile !== 'custom') { + const tileData = getTileFromGrid(tiles, tile.row, tile.col) + if (tileData) { + saveCurrentParams() + loadParams(tileData) + } + } else { + saveCurrentParams() + loadParams(customTile) } - }, engineValues.loopCount * 1000) - } + } + }) - useEffect(() => { - return () => clearSwitchTimer() - }, []) + const { saveCurrentParams, loadParams, handleEngineChange, handleEffectChange, randomizeParams, randomizeAllParams } = + useParameterSync({ + tiles, + setTiles, + customTile, + setCustomTile, + focusedTile, + playbackManager, + playing, + playbackId: PLAYBACK_ID.CUSTOM + }) + + const { handleLFOChange, handleParameterMapClick, handleUpdateMappingDepth, handleRemoveMapping, getMappedLFOs } = + useLFOMapping({ + playbackManager, + saveCurrentParams + }) const handleRandom = () => { - clearSwitchTimer() - if (mode === 'fm') { - setTiles(generateFMTileGrid(TILE_GRID.SIZE, TILE_GRID.COLUMNS, engineValues.complexity)) - } else { - setTiles(generateTileGrid(TILE_GRID.SIZE, TILE_GRID.COLUMNS, engineValues.complexity)) - } - setQueued(null) + cancelQueue() + regenerateAll() } const handleModeToggle = () => { const newMode = mode === 'bytebeat' ? 'fm' : 'bytebeat' - handleStop() - setSynthesisMode(newMode) - - if (playbackManagerRef.current) { - playbackManagerRef.current.setMode(newMode) - playbackManagerRef.current.setAlgorithm(engineValues.fmAlgorithm ?? 0) - playbackManagerRef.current.setFeedback(engineValues.fmFeedback ?? 0) - } - - if (newMode === 'fm') { - setTiles(generateFMTileGrid(TILE_GRID.SIZE, TILE_GRID.COLUMNS, engineValues.complexity)) - } else { - setTiles(generateTileGrid(TILE_GRID.SIZE, TILE_GRID.COLUMNS, engineValues.complexity)) - } - } - - const handleRandomizeAllParams = () => { - clearSwitchTimer() - let newRandomized: TileState | null = null - - if (playing === PLAYBACK_ID.CUSTOM) { - setCustomTile(prev => { - const randomized = randomizeTileParams(prev) - newRandomized = randomized - return randomized - }) - } else { - setTiles(prevTiles => { - const newTiles = prevTiles.map((row, rowIdx) => - row.map((tile, colIdx) => { - const randomized = randomizeTileParams(tile) - if (playing && focusedTile !== 'custom') { - const tileId = getTileId(focusedTile.row, focusedTile.col) - if (playing === tileId && rowIdx === focusedTile.row && colIdx === focusedTile.col) { - newRandomized = randomized - } - } - return randomized - }) - ) - return newTiles - }) - - setCustomTile(prev => randomizeTileParams(prev)) - } - - if (newRandomized && playbackManagerRef.current) { - const params = newRandomized as TileState - loadTileParams(params) - - playbackManagerRef.current.setEffects(params.effectParams) - playbackManagerRef.current.setVariables( - params.engineParams.a ?? DEFAULT_VARIABLES.a, - params.engineParams.b ?? DEFAULT_VARIABLES.b, - params.engineParams.c ?? DEFAULT_VARIABLES.c, - params.engineParams.d ?? DEFAULT_VARIABLES.d - ) - playbackManagerRef.current.setPitch(params.engineParams.pitch ?? 1.0) - - if (params.lfoConfigs) { - playbackManagerRef.current.setLFOConfig(0, params.lfoConfigs.lfo1) - playbackManagerRef.current.setLFOConfig(1, params.lfoConfigs.lfo2) - playbackManagerRef.current.setLFOConfig(2, params.lfoConfigs.lfo3) - playbackManagerRef.current.setLFOConfig(3, params.lfoConfigs.lfo4) - } - } - - setQueued(null) - } - - const playFormula = async (formula: string, id: string) => { - const sampleRate = getSampleRateFromIndex(engineValues.sampleRate) - const duration = LOOP_DURATION - - if (!playbackManagerRef.current) { - playbackManagerRef.current = new PlaybackManager({ sampleRate, duration }) - playbackManagerRef.current.setMode(mode) - } else { - await playbackManagerRef.current.updateOptions({ sampleRate, duration }) - } - - playbackManagerRef.current.stop() - playbackManagerRef.current.setEffects(effectValues) - playbackManagerRef.current.setVariables( - engineValues.a ?? DEFAULT_VARIABLES.a, - engineValues.b ?? DEFAULT_VARIABLES.b, - engineValues.c ?? DEFAULT_VARIABLES.c, - engineValues.d ?? DEFAULT_VARIABLES.d - ) - playbackManagerRef.current.setPitch(engineValues.pitch ?? 1.0) - const fmPatch = mode === 'fm' ? JSON.parse(formula) : null - const lfoRates = fmPatch?.lfoRates || undefined - playbackManagerRef.current.setAlgorithm(engineValues.fmAlgorithm ?? 0, lfoRates) - playbackManagerRef.current.setFeedback(engineValues.fmFeedback ?? 0) - - await playbackManagerRef.current.play(formula) - setPlaying(id) - setQueued(null) - return true + stop() + switchMode(newMode) + updateMode(newMode) } const handleTileClick = (_formula: string, row: number, col: number, isDoubleClick: boolean = false) => { @@ -203,21 +91,17 @@ function App() { if (!tile) return - if (focusedTile === 'custom' || (focusedTile.row !== row || focusedTile.col !== col)) { - saveCurrentTileParams() - } - - if (tile) { - loadTileParams(tile) - } - setFocusedTile({ row, col }) + setFocus({ row, col }) if (isDoubleClick || playing === null) { - clearSwitchTimer() - playFormula(tile.formula, id) + play(tile.formula, id, tile) } else { - setQueued(id) - startSwitchTimer(id) + queue(id, () => { + const queuedTile = getTileFromGrid(tiles, row, col) + if (queuedTile) { + play(queuedTile.formula, id, queuedTile) + } + }) } } @@ -225,127 +109,6 @@ function App() { handleTileClick(formula, row, col, true) } - - const handleEngineChange = async (parameterId: string, value: number) => { - engineSettings.setKey(parameterId as keyof typeof engineValues, value) - saveCurrentTileParams() - - if (parameterId === 'masterVolume' && playbackManagerRef.current) { - playbackManagerRef.current.setEffects({ ...effectValues, masterVolume: value }) - } - - if (parameterId === 'pitch' && playbackManagerRef.current && playing) { - playbackManagerRef.current.setPitch(value) - } - - if (parameterId === 'fmAlgorithm' && playbackManagerRef.current) { - playbackManagerRef.current.setAlgorithm(value) - } - - if (parameterId === 'fmFeedback' && playbackManagerRef.current) { - playbackManagerRef.current.setFeedback(value) - } - - if (['a', 'b', 'c', 'd'].includes(parameterId) && playbackManagerRef.current && playing) { - const updatedValues = { ...engineValues, [parameterId]: value } - playbackManagerRef.current.setVariables( - updatedValues.a ?? DEFAULT_VARIABLES.a, - updatedValues.b ?? DEFAULT_VARIABLES.b, - updatedValues.c ?? DEFAULT_VARIABLES.c, - updatedValues.d ?? DEFAULT_VARIABLES.d - ) - } - } - - const handleEffectChange = (parameterId: string, value: number | boolean | string) => { - effectSettings.setKey(parameterId as keyof typeof effectValues, value as never) - saveCurrentTileParams() - - if (playbackManagerRef.current) { - playbackManagerRef.current.setEffects(effectValues) - } - } - - const handleLFOChange = (lfoIndex: number, config: LFOConfig) => { - if (playbackManagerRef.current) { - playbackManagerRef.current.setLFOConfig(lfoIndex, config) - } - } - - const handleParameterMapClick = (paramId: string, lfoIndex: number) => { - const lfoKey = `lfo${lfoIndex + 1}` as 'lfo1' | 'lfo2' | 'lfo3' | 'lfo4' - const currentLFO = lfoSettings.get()[lfoKey] - - const existingMappingIndex = currentLFO.mappings.findIndex(m => m.targetParam === paramId) - - let updatedMappings - if (existingMappingIndex >= 0) { - updatedMappings = currentLFO.mappings.filter((_, i) => i !== existingMappingIndex) - } else { - updatedMappings = [...currentLFO.mappings, { targetParam: paramId, depth: 50 }] - } - - const updatedLFO = { ...currentLFO, mappings: updatedMappings } - lfoSettings.setKey(lfoKey, updatedLFO) - - if (playbackManagerRef.current) { - playbackManagerRef.current.setLFOConfig(lfoIndex, updatedLFO) - } - - saveCurrentTileParams() - - if (updatedMappings.length === 0 || existingMappingIndex >= 0) { - exitMappingMode() - } - } - - const handleUpdateMappingDepth = (lfoIndex: number, paramId: string, depth: number) => { - const lfoKey = `lfo${lfoIndex + 1}` as 'lfo1' | 'lfo2' | 'lfo3' | 'lfo4' - const currentLFO = lfoSettings.get()[lfoKey] - - const updatedMappings = currentLFO.mappings.map(m => - m.targetParam === paramId ? { ...m, depth } : m - ) - - const updatedLFO = { ...currentLFO, mappings: updatedMappings } - lfoSettings.setKey(lfoKey, updatedLFO) - - if (playbackManagerRef.current) { - playbackManagerRef.current.setLFOConfig(lfoIndex, updatedLFO) - } - - saveCurrentTileParams() - } - - const handleRemoveMapping = (lfoIndex: number, paramId: string) => { - const lfoKey = `lfo${lfoIndex + 1}` as 'lfo1' | 'lfo2' | 'lfo3' | 'lfo4' - const currentLFO = lfoSettings.get()[lfoKey] - - const updatedMappings = currentLFO.mappings.filter(m => m.targetParam !== paramId) - - const updatedLFO = { ...currentLFO, mappings: updatedMappings } - lfoSettings.setKey(lfoKey, updatedLFO) - - if (playbackManagerRef.current) { - playbackManagerRef.current.setLFOConfig(lfoIndex, updatedLFO) - } - - saveCurrentTileParams() - } - - const getMappedLFOs = (paramId: string): number[] => { - const lfos = lfoSettings.get() - const mapped: number[] = [] - - Object.entries(lfos).forEach(([, lfo], index) => { - if (lfo.mappings.some((m: { targetParam: string }) => m.targetParam === paramId)) { - mapped.push(index) - } - }) - - return mapped - } - const handleDownloadAll = async () => { setDownloading(true) const formulas = tiles.map(row => row.map(tile => tile.formula)) @@ -363,47 +126,15 @@ function App() { }) } - const handleRegenerate = (row: number, col: number) => { - let newTile: TileState - - if (mode === 'fm') { - const patch = generateRandomFMPatch(engineValues.complexity) - newTile = createFMTileState(patch) - } else { - const newFormula = generateRandomFormula(engineValues.complexity) - newTile = createTileStateFromCurrent(newFormula) - } - - setTiles(prevTiles => { - const newTiles = [...prevTiles] - newTiles[row] = [...newTiles[row]] - newTiles[row][col] = newTile - return newTiles - }) - } - - const handleStop = () => { - clearSwitchTimer() - playbackManagerRef.current?.stop() - setPlaying(null) - setQueued(null) - setPlaybackPosition(0) - } - const handleCustomEvaluate = (formula: string) => { - if (focusedTile !== 'custom') { - saveCurrentTileParams() - loadTileParams(customTile) - } - - setFocusedTile('custom') + setFocus('custom') setCustomTile({ ...customTile, formula }) - playFormula(formula, PLAYBACK_ID.CUSTOM) + play(formula, PLAYBACK_ID.CUSTOM, { ...customTile, formula }) } const handleCustomStop = () => { if (playing === PLAYBACK_ID.CUSTOM) { - handleStop() + stop() } } @@ -411,44 +142,9 @@ function App() { return generateRandomFormula(engineValues.complexity) } - const moveFocus = (direction: 'up' | 'down' | 'left' | 'right', step: number = 1) => { - saveCurrentTileParams() - - setFocusedTile(prev => { - if (prev === 'custom') return prev - - let { row, col } = prev - const maxRow = tiles.length - 1 - const maxCol = (tiles[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 - } - - const newTile = tiles[row]?.[col] - if (newTile) { - loadTileParams(newTile) - return { row, col } - } - - return prev - }) - } - const handleKeyboardSpace = () => { if (playing) { - handleStop() + stop() } else if (focusedTile !== 'custom') { const tile = tiles[focusedTile.row]?.[focusedTile.col] if (tile) { @@ -477,79 +173,15 @@ function App() { const handleKeyboardR = () => { if (focusedTile !== 'custom') { - handleRegenerate(focusedTile.row, focusedTile.col) + regenerateTile(focusedTile.row, focusedTile.col) } } - const handleKeyboardShiftR = () => { - handleRandom() - } - - const handleEscape = () => { - exitMappingMode() - } - const handleKeyboardC = () => { - if (focusedTile === 'custom') { - setCustomTile(prev => { - const randomized = randomizeTileParams(prev) - loadTileParams(randomized) - - if (playing === PLAYBACK_ID.CUSTOM && playbackManagerRef.current) { - playbackManagerRef.current.setEffects(randomized.effectParams) - playbackManagerRef.current.setVariables( - randomized.engineParams.a ?? DEFAULT_VARIABLES.a, - randomized.engineParams.b ?? DEFAULT_VARIABLES.b, - randomized.engineParams.c ?? DEFAULT_VARIABLES.c, - randomized.engineParams.d ?? DEFAULT_VARIABLES.d - ) - playbackManagerRef.current.setPitch(randomized.engineParams.pitch ?? 1.0) - - if (randomized.lfoConfigs) { - playbackManagerRef.current.setLFOConfig(0, randomized.lfoConfigs.lfo1) - playbackManagerRef.current.setLFOConfig(1, randomized.lfoConfigs.lfo2) - playbackManagerRef.current.setLFOConfig(2, randomized.lfoConfigs.lfo3) - playbackManagerRef.current.setLFOConfig(3, randomized.lfoConfigs.lfo4) - } - } - - return randomized - }) - } else { - const tileId = getTileId(focusedTile.row, focusedTile.col) - setTiles(prevTiles => { - const newTiles = [...prevTiles] - newTiles[focusedTile.row] = [...newTiles[focusedTile.row]] - const randomized = randomizeTileParams(newTiles[focusedTile.row][focusedTile.col]) - newTiles[focusedTile.row][focusedTile.col] = randomized - - loadTileParams(randomized) - - if (playing === tileId && playbackManagerRef.current) { - playbackManagerRef.current.setEffects(randomized.effectParams) - playbackManagerRef.current.setVariables( - randomized.engineParams.a ?? DEFAULT_VARIABLES.a, - randomized.engineParams.b ?? DEFAULT_VARIABLES.b, - randomized.engineParams.c ?? DEFAULT_VARIABLES.c, - randomized.engineParams.d ?? DEFAULT_VARIABLES.d - ) - playbackManagerRef.current.setPitch(randomized.engineParams.pitch ?? 1.0) - - if (randomized.lfoConfigs) { - playbackManagerRef.current.setLFOConfig(0, randomized.lfoConfigs.lfo1) - playbackManagerRef.current.setLFOConfig(1, randomized.lfoConfigs.lfo2) - playbackManagerRef.current.setLFOConfig(2, randomized.lfoConfigs.lfo3) - playbackManagerRef.current.setLFOConfig(3, randomized.lfoConfigs.lfo4) - } - } - - return newTiles - }) - } - } - - const handleKeyboardShiftC = () => { - handleRandomizeAllParams() + const tileId = focusedTile === 'custom' + ? PLAYBACK_ID.CUSTOM + : getTileId(focusedTile.row, focusedTile.col) + randomizeParams(tileId) } const handleDismissWarning = () => { @@ -565,21 +197,12 @@ function App() { onEnter: handleKeyboardEnter, onDoubleEnter: handleKeyboardDoubleEnter, onR: handleKeyboardR, - onShiftR: handleKeyboardShiftR, + onShiftR: handleRandom, onC: handleKeyboardC, - onShiftC: handleKeyboardShiftC, - onEscape: handleEscape + onShiftC: randomizeAllParams, + onEscape: exitMappingMode }) - useEffect(() => { - if (focusedTile !== 'custom') { - const element = document.querySelector(`[data-tile-id="${focusedTile.row}-${focusedTile.col}"]`) - if (element) { - element.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) - } - } - }, [focusedTile]) - return (
{showWarning && } @@ -655,7 +278,7 @@ function App() { {mobileHeaderTab === 'global' && (