diff options
Diffstat (limited to 'js/scripting-lang/web/src/app.js')
-rw-r--r-- | js/scripting-lang/web/src/app.js | 286 |
1 files changed, 0 insertions, 286 deletions
diff --git a/js/scripting-lang/web/src/app.js b/js/scripting-lang/web/src/app.js deleted file mode 100644 index 086cba1..0000000 --- a/js/scripting-lang/web/src/app.js +++ /dev/null @@ -1,286 +0,0 @@ -// app.js -// Entrypoint for the app - -import { initialState, cloneState } from './state.js'; -import { update } from './update.js'; -import { view } from './view.js'; -import { fetchPokemon, fetchEvolutionData, executeBabaYagaScript, getExampleScripts, getCurrentHarness } from './api.js'; -import { initDevMode } from './dev.js'; - -const root = document.getElementById('app'); -let state = cloneState(initialState); -let dev; - -/** - * Entrypoint for the app. - * - * This file implements a minimal Elm-style architecture using only browser APIs and ES modules. - * - All state is immutable and updated by a pure update function. - * - The entire UI is re-rendered as a string on each state change for simplicity and predictability. - * - Event delegation is used to keep wiring minimal and functional. - * - No 3rd party code: everything is browser-native for cozy portability and clarity. - * - * Why this approach? - * - Functional, pure update/view logic is easier for me to reason about and test. - * - Re-rendering the whole UI avoids bugs from manual DOM updates and keeps state/UI in sync. - * - Minimal code and clear data flow make it easy to extend or adapt for new projects. - */ - -// Enable devMode if ?dev=1 is in the URL -/** - * devMode enables logging of all actions and state transitions for debugging. - * - * Why? This makes the app's state flow transparent, helping you understand and debug the app without extra tooling. - */ -const devMode = window.location.search.includes('dev=1'); - -/** - * Generalized render function for Elm-style apps. - * - * @param {Object} config - Render configuration - * @param {HTMLElement} config.root - Root DOM element - * @param {any} config.state - Current app state - * @param {Function} config.view - View function (state => HTML string) - * @param {Array} [config.events] - Array of { selector, event, handler } - * @param {Function} [config.postRender] - Optional function({ root, state }) for post-render logic - */ -function render({ root, state, view, events = [], postRender }) { - root.innerHTML = view(state); - events.forEach(({ selector, event, handler }) => { - const el = root.querySelector(selector); - if (el) el.addEventListener(event, handler); - }); - if (typeof postRender === 'function') { - postRender({ root, state }); - } -} - -// --- App-specific config for render --- -function postRender({ root, state }) { - // Preserve scroll position - const scrollPosition = window.scrollY; - - const input = root.querySelector('#pokemon-query'); - const error = root.querySelector('.error'); - - // Only handle error focus - don't interfere with user typing - if (error) { - error.focus(); - } else if (input && !document.activeElement) { - // Only auto-focus search input if nothing is currently focused - input.focus(); - input.value = state.query; - input.setSelectionRange(input.value.length, input.value.length); - } - - // Restore scroll position - window.scrollTo(0, scrollPosition); -} - -function doRender() { - render({ - root, - state, - view, - events: [ - { selector: '#search-form', event: 'submit', handler: handleSubmit }, - { selector: '#pokemon-query', event: 'input', handler: handleInput }, - { selector: '#execute-script', event: 'click', handler: handleExecuteScript }, - { selector: '#clear-script', event: 'click', handler: handleClearScript }, - { selector: '#example-scripts', event: 'change', handler: handleLoadExample }, - { selector: '#baba-yaga-script', event: 'input', handler: handleScriptInput }, - ], - postRender, - }); -} - -/** - * Dispatches an action to update state and re-render. - * - * Why centralize dispatch? This enforces a single source of truth for state changes, making the app predictable and easy to debug. - * - * Why log actions/state in devMode? This provides a transparent, time-travel-like view of app logic without needing any extra tooling. - */ -function dispatch(action) { - const prevState = state; - state = update(state, action); - - if (devMode && dev && typeof dev.pushState === 'function') { - dev.pushState(state); - console.groupCollapsed(`Action: ${action.type}`); - console.log('Payload:', action.payload); - console.log('Prev state:', prevState); - console.log('Next state:', state); - console.groupEnd(); - } - - // Only re-render for actions that actually change the UI - const shouldRender = [ - 'FETCH_SUCCESS', - 'FETCH_ERROR', - 'FETCH_EVOLUTION_SUCCESS', - 'FETCH_EVOLUTION_ERROR', - 'EXECUTE_SCRIPT_SUCCESS', - 'EXECUTE_SCRIPT_ERROR', - 'CLEAR_SCRIPT_OUTPUT', - 'UPDATE_BABA_YAGA_SCRIPT' // Only when loading examples - ].includes(action.type); - - if (shouldRender) { - doRender(); - } -} - -/** - * Handles input events by updating state without re-rendering. - */ -function handleInput(e) { - // Update state directly without triggering re-render - state.query = e.target.value; -} - -/** - * Handles script input events by updating state without re-rendering. - */ -function handleScriptInput(e) { - // Update state directly without triggering re-render - state.babaYagaScript = e.target.value; -} - -/** - * Handles form submission, triggers async fetch, and dispatches state updates. - * - * Why handle async here? Keeps update/view pure and centralizes side-effect. - */ -async function handleSubmit(e) { - e.preventDefault(); - if (!state.query.trim()) return; - dispatch({ type: 'FETCH_START' }); - try { - const data = await fetchPokemon(state.query.trim()); - dispatch({ type: 'FETCH_SUCCESS', payload: data }); - - // Automatically fetch evolution chain after successful Pokémon search - try { - dispatch({ type: 'FETCH_EVOLUTION_START' }); - const evolutionData = await fetchEvolutionData(data.name); - dispatch({ type: 'FETCH_EVOLUTION_SUCCESS', payload: evolutionData }); - } catch (evolutionErr) { - dispatch({ type: 'FETCH_EVOLUTION_ERROR', payload: evolutionErr.message }); - } - } catch (err) { - dispatch({ type: 'FETCH_ERROR', payload: err.message }); - } -} - - - -/** - * Handles Baba Yaga script execution. - */ -async function handleExecuteScript(e) { - e.preventDefault(); - if (!state.evolutionChain || !state.babaYagaScript.trim()) return; - - dispatch({ type: 'EXECUTE_SCRIPT_START' }); - try { - // state.evolutionChain contains the wrapper object, pass it directly - const result = await executeBabaYagaScript(state.babaYagaScript, state.evolutionChain); - dispatch({ type: 'EXECUTE_SCRIPT_SUCCESS', payload: result }); - - // Update dev mode with the harness instance for enhanced debugging - console.log('[App] Checking dev mode integration:', { - devMode, - dev: !!dev, - updateHarness: dev ? typeof dev.updateHarness : 'no dev', - updateHarnessValue: dev ? dev.updateHarness : 'no dev' - }); - - if (devMode && dev && typeof dev.updateHarness === 'function') { - const currentHarness = getCurrentHarness(); - console.log('[App] Script executed, current harness:', !!currentHarness); - try { - dev.updateHarness(currentHarness); - console.log('[App] updateHarness called successfully'); - } catch (error) { - console.error('[App] Error calling updateHarness:', error); - } - } else { - console.log('[App] Dev mode or updateHarness not available:', { - devMode, - dev: !!dev, - updateHarness: dev ? typeof dev.updateHarness : false - }); - - // Try to access the function directly from window.dev - if (window.dev && typeof window.dev.updateHarness === 'function') { - console.log('[App] Found updateHarness on window.dev, trying direct call'); - const currentHarness = getCurrentHarness(); - try { - window.dev.updateHarness(currentHarness); - console.log('[App] Direct updateHarness call successful'); - } catch (error) { - console.error('[App] Error in direct updateHarness call:', error); - } - } - } - } catch (err) { - dispatch({ type: 'EXECUTE_SCRIPT_ERROR', payload: err.message }); - } -} - -/** - * Handles script clearing. - */ -function handleClearScript(e) { - e.preventDefault(); - dispatch({ type: 'UPDATE_BABA_YAGA_SCRIPT', payload: '' }); - dispatch({ type: 'CLEAR_SCRIPT_OUTPUT' }); -} - -/** - * Handles loading example scripts. - */ -function handleLoadExample(e) { - const selectedExample = e.target.value; - if (!selectedExample) return; - - const examples = getExampleScripts(); - if (examples[selectedExample]) { - // Update state and trigger re-render to show the example - state.babaYagaScript = examples[selectedExample]; - doRender(); - } - - // Reset the select - e.target.value = ''; -} - -// Initialize dev mode before first render -if (devMode) { - dev = initDevMode({ - getState: () => state, - setState: s => { state = s; }, - render: doRender, - harness: null // Will be updated when harness is created - }); -} - -// Initial render -doRender(); - -function updateHistoryInfo() { - if (!devMode || !dev) return; - dev.update(); -} - -function setHistoryPointer(idx) { - const info = dev.getHistoryInfo(); - if (idx < 1 || idx > info.length) return; - const newState = dev.setPointer(idx - 1); - if (newState) { - state = newState; - doRender(); - updateHistoryInfo(); - } -} |