This commit is contained in:
dmitry-vsl
2025-05-30 19:59:02 +00:00
parent 1a30311de4
commit 75b1c5e942
30 changed files with 959 additions and 832 deletions

View File

@@ -2,7 +2,7 @@
Example of a TODO app built using the Preact library
*/
import React from 'https://esm.sh/preact/compat'
import React from "preact/compat"
// Core
@@ -13,7 +13,7 @@ let state
if (globalThis.leporello) {
// Retrieve initial state from Leporello storage
// See: https://github.com/leporello-js/leporello-js?tab=readme-ov-file#saving-state-between-page-reloads
state = globalThis.leporello.storage.get('state')
state = globalThis.leporello.storage.get("state")
}
/*
@@ -21,14 +21,16 @@ if (globalThis.leporello) {
This helper function wraps such a function so that its result updates the global state
and can be used as an event handler.
*/
const handler = fn => (...args) => {
state = fn(state, ...args)
if (globalThis.leporello) {
// Persist state to Leporello storage to restore it after page reloads
globalThis.leporello.storage.set('state', state)
const handler =
fn =>
(...args) => {
state = fn(state, ...args)
if (globalThis.leporello) {
// Persist state to Leporello storage to restore it after page reloads
globalThis.leporello.storage.set("state", state)
}
render()
}
render()
}
// Higher-order function that injects the current state into a component
const connect = comp => props => comp(props, state)
@@ -39,12 +41,12 @@ const render = () => React.render(<App />, document.body)
if (state == null) {
state = {
todos: [],
text: '',
filter: 'ALL',
text: "",
filter: "ALL",
}
}
window.addEventListener('load', render)
window.addEventListener("load", render)
// Components
@@ -68,10 +70,10 @@ const Footer = () => (
const FilterLink = connect(({ filter, children }, state) => {
const disabled = state.filter == filter
return (
<button
onClick={handler(changeFilter.bind(null, filter))}
disabled={disabled}
style={{ marginLeft: '4px' }}
<button
onClick={handler(changeFilter.bind(null, filter))}
disabled={disabled}
style={{ marginLeft: "4px" }}
>
{children}
</button>
@@ -87,9 +89,9 @@ const TodoList = connect((_, state) => (
))
const Todo = ({ todo }) => (
<li
onClick={handler(toggleTodo.bind(null, todo))}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
<li
onClick={handler(toggleTodo.bind(null, todo))}
style={{ textDecoration: todo.completed ? "line-through" : "none" }}
>
{todo.text}
</li>
@@ -99,11 +101,7 @@ const AddTodo = connect((_, state) => {
return (
<div>
<form onSubmit={handler(createTodo)}>
<input
value={state.text}
onChange={handler(changeText)}
autoFocus
/>
<input value={state.text} onChange={handler(changeText)} autoFocus />
<button type="submit">Add Todo</button>
</form>
</div>
@@ -114,14 +112,14 @@ const AddTodo = connect((_, state) => {
// Returns a filtered list of TODOs based on the current filter state
function visibleTodos(state) {
if (state.filter == 'ALL') {
if (state.filter == "ALL") {
return state.todos
} else if (state.filter == 'ACTIVE') {
} else if (state.filter == "ACTIVE") {
return state.todos.filter(t => !t.completed)
} else if (state.filter == 'COMPLETED') {
} else if (state.filter == "COMPLETED") {
return state.todos.filter(t => t.completed)
} else {
throw new Error('Unknown filter')
throw new Error("Unknown filter")
}
}
@@ -146,18 +144,18 @@ function createTodo(state, e) {
}
return {
...state,
...state,
todos: [...state.todos, { text: state.text }],
text: '',
text: "",
}
}
// Toggles the completion state of a TODO item
function toggleTodo(todo, state) {
return {
...state,
...state,
todos: state.todos.map(t =>
t == todo ? { ...todo, completed: !todo.completed } : t
)
t == todo ? { ...todo, completed: !todo.completed } : t,
),
}
}