Examples

State storm

About

StateStorm: a busy dashboard that puts the browser runtime under load.

quillo run storm.qwe --watch then open http://localhost:8080

Press "Storm" and every 16 ms something changes at random: items are added, removed and shuffled (a keyed list), tabs switch (whole subtrees mount and unmount), items open and close (each open one runs its own timer), text changes. The parts use every kind of reactive state there is: ~state, props and callbacks, every timers, url(), live() data and bound inputs. "Reset" puts everything back, so a test can check that nothing that was removed is still held on to (tests/browser/storm.js).

Run it

$ quillo run storm.qwe --watch

Source

storm.qwe
let visits = 0// Live data: every page shows the same count, updated when it changes.server fn visit_count() = visitsserver fn add_visit() {    visits += 1    visits}let tabs = ["list", "grid", "form"]fn initial_items() = range(0, 12) | map((i) => { id: i, label: `Item ${i}` })component Ticker(label: str, ms: int) {    state n = 0    every(ms, () => { n += 1 })    <span.ticker>{label} <b>{n}</b></span>}component Counter(id: int, on_change) {    state n = 0    <div.counter>        <button @click={() => {            n += 1            on_change(id, n)        }}>+</button>        <b>{n}</b>    </div>}component Item(item: map, open: bool, on_toggle, on_remove) {    state clicks = 0    <li.item class={open && "open"}>        <button.link @click={() => {            clicks += 1            on_toggle(item.id)        }}>{item.label}</button>        {open && <Ticker label="open for" ms={50}/>}        <span.muted>{clicks} clicks</span>        <button.remove @click={() => { on_remove(item.id) }}>×</button>    </li>}component Where() {    let u = url()    <span.where>at {u.path}</span>}component Visits() {    state count = live(visit_count)    <div.visits>        Visits: <b>{count ?? "…"}</b>        <button @click={() => { add_visit() }}>Visit</button>    </div>}view Panel(title: str, children) =    <section.panel>        <h3>{title}</h3>        {children}    </section>component Storm() {    state items = initial_items()    state next_id = 12    state open = []    state tab = "list"    state storm = false    state changes = 0    state text = "hello"    state totals = {}    let toggle = (id) => { open = any(open, (o) => o == id) ? open | filter((o) => o != id) : open + [id] }    let remove = (id) => {        items = items | filter((it) => it.id != id)        open = open | filter((o) => o != id)    }    let counted = (id, n) => { totals = set(totals, `${id}`, n) }    let shake = () => {        let r = random_int(0, 6)        if r == 0 {            items = items + [{ id: next_id, label: `Item ${next_id}` }]            next_id += 1        }        if r == 1 && len(items) > 0 {            remove(items[random_int(0, len(items) - 1)].id)        }        if r == 2 {            items = items | sort_by((_) => random())        }        if r == 3 {            tab = tabs[random_int(0, 2)]        }        if r == 4 && len(items) > 0 {            toggle(items[random_int(0, len(items) - 1)].id)        }        if r >= 5 {            text = `change ${changes}`        }        changes += 1    }    if storm {        every(16, shake)    }    <div.storm>        <header>            <h1>StateStorm</h1>            <button.primary @click={() => { storm = !storm }}>{storm ? "Calm" : "Storm"}</button>            <button @click={() => {                storm = false                items = initial_items()                next_id = 12                open = []                tab = "list"                text = "hello"                totals = {}            }}>Reset</button>            <span.stats><b.changes>{changes}</b> changes · <b.count>{len(items)}</b> items · <b.opened>{len(open)}</b> open</span>        </header>        <aside>            {for t in tabs => <button key={t} class={t == tab && "active"} @click={() => { tab = t }}>{t}</button>}            <Where/>        </aside>        <main>            {match tab {                "list" => <ul.items>{for item in items =>                    <Item key={item.id} {item} open={any(open, (o) => o == item.id)} on_toggle={toggle} on_remove={remove}/>}</ul>                "grid" => <div.grid>{for i in range(0, 9) =>                    <Panel key={i} title={`Panel ${i}`}>                        <Ticker label="ticks" ms={40 + i * 10}/>                        <Counter id={i} on_change={counted}/>                        {i == 4 && <Visits/>}                    </Panel>}                </div>                _ => <form.form @submit={(e) => {}}>                    <label>Text <input bind:value={text}/></label>                    <p>{text} ({len(text)} characters)</p>                    <p>Totals: {len(keys(totals))} counters used</p>                </form>            }}        </main>    </div>}let style = css`* { box-sizing: border-box; }body { font: 14px/1.4 system-ui, sans-serif; margin: 0; color: #222; }.storm { display: grid; grid-template-columns: 9rem 1fr; grid-template-rows: auto 1fr; min-height: 100vh; }header { grid-column: 1 / -1; display: flex; gap: .75rem; align-items: center; padding: .6rem 1rem; border-bottom: 1px solid #e5e5e5; }header h1 { font-size: 1.1rem; margin: 0 1rem 0 0; }.stats { margin-left: auto; color: #666; font-variant-numeric: tabular-nums; }aside { display: flex; flex-direction: column; gap: .3rem; padding: 1rem; border-right: 1px solid #eee; }aside .where { margin-top: 1rem; color: #888; font-size: 12px; }main { padding: 1rem; overflow: auto; }button { font: inherit; padding: .25rem .7rem; border-radius: 6px; border: 1px solid #ccc; background: white; cursor: pointer; }button.primary { background: #222; color: white; border-color: #222; min-width: 5rem; }button.active { border-color: #222; font-weight: 600; }button.link { border: none; background: none; padding: 0; min-width: 5.5rem; text-align: left; }.items { list-style: none; padding: 0; margin: 0; columns: 2; }.item { display: flex; gap: .6rem; align-items: center; padding: .3rem .4rem; border-radius: 6px; break-inside: avoid; }.item.open { background: #eef6f0; }.item .remove { margin-left: auto; padding: 0 .45rem; }.muted, .ticker { color: #888; font-size: 12px; font-variant-numeric: tabular-nums; }.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); gap: .75rem; }.panel { border: 1px solid #e5e5e5; border-radius: 8px; padding: .25rem .75rem .75rem; display: flex; flex-direction: column; gap: .4rem; }.panel h3 { font-size: .9rem; margin: .5rem 0 .25rem; color: #555; }.counter { display: flex; gap: .5rem; align-items: center; }.form { display: flex; flex-direction: column; gap: .5rem; max-width: 24rem; }@media (prefers-color-scheme: dark) {  body { background: #161616; color: #ddd; }  header, aside, .panel { border-color: #2a2a2a; }  button { background: #222; color: #ddd; border-color: #444; }  button.primary { background: #ddd; color: #111; }  .item.open { background: #1d2a21; }}`route GET / =    <html>        <head>            <meta charset="utf-8"/>            <meta name="viewport" content="width=device-width, initial-scale=1"/>            <title>StateStorm</title>            <style>{style}</style>        </head>        <body><Storm/></body>    </html>

See storm.qwe on GitHub

Thread
11Useful? Like it, or add to its thread below.

Thread 0 posts

On State storm: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.