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,34 +2,33 @@
// Author: Sarah Bricault
// Canvas setup
const canvas = document.createElement('canvas')
const canvas = document.createElement("canvas")
canvas.width = 700
canvas.height = 700
document.body.appendChild(canvas)
const ctx = canvas.getContext('2d')
const ctx = canvas.getContext("2d")
ctx.translate(canvas.width / 2, canvas.height)
// Draw a tree
await fractalTreeBasic({totalIterations: 10, basicLength: 10, rotate: 25})
await fractalTreeBasic({ totalIterations: 10, basicLength: 10, rotate: 25 })
function sleep() {
return new Promise(resolve => setTimeout(resolve, 3))
}
async function fractalTreeBasic({totalIterations, basicLength, rotate}) {
async function fractalTreeBasic({ totalIterations, basicLength, rotate }) {
// Draw the tree trunk
const trunkLength = basicLength * 2 * Math.pow(1.2, totalIterations + 1)
const width = Math.pow(totalIterations, 0.6)
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(0, - trunkLength)
ctx.lineTo(0, -trunkLength)
ctx.lineWidth = width
ctx.strokeStyle = 'black'
ctx.strokeStyle = "black"
ctx.stroke()
await drawBranch(90, [0, - trunkLength], totalIterations + 1)
await drawBranch(90, [0, -trunkLength], totalIterations + 1)
async function drawBranch(angle, startPoint, iterations) {
const len = basicLength * Math.pow(1.2, iterations)
@@ -38,7 +37,7 @@ async function fractalTreeBasic({totalIterations, basicLength, rotate}) {
const red = Math.floor(255 - (iterations / totalIterations) * 255)
const green = 0
const blue = Math.floor( 255 - (iterations / totalIterations) * 255)
const blue = Math.floor(255 - (iterations / totalIterations) * 255)
const color = `rgb(${red}, ${green}, ${blue})`
const x1 = startPoint[0]
@@ -47,7 +46,7 @@ async function fractalTreeBasic({totalIterations, basicLength, rotate}) {
const y2 = y1 - len * Math.sin((angle * Math.PI) / 180)
const x2 = x1 + len * Math.cos((angle * Math.PI) / 180)
console.log('draw branch', x1, y1, x2, y2)
console.log("draw branch", x1, y1, x2, y2)
ctx.beginPath()
ctx.moveTo(x1, y1)

View File

@@ -1,8 +1,8 @@
// Original source:
// Original source:
// https://www.freecodecamp.org/news/how-to-create-animated-bubbles-with-html5-canvas-and-javascript/
const canvas = document.createElement('canvas')
canvas.style.backgroundColor = '#00b4ff'
const canvas = document.createElement("canvas")
canvas.style.backgroundColor = "#00b4ff"
document.body.appendChild(canvas)
canvas.width = window.innerWidth
canvas.height = window.innerHeight
@@ -10,9 +10,9 @@ canvas.height = window.innerHeight
const context = canvas.getContext("2d")
context.font = "30px Arial"
context.textAlign = 'center'
context.fillStyle = 'white'
context.fillText('Click to spawn bubbles', canvas.width/2, canvas.height/2)
context.textAlign = "center"
context.fillStyle = "white"
context.fillText("Click to spawn bubbles", canvas.width / 2, canvas.height / 2)
let circles = []
@@ -28,7 +28,7 @@ function draw(circle) {
1,
circle.x + 0.5,
circle.y + 0.5,
circle.radius
circle.radius,
)
gradient.addColorStop(0.3, "rgba(255, 255, 255, 0.3)")
@@ -39,20 +39,20 @@ function draw(circle) {
}
function move(circle, timeDelta) {
circle.x = circle.x + timeDelta*circle.dx
circle.y = circle.y - timeDelta*circle.dy
circle.x = circle.x + timeDelta * circle.dx
circle.y = circle.y - timeDelta * circle.dy
}
let intervalId
function startAnimation() {
if(intervalId == null) {
if (intervalId == null) {
intervalId = setInterval(animate, 20)
}
}
function stopAnimation() {
if(intervalId != null) {
if (intervalId != null) {
clearInterval(intervalId)
intervalId = null
}
@@ -65,31 +65,31 @@ const animate = () => {
const timeDelta = prevFrameTime == null ? 0 : now - prevFrameTime
prevFrameTime = now
if(circles.length == 0) {
if (circles.length == 0) {
return
}
context.clearRect(0, 0, canvas.width, canvas.height)
context.clearRect(0, 0, canvas.width, canvas.height)
circles.forEach(circle => {
move(circle, timeDelta)
draw(circle)
})
circles.forEach(circle => {
move(circle, timeDelta)
draw(circle)
})
}
const createCircles = (event) => {
const createCircles = event => {
startAnimation()
circles = circles.concat(Array.from({length: 50}, () => (
{
circles = circles.concat(
Array.from({ length: 50 }, () => ({
x: event.pageX,
y: event.pageY,
radius: Math.random() * 50,
dx: Math.random() * 0.3,
dy: Math.random() * 0.7,
hue: 200,
}
)))
})),
)
}
canvas.addEventListener("click", createCircles)

View File

@@ -1,34 +1,40 @@
window.addEventListener('load', () => {
const text = document.createElement('input')
window.addEventListener("load", () => {
const text = document.createElement("input")
const checkbox = document.createElement('input')
checkbox.setAttribute('type', 'checkbox')
const checkbox = document.createElement("input")
checkbox.setAttribute("type", "checkbox")
const radio = document.createElement('input')
radio.setAttribute('type', 'radio')
const radio = document.createElement("input")
radio.setAttribute("type", "radio")
const range = document.createElement('input')
range.setAttribute('type', 'range')
const range = document.createElement("input")
range.setAttribute("type", "range")
const select = document.createElement('select')
Array.from({length: 5}, (_, i) => i).forEach(i => {
const option = document.createElement('option')
option.setAttribute('value', i)
const select = document.createElement("select")
Array.from({ length: 5 }, (_, i) => i).forEach(i => {
const option = document.createElement("option")
option.setAttribute("value", i)
option.innerText = i
select.appendChild(option)
})
const div = document.createElement('div')
const div = document.createElement("div")
const elements = { text, checkbox, range, select, radio, div }
const elements = { text, checkbox, range, select, radio, div}
Object.entries(elements).forEach(([name, el]) => {
document.body.appendChild(el);
['click', 'input', 'change'].forEach(type => {
document.body.appendChild(el)
;["click", "input", "change"].forEach(type => {
el.addEventListener(type, e => {
const row = document.createElement('div')
const row = document.createElement("div")
div.appendChild(row)
row.innerText = [name, type, e.target.value, e.target.checked, e.target.selectedIndex].join(', ')
row.innerText = [
name,
type,
e.target.value,
e.target.checked,
e.target.selectedIndex,
].join(", ")
})
})
})

View File

@@ -1,21 +1,25 @@
import {ethers} from 'https://unpkg.com/ethers/dist/ethers.js'
import { ethers } from "ethers"
const URL = 'https://rpc.ankr.com/eth'
const URL = "https://eth-mainnet.public.blastapi.io"
const provider = await ethers.getDefaultProvider(URL)
const latest = await provider.getBlock('latest')
const latest = await provider.getBlock("latest")
/*
Find ethereum block by timestamp using binary search
*/
async function getBlockNumberByTimestamp(timestamp, low = 0, high = latest.number) {
if(low + 1 == high) {
async function getBlockNumberByTimestamp(
timestamp,
low = 0,
high = latest.number,
) {
if (low + 1 == high) {
return low
} else {
const mid = Math.floor((low + high) / 2)
const midBlock = await provider.getBlock(mid)
if(midBlock.timestamp > timestamp) {
if (midBlock.timestamp > timestamp) {
return getBlockNumberByTimestamp(timestamp, low, mid)
} else {
return getBlockNumberByTimestamp(timestamp, mid, high)
@@ -23,6 +27,6 @@ async function getBlockNumberByTimestamp(timestamp, low = 0, high = latest.numbe
}
}
const timestamp = new Date('2019-06-01').getTime()/1000
const timestamp = new Date("2019-06-01").getTime() / 1000
const blockNumber = await getBlockNumberByTimestamp(timestamp)
const block = await provider.getBlock(blockNumber)

View File

@@ -1,15 +1,15 @@
import {ethers} from 'https://unpkg.com/ethers@5.7.2/dist/ethers.esm.js'
import { ethers } from "ethers"
const URL = 'https://rpc.ankr.com/eth_goerli'
const URL = "https://eth-mainnet.public.blastapi.io"
const p = ethers.getDefaultProvider(URL)
const latest = await p.getBlock()
const txs = await Promise.all(latest.transactions.map(t =>
p.getTransactionReceipt(t)
))
const txs = await Promise.all(
latest.transactions.map(t => p.getTransactionReceipt(t)),
)
const totalGas = txs
.filter(tx => tx != null)
.reduce((gas,tx) => gas.add(tx.gasUsed), ethers.BigNumber.from(0))
.reduce((gas, tx) => gas.add(tx.gasUsed), ethers.BigNumber.from(0))

View File

@@ -1,7 +1,7 @@
// Fibonacci numbers
function fib(n) {
if(n == 0 || n == 1) {
if (n == 0 || n == 1) {
return n
} else {
return fib(n - 1) + fib(n - 2)

View File

@@ -2,30 +2,29 @@
// Author: Sarah Bricault
// Canvas setup
const canvas = document.createElement('canvas')
const canvas = document.createElement("canvas")
canvas.width = 700
canvas.height = 700
document.body.appendChild(canvas)
const ctx = canvas.getContext('2d')
const ctx = canvas.getContext("2d")
ctx.translate(canvas.width / 2, canvas.height)
// Draw a tree
fractalTreeBasic({totalIterations: 10, basicLength: 10, rotate: 25})
function fractalTreeBasic({totalIterations, basicLength, rotate}) {
fractalTreeBasic({ totalIterations: 10, basicLength: 10, rotate: 25 })
function fractalTreeBasic({ totalIterations, basicLength, rotate }) {
// Draw the tree trunk
const trunkLength = basicLength * 2 * Math.pow(1.2, totalIterations + 1)
const width = Math.pow(totalIterations, 0.6)
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(0, - trunkLength)
ctx.lineTo(0, -trunkLength)
ctx.lineWidth = width
ctx.strokeStyle = 'black'
ctx.strokeStyle = "black"
ctx.stroke()
drawBranch(90, [0, - trunkLength], totalIterations + 1)
drawBranch(90, [0, -trunkLength], totalIterations + 1)
function drawBranch(angle, startPoint, iterations) {
const len = basicLength * Math.pow(1.2, iterations)
@@ -34,7 +33,7 @@ function fractalTreeBasic({totalIterations, basicLength, rotate}) {
const red = Math.floor(255 - (iterations / totalIterations) * 255)
const green = 0
const blue = Math.floor( 255 - (iterations / totalIterations) * 255)
const blue = Math.floor(255 - (iterations / totalIterations) * 255)
const color = `rgb(${red}, ${green}, ${blue})`
const x1 = startPoint[0]
@@ -43,7 +42,7 @@ function fractalTreeBasic({totalIterations, basicLength, rotate}) {
const y2 = y1 - len * Math.sin((angle * Math.PI) / 180)
const x2 = x1 + len * Math.cos((angle * Math.PI) / 180)
console.log('draw branch', x1, y1, x2, y2)
console.log("draw branch", x1, y1, x2, y2)
ctx.beginPath()
ctx.moveTo(x1, y1)

View File

@@ -1,7 +1,8 @@
import _ from 'https://unpkg.com/lodash-es'
import _ from "lodash-es"
async function getPopularLanguages() {
const url = 'https://api.github.com/search/repositories?q=stars:%3E1&sort=stars'
const url =
"https://api.github.com/search/repositories?q=stars:%3E1&sort=stars"
const resp = await fetch(url)
const repos = await resp.json()
return _(repos.items)
@@ -9,7 +10,7 @@ async function getPopularLanguages() {
.filter(l => l != null)
.countBy()
.toPairs()
.orderBy(([lang, useCount]) => useCount, 'desc')
.orderBy(([lang, useCount]) => useCount, "desc")
.map(([lang]) => lang)
.value()
}

View File

@@ -1,6 +1,6 @@
import _ from 'https://unpkg.com/lodash-es'
import _ from "lodash-es"
const url = 'https://api.github.com/search/repositories?q=stars:%3E1&sort=stars'
const url = "https://api.github.com/search/repositories?q=stars:%3E1&sort=stars"
const resp = await fetch(url)
const repos = await resp.json()
const langs = _(repos.items)
@@ -8,43 +8,18 @@ const langs = _(repos.items)
.filter(l => l != null)
.countBy()
.toPairs()
.map(([language, count]) => ({language, count}))
.map(([language, count]) => ({ language, count }))
.value()
import {barY} from "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm";
import { barY } from "@observablehq/plot"
/*
Move the cursor to the following line and see the plot displayed alongside the code
*/
barY(langs, {x: "language", y: "count", sort: {x: "y", reverse: true}, fill: 'purple'})
.plot()
barY(langs, {
x: "language",
y: "count",
sort: { x: "y", reverse: true },
fill: "purple",
}).plot()

View File

@@ -1,38 +1,44 @@
const fib = n => {
if(n == 0) {
if (n == 0) {
return 0
}
if(n == 1) {
if (n == 1) {
return 1
}
return fib(n - 1) + fib(n - 2)
}
/* external */
import {h, render} from 'https://unpkg.com/preact?module';
/* external */
import {Stateful} from './stateful.js'
import { h, render } from "preact"
/* external */
import { Stateful } from "./stateful.js"
const Fibonacci = Stateful({
getInitialState: () => ({index: 0}),
getInitialState: () => ({ index: 0 }),
handlers: {
prev: ({index}, event) => ({index: index - 1}),
next: ({index}, event) => ({index: index + 1}),
prev: ({ index }, event) => ({ index: index - 1 }),
next: ({ index }, event) => ({ index: index + 1 }),
},
render: (props, state, handlers) =>
h('div', null,
h('h1', null,
'nth Fibonacci number is ',
render: (props, state, handlers) =>
h(
"div",
null,
h(
"h1",
null,
"nth Fibonacci number is ",
fib(state.index),
' for n = ',
state.index
" for n = ",
state.index,
),
h('button', {onClick: handlers.prev}, 'Previous'), ' ',
h('button', {onClick: handlers.next}, 'Next'), ' ',
)
h("button", { onClick: handlers.prev }, "Previous"),
" ",
h("button", { onClick: handlers.next }, "Next"),
" ",
),
})
render(h(Fibonacci), globalThis.document.body)

View File

@@ -1,18 +1,15 @@
import {Component} from 'https://unpkg.com/preact?module';
export const Stateful = ({getInitialState, handlers, render}) => {
import { Component } from "preact"
export const Stateful = ({ getInitialState, handlers, render }) => {
return class extends Component {
constructor() {
super()
this.compState = getInitialState()
this.handlers = Object.fromEntries(
Object
.entries(handlers)
.map(([name, h]) =>
[name, this.makeHandler(h)]
)
Object.entries(handlers).map(([name, h]) => [
name,
this.makeHandler(h),
]),
)
}
@@ -27,5 +24,4 @@ export const Stateful = ({getInitialState, handlers, render}) => {
return render(this.props, this.compState, this.handlers)
}
}
}

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,
),
}
}

View File

@@ -1,18 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Redux Todos Example</title>
<script src='https://unpkg.com/react/umd/react.development.js'></script>
<script src='https://unpkg.com/react-dom/umd/react-dom.development.js'></script>
<script src='https://unpkg.com/redux'></script>
<script src='https://unpkg.com/react-redux'></script>
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/redux"></script>
<script src="https://unpkg.com/react-redux"></script>
<script type='module'>
if(new URLSearchParams(window.location.search).get('leporello') == null) {
await import('../src/index.js');
<script type="module">
if (
new URLSearchParams(window.location.search).get("leporello") == null
) {
await import("../src/index.js")
}
</script>
</head>

View File

@@ -6,23 +6,23 @@ const nextTodoId = new Function(`
`)()
export const addTodo = text => ({
type: 'ADD_TODO',
type: "ADD_TODO",
id: nextTodoId(),
text
text,
})
export const setVisibilityFilter = filter => ({
type: 'SET_VISIBILITY_FILTER',
filter
type: "SET_VISIBILITY_FILTER",
filter,
})
export const toggleTodo = id => ({
type: 'TOGGLE_TODO',
id
type: "TOGGLE_TODO",
id,
})
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
SHOW_ALL: "SHOW_ALL",
SHOW_COMPLETED: "SHOW_COMPLETED",
SHOW_ACTIVE: "SHOW_ACTIVE",
}

View File

@@ -1,15 +1,9 @@
import Footer from './Footer.js'
import AddTodo from '../containers/AddTodo.js'
import VisibleTodoList from '../containers/VisibleTodoList.js'
import Footer from "./Footer.js"
import AddTodo from "../containers/AddTodo.js"
import VisibleTodoList from "../containers/VisibleTodoList.js"
const h = React.createElement
const App = () => (
h('div', null,
h(AddTodo),
h(VisibleTodoList),
h(Footer),
)
)
const App = () => h("div", null, h(AddTodo), h(VisibleTodoList), h(Footer))
export default App

View File

@@ -1,15 +1,16 @@
import FilterLink from '../containers/FilterLink.js'
import { VisibilityFilters } from '../actions/index.js'
import FilterLink from "../containers/FilterLink.js"
import { VisibilityFilters } from "../actions/index.js"
const h = React.createElement
const Footer = () => (
h('div', null,
h('span', null, 'Show: '),
h(FilterLink, {filter: VisibilityFilters.SHOW_ALL}, 'All'),
h(FilterLink, {filter: VisibilityFilters.SHOW_ACTIVE}, 'Active'),
h(FilterLink, {filter: VisibilityFilters.SHOW_COMPLETED}, 'Completed'),
const Footer = () =>
h(
"div",
null,
h("span", null, "Show: "),
h(FilterLink, { filter: VisibilityFilters.SHOW_ALL }, "All"),
h(FilterLink, { filter: VisibilityFilters.SHOW_ACTIVE }, "Active"),
h(FilterLink, { filter: VisibilityFilters.SHOW_COMPLETED }, "Completed"),
)
)
export default Footer

View File

@@ -1,13 +1,16 @@
const h = React.createElement
const Link = ({ active, children, onClick }) => (
h('button', {
onClick,
disabled: active,
style:{
marginLeft: '4px',
}
}, children)
)
const Link = ({ active, children, onClick }) =>
h(
"button",
{
onClick,
disabled: active,
style: {
marginLeft: "4px",
},
},
children,
)
export default Link

View File

@@ -1,12 +1,15 @@
const h = React.createElement
const Todo = ({ onClick, completed, text }) => (
h('li', {
onClick,
style: {
textDecoration: completed ? 'line-through' : 'none'
const Todo = ({ onClick, completed, text }) =>
h(
"li",
{
onClick,
style: {
textDecoration: completed ? "line-through" : "none",
},
},
}, text)
)
text,
)
export default Todo

View File

@@ -1,17 +1,18 @@
import Todo from './Todo.js'
import Todo from "./Todo.js"
const h = React.createElement
const TodoList = ({ todos, toggleTodo }) => (
h('ul', null,
const TodoList = ({ todos, toggleTodo }) =>
h(
"ul",
null,
todos.map(todo =>
h(Todo, {
key: todo.id,
...todo,
onClick: () => toggleTodo(todo.id),
})
)
}),
),
)
)
export default TodoList

View File

@@ -1,25 +1,27 @@
import { addTodo } from '../actions/index.js'
import { addTodo } from "../actions/index.js"
const h = React.createElement
const AddTodo = ({ dispatch }) => {
const inputref = {}
return (
h('div', null,
h('form', {
return h(
"div",
null,
h(
"form",
{
onSubmit: e => {
e.preventDefault()
if (inputref.input.value.trim()) {
dispatch(addTodo(inputref.input.value))
Object.assign(inputref.input, {value: ''})
Object.assign(inputref.input, { value: "" })
}
}
},
},
h('input', {ref: input => Object.assign(inputref, {input})}),
h('button', {type: 'submit'}, 'Add Todo')
)
)
h("input", { ref: input => Object.assign(inputref, { input }) }),
h("button", { type: "submit" }, "Add Todo"),
),
)
}

View File

@@ -1,15 +1,12 @@
import { setVisibilityFilter } from '../actions/index.js'
import Link from '../components/Link.js'
import { setVisibilityFilter } from "../actions/index.js"
import Link from "../components/Link.js"
const mapStateToProps = (state, ownProps) => ({
active: ownProps.filter === state.visibilityFilter
active: ownProps.filter === state.visibilityFilter,
})
const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
})
export default ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(Link)
export default ReactRedux.connect(mapStateToProps, mapDispatchToProps)(Link)

View File

@@ -1,28 +1,25 @@
import { toggleTodo } from '../actions/index.js'
import TodoList from '../components/TodoList.js'
import { VisibilityFilters } from '../actions/index.js'
import { toggleTodo } from "../actions/index.js"
import TodoList from "../components/TodoList.js"
import { VisibilityFilters } from "../actions/index.js"
const getVisibleTodos = (todos, filter) => {
if(filter == VisibilityFilters.SHOW_ALL) {
if (filter == VisibilityFilters.SHOW_ALL) {
return todos
} else if(filter == VisibilityFilters.SHOW_COMPLETED) {
} else if (filter == VisibilityFilters.SHOW_COMPLETED) {
return todos.filter(t => t.completed)
} else if(filter == VisibilityFilters.SHOW_ACTIVE) {
} else if (filter == VisibilityFilters.SHOW_ACTIVE) {
return todos.filter(t => !t.completed)
} else {
throw new Error('Unknown filter: ' + filter)
throw new Error("Unknown filter: " + filter)
}
}
const mapStateToProps = state => ({
todos: getVisibleTodos(state.todos, state.visibilityFilter)
todos: getVisibleTodos(state.todos, state.visibilityFilter),
})
const mapDispatchToProps = dispatch => ({
toggleTodo: id => dispatch(toggleTodo(id))
toggleTodo: id => dispatch(toggleTodo(id)),
})
export default ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default ReactRedux.connect(mapStateToProps, mapDispatchToProps)(TodoList)

View File

@@ -1,11 +1,11 @@
import App from './components/App.js'
import rootReducer from './reducers/index.js'
import App from "./components/App.js"
import rootReducer from "./reducers/index.js"
const h = React.createElement
const store = Redux.createStore(rootReducer)
ReactDOM.render(
h(ReactRedux.Provider, {store}, h(App)),
document.getElementById('root')
h(ReactRedux.Provider, { store }, h(App)),
document.getElementById("root"),
)

View File

@@ -1,7 +1,7 @@
import todos from './todos.js'
import visibilityFilter from './visibilityFilter.js'
import todos from "./todos.js"
import visibilityFilter from "./visibilityFilter.js"
export default Redux.combineReducers({
todos,
visibilityFilter
visibilityFilter,
})

View File

@@ -1,18 +1,16 @@
const todos = (state = [], action) => {
if(action.type == 'ADD_TODO') {
if (action.type == "ADD_TODO") {
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
completed: false,
},
]
} else if(action.type == 'TOGGLE_TODO') {
} else if (action.type == "TOGGLE_TODO") {
return state.map(todo =>
(todo.id === action.id)
? {...todo, completed: !todo.completed}
: todo
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo,
)
} else {
return state

View File

@@ -1,7 +1,7 @@
import { VisibilityFilters } from '../actions/index.js'
import { VisibilityFilters } from "../actions/index.js"
const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
if(action.type == 'SET_VISIBILITY_FILTER') {
if (action.type == "SET_VISIBILITY_FILTER") {
return action.filter
} else {
return state