-
Notifications
You must be signed in to change notification settings - Fork 5
/
index.tsx
134 lines (118 loc) · 4.23 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import * as L from "lonna"
import { globalScope } from "lonna";
import { h, mount, ListView } from "../../src/index"
import itemAddedFromSocketE from "./fake-socket";
// The domain object constructor
let idCounter = 1;
type Id = number
type TodoItem = {
name: string,
id: Id,
completed: boolean
}
function todoItem(name: string, id: number = idCounter++, completed: boolean = false): TodoItem {
return {
name,
completed,
id
}
}
const initialItems = ["learn typescript", "fix handbrake"].map(s => todoItem(s));
type AppEvent = { action: "add", name: string } | { action: "remove", id: Id } | { action: "update", item: TodoItem }
const appEvents = L.bus<AppEvent>()
// Events/actions
// New items event stream is merged from use events and events from "server"
// Merging two streams of strings and finally mapping them into TodoItem objects
//const newItemE = Rx.map(Rx.merge(itemAddedFromSocketE, addItemBus), todoItem)
itemAddedFromSocketE.forEach(name => appEvents.push({ action: "add", name }))
// The state "megablob" reactive property created by reducing from events
function reducer(items: TodoItem[], event: AppEvent): TodoItem[] {
switch (event.action) {
case "add": return items.concat(todoItem(event.name))
case "remove": return items.filter(i => i.id !== event.id)
case "update":return items.map(i => i.id === event.item.id ? event.item : i)
default:
console.warn("Unknown event", event)
return items
}
}
const allItems = appEvents.pipe(L.scan(initialItems, reducer, globalScope))
const App = () => {
return (
<div>
<h1>TODO App</h1>
<ItemList items={allItems} />
<NewItem />
<JsonView json={allItems} />
</div>
);
};
/*
ItemList2 uses the "observable" version of ListView. Here the renderObservable function gets
Property<TodoItem> and is thus able to observe changes in the item. Now we don't have to replace
the whole item view when something changes.
*/
const ItemList = ({ items }: { items: L.Property<TodoItem[]>}) => {
return (
<ul>
{/* when using this variant of ListView (renderItem) the items
will be completely replaced with changed (based on the given `equals`) */}
<ListView
observable={items}
renderObservable={(id: number, item: L.Property<TodoItem>) => <li><ItemView id={id} item={item}/></li>}
getKey={ item => item.id }
/>
</ul>
);
};
const ItemView = ({ id, item }: { id: number, item: L.Property<TodoItem> }) => {
// Use a "dependent atom", where you can specify what happens when the value is changed. In
// this case we push changes to the bus which will then cause state changes to propagate back here.
// A dependent atom provides a bridge between atom-based components and "unidirectional data flow"
// style state management.
const itemAtom = L.atom(item, updated => appEvents.push({ action: "update", item: updated }))
return (
<span>
<span className="name"><TextInput value={L.view(itemAtom, "name")} /></span>
<Checkbox checked={L.view(itemAtom, "completed")}/>
<a className="removeItem" onClick={() => appEvents.push({ action: "remove", id})}>
remove
</a>
</span>
);
};
const NewItem = () => {
const name = L.atom("")
const addNew = () => appEvents.push({ action: "add", name: name.get() })
return (
<div className="newItem">
<TextInput placeholder="new item name" value={name} />
<button onClick={addNew}>Add new item</button>
</div>
);
};
const TextInput = (props: { value: L.Atom<string> } & any) => {
return <input {...{
type: "text",
onInput: e => {
props.value.set(e.currentTarget.value)
},
...props,
value: props.value
}} />
};
const Checkbox = (props: { checked: L.Atom<boolean> } & any) => {
return <input {...{
type: "checkbox",
onInput: e => {
props.checked.set(e.currentTarget.checked)
},
...props,
checked: props.checked
}} />
};
const JsonView = ({ json }: { json: L.Property<any>}) => {
const s = L.view(json, st => JSON.stringify(st, null, 2))
return <pre>{s}</pre>;
};
mount(<App/>, document.getElementById("root")!)