diff --git a/src/components/controllers/repl-page.jsx b/src/components/controllers/repl-page.jsx index 638fc0359..60fdc1d0c 100644 --- a/src/components/controllers/repl-page.jsx +++ b/src/components/controllers/repl-page.jsx @@ -52,7 +52,7 @@ async function getInitialCode(query) { if (typeof window !== 'undefined' && localStorage.getItem('preact-www-repl-code')) { code = localStorage.getItem('preact-www-repl-code'); } else { - const slug = 'counter'; + const slug = 'counter-hooks'; if (typeof window !== 'undefined') { route(`/repl?example=${encodeURIComponent(slug)}`, true); } diff --git a/src/components/controllers/repl/examples.js b/src/components/controllers/repl/examples.js deleted file mode 100644 index 387eb8ff7..000000000 --- a/src/components/controllers/repl/examples.js +++ /dev/null @@ -1,76 +0,0 @@ -import simpleCounterExample from './examples/simple-counter.txt?url'; -import counterWithHtmExample from './examples/counter-with-htm.txt?url'; -import todoExample from './examples/todo-list.txt?url'; -import todoExampleSignal from './examples/todo-list-signal.txt?url'; -import repoListExample from './examples/github-repo-list.txt?url'; -import contextExample from './examples/context.txt?url'; -import spiralExample from './examples/spiral.txt?url'; - -export const EXAMPLES = [ - { - name: 'Simple Counter', - slug: 'counter', - url: simpleCounterExample - }, - { - name: 'Todo List', - slug: 'todo', - url: todoExample - }, - { - name: 'Todo List (Signals)', - slug: 'todo-list-signals', - url: todoExampleSignal - }, - { - name: 'Github Repo List', - slug: 'github-repo-list', - url: repoListExample - }, - { - group: 'Advanced', - items: [ - { - name: 'Counter using HTM', - slug: 'counter-htm', - url: counterWithHtmExample - }, - { - name: 'Context', - slug: 'context', - url: contextExample - } - ] - }, - { - group: 'Animation', - items: [ - { - name: 'Spiral', - slug: 'spiral', - url: spiralExample - } - ] - } -]; - -export function getExample(slug, list = EXAMPLES) { - for (let i = 0; i < list.length; i++) { - let item = list[i]; - if (item.group) { - let found = getExample(slug, item.items); - if (found) return found; - } else if (item.slug.toLowerCase() === slug.toLowerCase()) { - return item; - } - } -} - -/** - * @param {string} slug - */ -export async function fetchExample(slug) { - const example = getExample(slug); - if (!example) return; - return await fetch(example.url).then(r => r.text()); -} diff --git a/src/components/controllers/repl/examples/context.txt b/src/components/controllers/repl/examples/context.txt index 310316b43..8cb582271 100644 --- a/src/components/controllers/repl/examples/context.txt +++ b/src/components/controllers/repl/examples/context.txt @@ -1,4 +1,4 @@ -import { render, createContext } from 'preact'; +import { render, createContext } from 'preact'; import { useState, useMemo, useContext } from 'preact/hooks'; const CounterContext = createContext(null); @@ -21,9 +21,7 @@ function App() { setCount(count + 1); } - const counter = useMemo(() => { - return { count, increment }; - }, [count]); + const counter = useMemo(() => ({ count, increment }), [count]); return ( diff --git a/src/components/controllers/repl/examples/counter-with-htm.txt b/src/components/controllers/repl/examples/counter-with-htm.txt deleted file mode 100644 index 598fc7263..000000000 --- a/src/components/controllers/repl/examples/counter-with-htm.txt +++ /dev/null @@ -1,23 +0,0 @@ -import { render } from 'preact'; -import { useReducer } from 'preact/hooks'; -import { html } from 'htm/preact'; - -export function Counter() { - const [count, add] = useReducer((a, b) => a + b, 0); - - return html` - - - - - `; -} - -render(, document.getElementById('app')); diff --git a/src/components/controllers/repl/examples/counters/counter-hooks.txt b/src/components/controllers/repl/examples/counters/counter-hooks.txt new file mode 100644 index 000000000..aca4bf684 --- /dev/null +++ b/src/components/controllers/repl/examples/counters/counter-hooks.txt @@ -0,0 +1,16 @@ +import { render } from 'preact'; +import { useState } from 'preact/hooks'; + +function Counter() { + const [count, setCount] = useState(0); + + return ( +
+ + + +
+ ); +} + +render(, document.getElementById('app')); diff --git a/src/components/controllers/repl/examples/counters/counter-htm.txt b/src/components/controllers/repl/examples/counters/counter-htm.txt new file mode 100644 index 000000000..261467326 --- /dev/null +++ b/src/components/controllers/repl/examples/counters/counter-htm.txt @@ -0,0 +1,17 @@ +import { render } from 'preact'; +import { useState } from 'preact/hooks'; +import { html } from 'htm/preact'; + +export function Counter() { + const [count, setCount] = useState(0); + + return html` +
+ + + +
+ `; +} + +render(, document.getElementById('app')); diff --git a/src/components/controllers/repl/examples/counters/counter-signals.txt b/src/components/controllers/repl/examples/counters/counter-signals.txt new file mode 100644 index 000000000..8edf2e36b --- /dev/null +++ b/src/components/controllers/repl/examples/counters/counter-signals.txt @@ -0,0 +1,16 @@ +import { render } from 'preact'; +import { signal } from '@preact/signals'; + +const count = signal(0); + +function Counter() { + return ( +
+ + + +
+ ); +} + +render(, document.getElementById('app')); diff --git a/src/components/controllers/repl/examples/counters/counter.txt b/src/components/controllers/repl/examples/counters/counter.txt new file mode 100644 index 000000000..555b509a0 --- /dev/null +++ b/src/components/controllers/repl/examples/counters/counter.txt @@ -0,0 +1,21 @@ +import { Component, render } from 'preact'; + +class Counter extends Component { + state = { count: 0 }; + + render({}, { count }) { + return ( +
+ + + +
+ ); + } +} + +render(, document.getElementById('app')); diff --git a/src/components/controllers/repl/examples/index.js b/src/components/controllers/repl/examples/index.js new file mode 100644 index 000000000..b241e7e7c --- /dev/null +++ b/src/components/controllers/repl/examples/index.js @@ -0,0 +1,97 @@ +// Counters +import simpleCounterExample from './counters/counter.txt?url'; +import simpleCounterHooksExample from './counters/counter-hooks.txt?url'; +import simpleCounterSignalsExample from './counters/counter-signals.txt?url'; +import simpleCounterHTMExample from './counters/counter-htm.txt?url'; + +// Todo Lists +import todoExample from './todo-lists/todo-list.txt?url'; +import todoSignalExample from './todo-lists/todo-list-signals.txt?url'; + +import repoListExample from './github-repo-list.txt?url'; +import contextExample from './context.txt?url'; +import spiralExample from './spiral.txt?url'; + +export const EXAMPLES = [ + { + group: 'Simple Counters', + items: [ + { + name: 'Simple Counter', + slug: 'counter', + url: simpleCounterExample + }, + { + name: 'Simple Counter (Hooks)', + slug: 'counter-hooks', + url: simpleCounterHooksExample + }, + { + name: 'Simple Counter (Signals)', + slug: 'counter-signals', + url: simpleCounterSignalsExample + }, + { + name: 'Simple Counter (HTM)', + slug: 'counter-htm', + url: simpleCounterHTMExample + } + ] + }, + { + group: 'Todo Lists', + items: [ + { + name: 'Todo List', + slug: 'todo', + url: todoExample + }, + { + name: 'Todo List (Signals)', + slug: 'todo-signals', + url: todoSignalExample + } + ] + }, + { + name: 'Github Repo List', + slug: 'github-repo-list', + url: repoListExample + }, + { + name: 'Context', + slug: 'context', + url: contextExample + }, + { + group: 'Animation', + items: [ + { + name: 'Spiral', + slug: 'spiral', + url: spiralExample + } + ] + } +]; + +export function getExample(slug, list = EXAMPLES) { + for (let i = 0; i < list.length; i++) { + let item = list[i]; + if (item.group) { + let found = getExample(slug, item.items); + if (found) return found; + } else if (item.slug.toLowerCase() === slug.toLowerCase()) { + return item; + } + } +} + +/** + * @param {string} slug + */ +export async function fetchExample(slug) { + const example = getExample(slug); + if (!example) return; + return await fetch(example.url).then(r => r.text()); +} diff --git a/src/components/controllers/repl/examples/simple-counter.txt b/src/components/controllers/repl/examples/simple-counter.txt deleted file mode 100644 index d7cb7fe83..000000000 --- a/src/components/controllers/repl/examples/simple-counter.txt +++ /dev/null @@ -1,16 +0,0 @@ -import { render } from 'preact'; -import { useState } from 'preact/hooks'; - -function Counter() { - const [value, setValue] = useState(0); - - return ( - <> -
Counter: {value}
- - - - ); -} - -render(, document.getElementById('app')); diff --git a/src/components/controllers/repl/examples/spiral.txt b/src/components/controllers/repl/examples/spiral.txt index c4b9f7f5d..7dae9af3b 100644 --- a/src/components/controllers/repl/examples/spiral.txt +++ b/src/components/controllers/repl/examples/spiral.txt @@ -9,12 +9,12 @@ options.debounceRendering = requestAnimationFrame; class Spiral extends Component { state = { x: 0, y: 0, big: false, count: 0 }; - handleMouseMove = e => { + handlePointerMove = e => { this.setState({ x: e.pageX, y: e.pageY }); }; - handleMouseDownUp = e => { - this.setState({ big: e.type === 'mousedown' }); + handlePointerDownUp = e => { + this.setState({ big: e.type === 'pointerdown' }); }; increment = () => { @@ -31,7 +31,7 @@ class Spiral extends Component { cancelAnimationFrame(this.raf); } - render(props, { x, y, big, count }) { + render({}, { x, y, big, count }) { let max = (COUNT + Math.sin((count / 90) * 2 * Math.PI) * COUNT * 0.5) | 0, dots = []; @@ -47,9 +47,9 @@ class Spiral extends Component { return (
{dots} @@ -90,7 +90,6 @@ class Dot extends Component { render(, document.getElementById('app')); - // Add some styles!! const style = document.createElement('style'); document.body.append(style); diff --git a/src/components/controllers/repl/examples.css b/src/components/controllers/repl/examples/style.css similarity index 87% rename from src/components/controllers/repl/examples.css rename to src/components/controllers/repl/examples/style.css index 5808aea2a..443e5ee0a 100644 --- a/src/components/controllers/repl/examples.css +++ b/src/components/controllers/repl/examples/style.css @@ -1,3 +1,15 @@ +.counter-container { + input, + button { + margin: 0.5rem; + text-align: center; + } + + input { + width: 3rem; + } +} + .list-item { padding: 1rem; margin: 1rem; diff --git a/src/components/controllers/repl/examples/todo-list-signal.txt b/src/components/controllers/repl/examples/todo-list-signal.txt deleted file mode 100644 index f91574438..000000000 --- a/src/components/controllers/repl/examples/todo-list-signal.txt +++ /dev/null @@ -1,59 +0,0 @@ -import { render } from "preact"; -import { signal, computed } from "@preact/signals"; - -const todos = signal([ - { text: "Write my first post", completed: true }, - { text: "Buy new groceries", completed: false }, - { text: "Walk the dog", completed: false }, -]); - -const completedCount = computed(() => { - return todos.value.filter(todo => todo.completed).length; -}); - -const newItem = signal(""); - -function addTodo() { - todos.value = [...todos.value, { text: newItem.value, completed: false }]; - newItem.value = ""; // Reset input value on add -} - -function removeTodo(index) { - todos.value.splice(index, 1) - todos.value = [...todos.value]; -} - -function TodoList() { - const onInput = event => (newItem.value = event.target.value); - - return ( - <> - - -
    - {todos.value.map((todo, index) => { - return ( -
  • - - {' '} - -
  • - ); - })} -
-

Completed count: {completedCount.value}

- - ); -} - -render(, document.getElementById("app")); diff --git a/src/components/controllers/repl/examples/todo-list.txt b/src/components/controllers/repl/examples/todo-list.txt deleted file mode 100644 index a77ba5e04..000000000 --- a/src/components/controllers/repl/examples/todo-list.txt +++ /dev/null @@ -1,28 +0,0 @@ -import { render, Component } from 'preact'; - -class TodoList extends Component { - state = { todos: [], text: '' }; - setText = e => { - this.setState({ text: e.target.value }); - }; - addTodo = () => { - let { todos, text } = this.state; - todos = todos.concat({ text }); - this.setState({ todos, text: '' }); - }; - render({}, { todos, text }) { - return ( -
- - -
    - {todos.map(todo => ( -
  • {todo.text}
  • - ))} -
-
- ); - } -} - -render(, document.getElementById('app')); diff --git a/src/components/controllers/repl/examples/todo-lists/todo-list-signals.txt b/src/components/controllers/repl/examples/todo-lists/todo-list-signals.txt new file mode 100644 index 000000000..db5f46cf5 --- /dev/null +++ b/src/components/controllers/repl/examples/todo-lists/todo-list-signals.txt @@ -0,0 +1,58 @@ +import { render } from 'preact'; +import { signal, computed } from '@preact/signals'; + +const todos = signal([ + { text: 'Write my first post', completed: true }, + { text: 'Buy new groceries', completed: false }, + { text: 'Walk the dog', completed: false } +]); + +const completedCount = computed( + () => todos.value.filter(todo => todo.completed).length +); + +const newItem = signal(''); + +function addTodo(e) { + e.preventDefault(); + + todos.value = [...todos.value, { text: newItem.value, completed: false }]; + newItem.value = ''; // Reset input value on add +} + +function removeTodo(index) { + todos.value.splice(index, 1); + todos.value = [...todos.value]; +} + +function TodoList() { + const onInput = event => (newItem.value = event.target.value); + + return ( +
+ + +
    + {todos.value.map((todo, index) => ( +
  • + {' '} + +
  • + ))} +
+

Completed count: {completedCount.value}

+
+ ); +} + +render(, document.getElementById('app')); diff --git a/src/components/controllers/repl/examples/todo-lists/todo-list.txt b/src/components/controllers/repl/examples/todo-lists/todo-list.txt new file mode 100644 index 000000000..feb423b10 --- /dev/null +++ b/src/components/controllers/repl/examples/todo-lists/todo-list.txt @@ -0,0 +1,68 @@ +import { render, Component } from 'preact'; + +class TodoList extends Component { + state = { + todos: [ + { text: "Write my first post", completed: true }, + { text: "Buy new groceries", completed: false }, + { text: "Walk the dog", completed: false }, + ], + newItem: '' + }; + + setNewItem = e => { + this.setState({ newItem: e.target.value }); + }; + + addTodo = e => { + e.preventDefault(); + + let { todos, newItem } = this.state; + todos = todos.concat({ text: newItem, completed: false }); + this.setState({ todos, newItem: '' }); // Reset input value on add + }; + + completeTodo = (index) => { + let { todos } = this.state; + todos[index].completed = !todos[index].completed; + this.setState({ todos }); + }; + + removeTodo = (index) => { + let { todos } = this.state; + todos.splice(index, 1); + this.setState({ todos }); + }; + + completedCount = () => { + return this.state.todos.filter(todo => todo.completed).length; + }; + + render({}, { todos, newItem }) { + return ( +
+ + +
    + {todos.map((todo, index) => ( +
  • + + {' '} + +
  • + ))} +
+

Completed count: {this.completedCount()}

+
+ ); + } +} + +render(, document.getElementById('app')); diff --git a/src/components/controllers/repl/index.jsx b/src/components/controllers/repl/index.jsx index ed32d2316..72109fda1 100644 --- a/src/components/controllers/repl/index.jsx +++ b/src/components/controllers/repl/index.jsx @@ -8,7 +8,7 @@ import { useStoredValue } from '../../../lib/localstorage'; import { useResource } from '../../../lib/use-resource'; import { parseStackTrace } from './errors'; import style from './style.module.css'; -import REPL_CSS from './examples.css?raw'; +import REPL_CSS from './examples/style.css?raw'; /** * @param {Object} props