diff options
-rw-r--r-- | elm/cyoa/elm-stuff/0.19.1/d.dat | bin | 1554 -> 1594 bytes | |||
-rw-r--r-- | js/seed/src/app.js | 40 | ||||
-rw-r--r-- | js/seed/src/dev.js | 74 | ||||
-rw-r--r-- | js/seed/src/view.js | 17 | ||||
-rw-r--r-- | js/seed/style.css | 4 |
5 files changed, 124 insertions, 11 deletions
diff --git a/elm/cyoa/elm-stuff/0.19.1/d.dat b/elm/cyoa/elm-stuff/0.19.1/d.dat index 7223730..7587e5b 100644 --- a/elm/cyoa/elm-stuff/0.19.1/d.dat +++ b/elm/cyoa/elm-stuff/0.19.1/d.dat Binary files differdiff --git a/js/seed/src/app.js b/js/seed/src/app.js index 5fd577c..34b4579 100644 --- a/js/seed/src/app.js +++ b/js/seed/src/app.js @@ -5,9 +5,11 @@ import { initialState, cloneState } from './state.js'; import { update } from './update.js'; import { view } from './view.js'; import { fetchPokemon } from './api.js'; +import { initDevMode } from './dev.js'; const root = document.getElementById('app'); let state = cloneState(initialState); +let dev; /** * Entrypoint for the app. @@ -90,6 +92,7 @@ function dispatch(action) { const prevState = state; state = update(state, action); if (devMode) { + dev.pushState(state); console.groupCollapsed(`Action: ${action.type}`); console.log('Payload:', action.payload); console.log('Prev state:', prevState); @@ -111,7 +114,7 @@ function handleInput(e) { /** * Handles form submission, triggers async fetch, and dispatches state updates. * - * Why handle async here? Keeps update/view pure and side-effect free, following functional programming best practices. + * Why handle async here? Keeps update/view pure and centralizes side-effect. */ async function handleSubmit(e) { e.preventDefault(); @@ -126,4 +129,37 @@ async function handleSubmit(e) { } // Initial render -doRender(); \ No newline at end of file +doRender(); + +// After devMode is set +if (devMode) { + dev = initDevMode({ + getState: () => state, + setState: s => { state = s; }, + 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(); + } +} + +function handleSliderChange(e) { + setHistoryPointer(Number(e.target.value)); +} + +function handleStepperChange(e) { + setHistoryPointer(Number(e.target.value)); +} \ No newline at end of file diff --git a/js/seed/src/dev.js b/js/seed/src/dev.js new file mode 100644 index 0000000..ee1a6e7 --- /dev/null +++ b/js/seed/src/dev.js @@ -0,0 +1,74 @@ +// devMode.js +// Minimal, single-file dev mode with scriptable console API + +/** + * Initialize dev mode: exposes a scriptable API for stepping through state history. + * @param {object} opts + * @param {function} opts.getState - returns current app state + * @param {function} opts.setState - sets app state + * @param {function} opts.render - triggers app re-render + */ +export function initDevMode({ getState, setState, render }) { + let history = []; + let pointer = -1; + let firstLoad = true; + + function pushState(state) { + if (pointer < history.length - 1) history = history.slice(0, pointer + 1); + history.push(clone(state)); + pointer = history.length - 1; + logInstructions(); + } + function goTo(idx) { + if (idx < 0 || idx >= history.length) return; + pointer = idx; + setState(clone(history[pointer])); + render(); + logInstructions(); + } + function next() { + if (pointer < history.length - 1) goTo(pointer + 1); + } + function prev() { + if (pointer > 0) goTo(pointer - 1); + } + function get() { + return history[pointer]; + } + function clone(obj) { + return JSON.parse(JSON.stringify(obj)); + } + function table(obj) { + console.table(dev.history); + } + function logInstructions() { + if (firstLoad) { + console.log('[DevMode] State history debugger'); + console.log('Usage:'); + console.log('- dev.next() // step forward'); + console.log('- dev.prev() // step backward'); + console.log('- dev.goTo(n) // jump to state n (1-based)'); + console.log('- dev.get() // get current state'); + console.log('- dev.table() // display history as a table'); + console.log('- dev.history // array of all states'); + console.log('- dev.pointer // current pointer (0-based)'); + firstLoad = false; + } + } + + // Expose API globally for console use + window.dev = { + next, + prev, + goTo, + get, + table, + get pointer() { return pointer; }, + get history() { return history.slice(); }, + }; + + // Initial state + pushState(getState()); + + return { pushState }; +} \ No newline at end of file diff --git a/js/seed/src/view.js b/js/seed/src/view.js index cdf1d08..5feef6e 100644 --- a/js/seed/src/view.js +++ b/js/seed/src/view.js @@ -22,26 +22,29 @@ */ export function view(state) { return ` - <main role="main"> + <h1>PokéDex</h1> + <container> <form id="search-form" autocomplete="off"> - <label for="pokemon-query">Pokémon Name</label> + <label for="pokemon-query">Pokémon Name (or number)</label> <input id="pokemon-query" type="text" value="${escape(state.query)}" placeholder="e.g. pikachu" aria-label="Pokémon Name" required /> <button type="submit" ${state.loading ? 'disabled' : ''}>${state.loading ? 'Loading...' : 'Search'}</button> </form> ${state.error ? `<div class="error" role="alert" tabindex="-1">${escape(state.error)}</div>` : ''} ${state.pokemon ? renderResult(state.pokemon) : ''} - </main> + </container> `; } function renderResult(pokemon) { return ` <div class="result"> + <h2>${capitalize(pokemon.name)} (#${pokemon.id})</h2> <img class="pokemon-sprite" src="${pokemon.sprites.front_default}" alt="${escape(pokemon.name)} sprite" /> - <div><strong>${capitalize(pokemon.name)}</strong> (#${pokemon.id})</div> - <div>Type: ${pokemon.types.map(t => escape(t.type.name)).join(', ')}</div> - <div>Height: ${pokemon.height / 10} m</div> - <div>Weight: ${pokemon.weight / 10} kg</div> + <ul> + <li>Type: <b>${pokemon.types.map(t => capitalize(escape(t.type.name))).join(', ')}</b></li> + <li>Height: <b>${pokemon.height / 10} m</b></li> + <li>Weight: <b>${pokemon.weight / 10} kg</b></li> + </ul> </div> `; } diff --git a/js/seed/style.css b/js/seed/style.css index 1d70c7f..da35a7a 100644 --- a/js/seed/style.css +++ b/js/seed/style.css @@ -67,13 +67,13 @@ button:disabled { border: 1.5px solid var(--color-result-border); border-radius: 0.3em; background: var(--color-result-bg); - text-align: center; } .pokemon-sprite { width: 120px; height: 120px; object-fit: contain; - margin-bottom: 0.5em; + margin: 0 auto; + display: block; } .error { color: var(--color-error); |