Skip to content

Commit 901103c

Browse files
committed
Merge branch 'WorldMaker-max/entitled-raven'
2 parents e923828 + d72a253 commit 901103c

File tree

12 files changed

+1637
-0
lines changed

12 files changed

+1637
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
**/*.js
2+
index.html
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"semi": false,
3+
"singleQuote": true,
4+
"endOfLine": "auto"
5+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { writeFile } from 'node:fs/promises'
2+
import { buildStamp, makeTestComponentContext, makeTestEvent } from 'butterfloat'
3+
import { build } from 'esbuild'
4+
import { JSDOM } from 'jsdom'
5+
import { NEVER } from 'rxjs'
6+
7+
await build({
8+
entryPoints: ['./app-vm.ts', './app.tsx', './data.ts', './row-vm.ts', './row.tsx'],
9+
bundle: false,
10+
format: 'esm',
11+
outdir: '.',
12+
})
13+
14+
await build({
15+
entryPoints: ['./main.ts'],
16+
bundle: true,
17+
format: 'esm',
18+
outdir: '.',
19+
})
20+
21+
const dom = new JSDOM(`
22+
<!DOCTYPE html>
23+
<html lang="en">
24+
<head>
25+
<meta charset="utf-8"/>
26+
<title>Butterfloat</title>
27+
<link href="/css/currentStyle.css" rel="stylesheet"/>
28+
<script type="module" src="./main.js"></script>
29+
</head>
30+
<body>
31+
<div id='main'>
32+
</div>
33+
<span class="preloadicon glyphicon glyphicon-remove" aria-hidden="true"></span>
34+
</body>
35+
</html>
36+
`)
37+
38+
const { window } = dom
39+
const { document } = window
40+
globalThis.document = document
41+
globalThis.window = window
42+
43+
const { App } = await import('./app.js')
44+
const { context: appContext } = makeTestComponentContext({
45+
run: makeTestEvent(NEVER),
46+
runlots: makeTestEvent(NEVER),
47+
add: makeTestEvent(NEVER),
48+
update: makeTestEvent(NEVER),
49+
clear: makeTestEvent(NEVER),
50+
swaprows: makeTestEvent(NEVER),
51+
})
52+
const appStamp = buildStamp(App({}, appContext), document)
53+
appStamp.id = 'app'
54+
document.body.append(appStamp)
55+
56+
const { Row } = await import('./row.js')
57+
const { AppViewModel } = await import('./app-vm.js')
58+
const { RowViewModel } = await import('./row-vm.js')
59+
const vm = new RowViewModel(new AppViewModel(), -999)
60+
const { context: rowContext } = makeTestComponentContext({
61+
select: makeTestEvent(NEVER),
62+
remove: makeTestEvent(NEVER),
63+
})
64+
const rowStamp = buildStamp(Row({ vm }, rowContext), document)
65+
rowStamp.id = 'row'
66+
document.body.append(rowStamp)
67+
68+
await writeFile('./index.html', dom.serialize())
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { butterfly, StateSetter } from 'butterfloat'
2+
import { filter, map, mergeMap, Observable, range } from 'rxjs'
3+
import { RowViewModel } from './row-vm.js'
4+
5+
export interface IdRange {
6+
min: number
7+
max: number
8+
added: [start: number, count: number]
9+
}
10+
11+
export class AppViewModel {
12+
readonly #idRange: Observable<IdRange>
13+
readonly #setIdRange: (idRange: StateSetter<IdRange>) => void
14+
get idRange() {
15+
return this.#idRange
16+
}
17+
18+
readonly #selectedId: Observable<number>
19+
readonly #setSelectedId: (id: StateSetter<number>) => void
20+
get selectedId() {
21+
return this.#selectedId
22+
}
23+
24+
readonly #rows: Observable<RowViewModel>
25+
get rows() {
26+
return this.#rows
27+
}
28+
29+
readonly #rowsToUpdate: Observable<number>
30+
readonly #setRowsToUpdate: (rowsToUpdate: StateSetter<number>) => void
31+
get rowsToUpdate() {
32+
return this.#rowsToUpdate
33+
}
34+
35+
constructor() {
36+
;[this.#idRange, this.#setIdRange] = butterfly<IdRange>({
37+
min: 0,
38+
max: 0,
39+
added: [-1, -1],
40+
})
41+
;[this.#selectedId, this.#setSelectedId] = butterfly<number>(-1)
42+
;[this.#rowsToUpdate, this.#setRowsToUpdate] = butterfly<number>(-1)
43+
44+
this.#rows = this.#idRange.pipe(
45+
filter((idRange) => idRange.added[1] > 0),
46+
mergeMap((idRange) => range(idRange.added[0], idRange.added[1])),
47+
map((id) => new RowViewModel(this, id)),
48+
)
49+
}
50+
51+
clear() {
52+
this.#setIdRange((current) => ({
53+
min: current.max,
54+
max: current.max,
55+
added: [-1, -1],
56+
}))
57+
}
58+
59+
selectRow(id: number) {
60+
this.#setSelectedId(id)
61+
}
62+
63+
createRows(count: number) {
64+
this.#setIdRange((current) => {
65+
const min = current.max
66+
const max = current.max + count
67+
return { min, max, added: [current.max, count] }
68+
})
69+
}
70+
71+
appendRows(count: number) {
72+
this.#setIdRange((current) => {
73+
const min = current.min
74+
const max = current.max + count
75+
return { min, max, added: [current.max, count] }
76+
})
77+
}
78+
79+
updateRow(id: number) {
80+
this.#setRowsToUpdate(id)
81+
}
82+
}

frameworks/keyed/butterfloat/app.tsx

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { ComponentContext, jsx, ObservableEvent } from 'butterfloat'
2+
import { AppViewModel } from './app-vm.js'
3+
import { Row } from './row.js'
4+
import { map, withLatestFrom } from 'rxjs'
5+
6+
interface AppEvents {
7+
run: ObservableEvent<MouseEvent>
8+
runlots: ObservableEvent<MouseEvent>
9+
add: ObservableEvent<MouseEvent>
10+
update: ObservableEvent<MouseEvent>
11+
clear: ObservableEvent<MouseEvent>
12+
swaprows: ObservableEvent<MouseEvent>
13+
tbodyAttach: ObservableEvent<HTMLElement>
14+
}
15+
16+
export function App(
17+
_props: unknown,
18+
{ bindEffect, bindImmediateEffect, events }: ComponentContext<AppEvents>,
19+
) {
20+
const vm = new AppViewModel()
21+
22+
const children = vm.rows.pipe(map((row) => () => <Row vm={row} />))
23+
24+
bindImmediateEffect(events.run, () => vm.createRows(1000))
25+
bindImmediateEffect(events.runlots, () => vm.createRows(10000))
26+
bindImmediateEffect(events.add, () => vm.appendRows(1000))
27+
bindImmediateEffect(events.clear, () => vm.clear())
28+
bindEffect(
29+
events.update.pipe(withLatestFrom(events.tbodyAttach)),
30+
([_, tbody]) => {
31+
const rows = tbody.querySelectorAll('tr')
32+
for (let i = 0; i < rows.length; i += 10) {
33+
const row = rows[i]
34+
const id = Number.parseInt(row.dataset.id!, 10)
35+
vm.updateRow(id)
36+
}
37+
},
38+
)
39+
bindEffect(
40+
events.swaprows.pipe(withLatestFrom(events.tbodyAttach)),
41+
([_, tbody]) => {
42+
const rows = tbody.querySelectorAll('tr')
43+
if (rows.length > 998) {
44+
const row0 = rows[0]
45+
const row1 = rows[1]
46+
const row997 = rows[997]
47+
const row998 = rows[998]
48+
row0.after(row998)
49+
row997.after(row1)
50+
}
51+
},
52+
)
53+
54+
return (
55+
<div class="container">
56+
<div class="jumbotron">
57+
<div class="row">
58+
<div class="col-md-6">
59+
<h1>Butterfloat</h1>
60+
</div>
61+
<div class="col-md-6">
62+
<div class="row">
63+
<div class="col-sm-6 smallpad">
64+
<button
65+
type="button"
66+
class="btn btn-primary btn-block"
67+
id="run"
68+
events={{ click: events.run }}
69+
>
70+
Create 1,000 rows
71+
</button>
72+
</div>
73+
<div class="col-sm-6 smallpad">
74+
<button
75+
type="button"
76+
class="btn btn-primary btn-block"
77+
id="runlots"
78+
events={{ click: events.runlots }}
79+
>
80+
Create 10,000 rows
81+
</button>
82+
</div>
83+
<div class="col-sm-6 smallpad">
84+
<button
85+
type="button"
86+
class="btn btn-primary btn-block"
87+
id="add"
88+
events={{ click: events.add }}
89+
>
90+
Append 1,000 rows
91+
</button>
92+
</div>
93+
<div class="col-sm-6 smallpad">
94+
<button
95+
type="button"
96+
class="btn btn-primary btn-block"
97+
id="update"
98+
events={{ click: events.update }}
99+
>
100+
Update every 10th row
101+
</button>
102+
</div>
103+
<div class="col-sm-6 smallpad">
104+
<button
105+
type="button"
106+
class="btn btn-primary btn-block"
107+
id="clear"
108+
events={{ click: events.clear }}
109+
>
110+
Clear
111+
</button>
112+
</div>
113+
<div class="col-sm-6 smallpad">
114+
<button
115+
type="button"
116+
class="btn btn-primary btn-block"
117+
id="swaprows"
118+
events={{ click: events.swaprows }}
119+
>
120+
Swap Rows
121+
</button>
122+
</div>
123+
</div>
124+
</div>
125+
</div>
126+
</div>
127+
<table class="table table-hover table-striped test-data">
128+
<tbody
129+
id="tbody"
130+
childrenBind={children}
131+
childrenBindMode="append"
132+
events={{ bfDomAttach: events.tbodyAttach }}
133+
></tbody>
134+
</table>
135+
</div>
136+
)
137+
}

frameworks/keyed/butterfloat/data.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const adjectives = [
2+
'pretty',
3+
'large',
4+
'big',
5+
'small',
6+
'tall',
7+
'short',
8+
'long',
9+
'handsome',
10+
'plain',
11+
'quaint',
12+
'clean',
13+
'elegant',
14+
'easy',
15+
'angry',
16+
'crazy',
17+
'helpful',
18+
'mushy',
19+
'odd',
20+
'unsightly',
21+
'adorable',
22+
'important',
23+
'inexpensive',
24+
'cheap',
25+
'expensive',
26+
'fancy',
27+
]
28+
const colors = [
29+
'red',
30+
'yellow',
31+
'blue',
32+
'green',
33+
'pink',
34+
'brown',
35+
'purple',
36+
'brown',
37+
'white',
38+
'black',
39+
'orange',
40+
]
41+
42+
const nouns = [
43+
'table',
44+
'chair',
45+
'house',
46+
'bbq',
47+
'desk',
48+
'car',
49+
'pony',
50+
'cookie',
51+
'sandwich',
52+
'burger',
53+
'pizza',
54+
'mouse',
55+
'keyboard',
56+
]
57+
58+
export function randomLabel() {
59+
const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]
60+
const color = colors[Math.floor(Math.random() * colors.length)]
61+
const noun = nouns[Math.floor(Math.random() * nouns.length)]
62+
return `${adjective} ${color} ${noun}`
63+
}

frameworks/keyed/butterfloat/main.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { runStamps, StampCollection } from 'butterfloat'
2+
import { App } from './app.js'
3+
import { Row } from './row.js'
4+
5+
const stamps = new StampCollection()
6+
7+
const appStamp = document.querySelector<HTMLTemplateElement>('template#app')
8+
if (appStamp) {
9+
stamps.registerOnlyStamp(App, appStamp)
10+
}
11+
12+
const rowStamp = document.querySelector<HTMLTemplateElement>('template#row')
13+
if (rowStamp) {
14+
stamps.registerOnlyStamp(Row, rowStamp)
15+
}
16+
17+
const main = document.querySelector('#main')!
18+
runStamps(main, App, stamps)

0 commit comments

Comments
 (0)