diff --git a/fsm-actions.js b/fsm-actions.js index 9b8608f..87e3531 100644 --- a/fsm-actions.js +++ b/fsm-actions.js @@ -1,7 +1,8 @@ import StateMachine from './fsm.js'; import StorageProxy from './storage.js'; import StreamMapper from './streammapper.js'; -import { effect, html, signal } from './reactive.js'; +import debounce from './debounce.js'; +import { effect, html, signal, render } from './reactive.js'; // shorter keys than crypto.randomUUID() const random = () => window.crypto.getRandomValues(new Uint32Array(1))[0]; @@ -20,6 +21,9 @@ const defaultState = { const DEBUG = false; +// Maximum write interval +const writeInterval = 5000; + // Use a unique key for app storage const appKey = 'rfsm-actions'; const clearStorage = false; @@ -35,17 +39,14 @@ for (const k in defaultState) { // Debounced storage updater // TODO: should be generalized -const updateStorage = () => { +const updateStorageImmediately = () => { + console.log('updateStorage()'); for (let i in defaultState) { - const stored = storagingState[i]; - const live = appState[i]; - if (Object.is(live, stored) === false) { - storagingState[i] = appState[i].value; - //console.log(`Updating storage key ${i} with value`, appState[i]); - } + storagingState[i] = appState[i].value; } }; -//const updateStorageDebounced = debounce(updateStorage, 1000); + +const updateStorage = debounce(updateStorageImmediately, writeInterval); // Component container const appContainer = document.querySelector('#app'); @@ -55,37 +56,51 @@ const machineConfig = { initial: 'uninitialized', states: { uninitialized: ['initialized'], - initialized: ['viewing'], - viewing: ['editing'], - editing: ['viewing'] + initialized: ['viewlist'], + viewlist: ['editing'], + editing: ['viewlist'] } }; const machine = new StateMachine(machineConfig); -/* +// Initialize signal tracking machine current state +appState.current = signal(machine.current); + machine.on('*', (prev) => { //console.log(`FSM.on(*): state changed from ${prev} to ${machine.current}, (${appState.current.value})`); console.log('FSM.on(*): ', prev, 'to', machine.current); - if (prev !== machine.current) { - console.log('FSM.on(*): changing state', prev, 'to', machine.current); - //appState.current.value = machine.current; + + // Sync signal to state machine current state + appState.current.value = machine.current; + + console.log('Effect: state changed to', machine.current); + // Don't bother writing until after initialization + if (!['uninitialized', 'initialized'].includes(machine.current)) { + console.log('effect: calling updateStorage()'); + updateStorage(); } }); -*/ machine.on('initialized', async (prev) => { - console.log('FSM.initialized: rendering'); - appContainer.innerHTML = html`${App(appState)}`; - console.log('FSM.initialized: rendering started, setting state to viewing'); - //appState.current.value = 'viewing'; - appState.current.value = machine.current; + console.log('FSM.initialized'); + + // Increment counter once per page load + appState.counter.value = appState.counter.value + 1; + machine.go(); }); -machine.on('viewing', (prev) => { - console.log('FSM.viewing'); - appContainer.innerHTML = html`${App(appState)}`; +machine.on('viewlist', (prev) => { + console.log('FSM.viewlist'); + + // Watch for item changes and re-render + // This effect runs immediately, handling the initial render + effect(() => { + const items = appState.items.value; // Subscribe to items + console.log('Rendering app with', items.length, 'items'); + render(appContainer, html`${App(appState)}`); + }); }); // Item factory @@ -104,10 +119,10 @@ const createItem = (text) => ({ const actions = { addItem: async ({text}) => { if (text.trim()) { + console.log('addItem()'); const newItem = createItem(text); appState.items.value = [...appState.items.value, newItem]; - console.log('updateItem(): setting state to viewing'); - //appState.current.value = 'viewing'; + console.log('addItem(): done'); } }, @@ -130,15 +145,13 @@ const actions = { } console.log('new items', newItems); } - console.log('updateItem(): setting state to viewing'); - //appState.current.value = 'viewing'; + console.log('updateItem(): done'); }, deleteItem: async ({item}) => { console.log('actions.deleteItem', item); appState.items.value = appState.items.value.filter(t => t.id != item.id); - console.log('deletedItem(): setting state to viewing'); - //appState.current.value = 'viewing'; + console.log('deletedItem()'); } }; @@ -156,6 +169,7 @@ const emit = StreamMapper(actions, listener); const StateInfo = ({ currentState, items }) => { console.log('StateInfo', currentState, items); + // State metadata const total = items.length; @@ -197,14 +211,14 @@ const Card = ({ item }) => { }; return html` -
+
${Textarea({value: s.value.text})} ${Button({label: 'Delete', onclick: onDelete})}
`; }; -const Cards = ({items}) => { +const Cards = ({ items }) => { const s = signal(items); return html` @@ -216,7 +230,6 @@ const Cards = ({items}) => { const App = (state) => { console.log('App', state); - state.counter.value = state.counter.value + 1; const onClickNew = () => emit('addItem', {text: 'New item'}); @@ -234,7 +247,7 @@ const App = (state) => { ${Button({label: '+', onclick: onClickNew})} ${Button({label: layout.value, onclick: onClickLayout})} ${Button({label: sort.value, onclick: onClickSort})} - ${StateInfo({currentState: state.current.value, items: state.items.value})} + ${StateInfo({currentState: machine.current, items: state.items.value})} ${Cards({items: state.items.value})}