Skip to content

Commit 46ddf39

Browse files
Merge branch 'develop' into feature/hiccup-svg-deref-attribs
* develop: (38 commits) Publish feat(csp): update Mult/PubSub unsub handling, add docs minor(csp): rename MAX_QUEUE => MAX_WRITES docs(csp): add various doc strings fix(csp): update select() docs(csp): add docs for all Channel ops docs(csp): update readme example docs(csp): update/extend readme (update example) docs(csp): update/extend readme Publish docs: update main readme docs: regen readmes docs(csp): update readme (example, doc links) refactor(examples): update csp-bus feat(csp): add into() to feed (async) iterables into a channel feat(meta-css): add color-scheme, light-dark() and appearance rules/tpls fix(rdom): update $compile() async-iterable attrib handling feat(csp): add opt. generics for PubSub.subscribe()/unsubscribe() feat(examples): add csp-bus example docs(examples): update table ...
2 parents 0ad9289 + 85861eb commit 46ddf39

File tree

190 files changed

+2819
-2663
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

190 files changed

+2819
-2663
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424
node-version: ">=20.0.0"
2525
cache: "yarn"
2626
- uses: goto-bus-stop/setup-zig@v2
27+
with:
28+
version: 0.12.0
2729
- uses: oven-sh/setup-bun@v1
2830
with:
2931
bun-version: latest

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,16 @@ feature or `develop` branches)
255255

256256
### Latest updates
257257

258-
As of: 2024-04-23
258+
As of: 2024-04-26
259259

260260
| Status | Package | Version | Changelog |
261261
|:-------------------------------------------------|:----------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------|
262+
| ![](https://img.shields.io/badge/-feat-green) | [`@thi.ng/csp`](./packages/csp) | [![version](https://img.shields.io/npm/v/@thi.ng/csp.svg)](https://www.npmjs.com/package/@thi.ng/csp) | [changelog](./packages/csp/CHANGELOG.md) |
262263
| ![](https://img.shields.io/badge/-feat-green) | [`@thi.ng/file-io`](./packages/file-io) | [![version](https://img.shields.io/npm/v/@thi.ng/file-io.svg)](https://www.npmjs.com/package/@thi.ng/file-io) | [changelog](./packages/file-io/CHANGELOG.md) |
264+
| ![](https://img.shields.io/badge/-feat-green) | [`@thi.ng/hiccup`](./packages/hiccup) | [![version](https://img.shields.io/npm/v/@thi.ng/hiccup.svg)](https://www.npmjs.com/package/@thi.ng/hiccup) | [changelog](./packages/hiccup/CHANGELOG.md) |
265+
| ![](https://img.shields.io/badge/-feat-green) | [`@thi.ng/meta-css`](./packages/meta-css) | [![version](https://img.shields.io/npm/v/@thi.ng/meta-css.svg)](https://www.npmjs.com/package/@thi.ng/meta-css) | [changelog](./packages/meta-css/CHANGELOG.md) |
266+
| ![](https://img.shields.io/badge/-fix-orange) | [`@thi.ng/rdom`](./packages/rdom) | [![version](https://img.shields.io/npm/v/@thi.ng/rdom.svg)](https://www.npmjs.com/package/@thi.ng/rdom) | [changelog](./packages/rdom/CHANGELOG.md) |
267+
| ![](https://img.shields.io/badge/-refactor-cyan) | [`@thi.ng/rstream-csp`](./packages/rstream-csp) | [![version](https://img.shields.io/npm/v/@thi.ng/rstream-csp.svg)](https://www.npmjs.com/package/@thi.ng/rstream-csp) | [changelog](./packages/rstream-csp/CHANGELOG.md) |
263268
| ![](https://img.shields.io/badge/-feat-green) | [`@thi.ng/wasm-api`](./packages/wasm-api) | [![version](https://img.shields.io/npm/v/@thi.ng/wasm-api.svg)](https://www.npmjs.com/package/@thi.ng/wasm-api) | [changelog](./packages/wasm-api/CHANGELOG.md) |
264269
| ![](https://img.shields.io/badge/-feat-green) | [`@thi.ng/wasm-api-bindgen`](./packages/wasm-api-bindgen) | [![version](https://img.shields.io/npm/v/@thi.ng/wasm-api-bindgen.svg)](https://www.npmjs.com/package/@thi.ng/wasm-api-bindgen) | [changelog](./packages/wasm-api-bindgen/CHANGELOG.md) |
265270
| ![](https://img.shields.io/badge/-refactor-cyan) | [`@thi.ng/wasm-api-dom`](./packages/wasm-api-dom) | [![version](https://img.shields.io/npm/v/@thi.ng/wasm-api-dom.svg)](https://www.npmjs.com/package/@thi.ng/wasm-api-dom) | [changelog](./packages/wasm-api-dom/CHANGELOG.md) |

assets/examples/csp-bus.png

66.3 KB
Loading

examples/README.md

Lines changed: 145 additions & 143 deletions
Large diffs are not rendered by default.

examples/csp-bus/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# csp-bus
2+
3+
![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/csp-bus.png)
4+
5+
[Live demo](http://demo.thi.ng/umbrella/csp-bus/)
6+
7+
## Developing & building
8+
9+
Please refer to the instructions on the wiki:
10+
11+
- [Development](https://github.com/thi-ng/umbrella/wiki/Development-mode-for-examples-using-thi.ng-meta%E2%80%90css)
12+
- [Production build](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions)
13+
14+
## Authors
15+
16+
- Karsten Schmidt
17+
18+
## License
19+
20+
© 2024 Karsten Schmidt // Apache Software License 2.0

examples/csp-bus/css/custom.mcss.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"tables": {},
3+
"vars": {},
4+
"decls": [],
5+
"specs": [],
6+
"templates": []
7+
}

examples/csp-bus/css/includes.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// list of CSS class names to force-include in generated CSS
2+
// (one class per line, basic wildcards supported)

examples/csp-bus/css/style.mcss

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// thi.ng/meta-css stylesheet
2+
// see package readme for more details/usage
3+
// use `yarn css:build` or `yarn css:watch` to transpile to CSS
4+
// also see component-specific *.mcss files in /src folder
5+
6+
:root {
7+
color-scheme-light-dark
8+
bg=#fff
9+
text=#000
10+
log=#777
11+
dark:bg=#000
12+
dark:text=#fff
13+
dark:log=#999
14+
}
15+
16+
body { system-sans-serif ma3 bg-color(bg) color(text) }
17+
18+
textarea[disabled] { w-100 pa2 bw0 bg-color(bg) color(log) }
19+
20+
button.stop { db w-100 bg-color-orange color-white h2 bw0 mb3 cursor-pointer }
21+
22+
a.link { color(text) }

examples/csp-bus/index.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<link
5+
rel="icon"
6+
href='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">⛱️</text></svg>'
7+
/>
8+
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
9+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
10+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
11+
<title>csp-bus · @thi.ng/umbrella</title>
12+
<link href="/css/style.css" rel="stylesheet">
13+
<script>window.goatcounter = { path: (p) => location.host + p };</script>
14+
<script data-goatcounter="https://thing.goatcounter.com/count" async src="//gc.zgo.at/count.js"></script>
15+
</head>
16+
<body>
17+
<div id="app"></div>
18+
<div><a class="link" href="https://github.com/thi-ng/umbrella/tree/develop/examples/csp-bus">Source code</a></div>
19+
<script type="module" src="/src/index.ts"></script>
20+
</body>
21+
</html>

examples/csp-bus/package.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "@example/csp-bus",
3+
"version": "0.0.1",
4+
"private": true,
5+
"description": "CSP channel-based event handling, async transducers & reactive UI components",
6+
"repository": "https://github.com/thi-ng/umbrella",
7+
"author": "Karsten Schmidt <[email protected]>",
8+
"license": "Apache-2.0",
9+
"scripts": {
10+
"start": "yarn css:build && yarn start:only",
11+
"start:only": "vite --host --open",
12+
"css:watch": "../../node_modules/.bin/metacss develop --bundle --watch --pretty --out-specs css/framework.json --out-css css/style.css --force @css/includes.txt ../../packages/meta-css/specs/*.mcss.json css/*.mcss.json css/*.mcss src/*.mcss",
13+
"css:build": "../../node_modules/.bin/metacss develop --bundle --out-specs css/framework.json --out-css css/style.css --force @css/includes.txt ../../packages/meta-css/specs/*.mcss.json css/*.mcss.json css/*.mcss src/*.mcss",
14+
"build": "yarn css:build && tsc && vite build --base='./'",
15+
"preview": "vite preview --host --open"
16+
},
17+
"devDependencies": {
18+
"@thi.ng/meta-css": "workspace:^",
19+
"typescript": "^5.4.3",
20+
"vite": "^5.2.6"
21+
},
22+
"dependencies": {
23+
"@thi.ng/api": "workspace:^",
24+
"@thi.ng/csp": "workspace:^",
25+
"@thi.ng/date": "workspace:^",
26+
"@thi.ng/hiccup-html": "workspace:^",
27+
"@thi.ng/rdom": "workspace:^",
28+
"@thi.ng/system": "workspace:^",
29+
"@thi.ng/transducers": "workspace:^",
30+
"@thi.ng/transducers-async": "workspace:^"
31+
},
32+
"browser": {
33+
"process": false
34+
},
35+
"thi.ng": {
36+
"readme": [
37+
"csp",
38+
"date",
39+
"hiccup-html",
40+
"rdom",
41+
"system",
42+
"transducers-async"
43+
],
44+
"screenshot": "examples/csp-bus.png"
45+
}
46+
}

examples/csp-bus/src/api.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { Channel, PubSub } from "@thi.ng/csp";
2+
import type { ILifecycle } from "@thi.ng/system";
3+
import type { Counter, CounterGroup } from "./counter.js";
4+
5+
export type StartCounterEvent = ["start-counter", Counter];
6+
export type StopAllEvent = ["stop"];
7+
export type LogEvent = ["log", string];
8+
9+
export type Event = StartCounterEvent | StopAllEvent | LogEvent;
10+
11+
export type EventBus = PubSub<Event>;
12+
13+
// our app consists of multiple app components
14+
export interface App {
15+
bus: EventBus;
16+
counters: CounterGroup;
17+
logger: Channel<string>;
18+
ui: ILifecycle;
19+
}

examples/csp-bus/src/counter.mcss

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// put component specific thi.ng/meta-css style defs here...
2+
// these will be included in the generated CSS via the `css:build`
3+
// and `css:watch` script aliases in `package.json`
4+
5+
.counter {
6+
bg=#ccc
7+
text=#333
8+
bt-bg=#000
9+
bt-text=#fff
10+
bt-bg-disabled=#aaa
11+
bt-text-disabled=#ccc
12+
prog-bg=#99a
13+
prog-val=#d5008f
14+
15+
dark:bg=#333
16+
dark:text=#ccc
17+
dark:bt-bg=#fff
18+
dark:bt-text=#000
19+
dark:bt-bg-disabled=#555
20+
dark:bt-text-disabled=#333
21+
dark:prog-bg=#556
22+
dark:prog-val=#ff41b4
23+
24+
pa2 mb2 grid grid-cols(4rem 1fr 4rem)
25+
bg-color(bg) color(text)
26+
{
27+
button {
28+
bg-color(bt-bg) color(bt-text) bw0 h2 pa2 mr3 cursor-pointer
29+
{ &[disabled] { bg-color(bt-bg-disabled) color(bt-text-disabled) cursor-auto } }
30+
}
31+
progress {
32+
w-100 h2 ma0 pa0 mr3 bw0 appearance(none) bg-color(prog-bg)
33+
{
34+
[value]::-webkit-progress-value { bg-color(prog-val) }
35+
[value]::-webkit-progress-bar { bg-color(prog-bg) }
36+
[value]::-moz-progress-bar { bg-color(prog-val) }
37+
}
38+
}
39+
>span { mt2 tr }
40+
}
41+
}

examples/csp-bus/src/counter.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { Maybe } from "@thi.ng/api";
2+
import { Channel, Mult, channel, mult } from "@thi.ng/csp";
3+
import { button, div, progress } from "@thi.ng/hiccup-html";
4+
import { Component, type NumOrElement } from "@thi.ng/rdom";
5+
import { map } from "@thi.ng/transducers-async";
6+
import type { EventBus } from "./api.js";
7+
8+
// counter component with local state in the form of CSP channels
9+
export class Counter extends Component {
10+
value: Mult<number>;
11+
disabled: Channel<boolean>;
12+
13+
constructor(public bus: EventBus, public delay: number, public id: number) {
14+
super();
15+
this.value = mult<any>();
16+
this.disabled = channel<boolean>();
17+
this.value.write(0);
18+
this.disabled.write(false);
19+
}
20+
21+
// thi.ng/rdom component lifecycle hook
22+
// called when this component is mounted in the browser DOM
23+
async mount(parent: ParentNode, index?: Maybe<NumOrElement>) {
24+
return (this.el = await this.$compile(
25+
div(
26+
".counter",
27+
{},
28+
button(
29+
{
30+
disabled: this.disabled,
31+
onclick: () => this.bus.write(["start-counter", this]),
32+
},
33+
"start"
34+
),
35+
progress({
36+
max: 100,
37+
value: this.value.subscribe(),
38+
}),
39+
map((x) => `${x}%`, this.value.subscribe())
40+
)
41+
).mount(parent, index));
42+
}
43+
}
44+
45+
// simple wrapper component for multiple counters (configurable number)
46+
export class CounterGroup extends Component {
47+
constructor(public config: number[], public bus: EventBus) {
48+
super();
49+
}
50+
51+
async mount(parent: ParentNode, index?: Maybe<NumOrElement>) {
52+
return (this.el = await this.$compile(
53+
div(
54+
{},
55+
...this.config.map(
56+
(delay, i) => new Counter(this.bus, delay, i + 1)
57+
)
58+
)
59+
).mount(parent, index));
60+
}
61+
}
62+
63+
// syntax sugar for CounterGroup ctor
64+
export const initCounters = async (config: number[], bus: EventBus) =>
65+
new CounterGroup(config, bus);

examples/csp-bus/src/events.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { consumeWith, into, pubsub } from "@thi.ng/csp";
2+
import { FMT_HHmmss, FMT_yyyyMMdd } from "@thi.ng/date";
3+
import { concat, delayed, range } from "@thi.ng/transducers-async";
4+
import type {
5+
App,
6+
Event,
7+
EventBus,
8+
LogEvent,
9+
StartCounterEvent,
10+
StopAllEvent,
11+
} from "./api";
12+
13+
export const initEvents = async ({ logger }: App) => {
14+
// the event bus is a simple pubsub CSP channel construct
15+
// events are triggered by writing a new value in this channel
16+
17+
// event handlers (below) are subscribing to specific topics (via filtered
18+
// topic based CSP channels) and each handler is running in its own channel
19+
// consumer... the topic function used here simply extracts the event name
20+
// from incoming event tuples (see event definition in /api.ts)
21+
const bus: EventBus = pubsub<Event>((x) => x[0]);
22+
23+
// async event handler & channel consumer to handle counter events in the
24+
// form: `["start-counter", counterInstance]`
25+
consumeWith(
26+
// create a topic subscription channel for this specific event type
27+
bus.subscribeTopic<StartCounterEvent>("start-counter"),
28+
// this function is called for side effects of each counter event received,
29+
// we make it async here to simplify animating the counter
30+
async ([_, { value, disabled, id, delay }]) => {
31+
// we *could* use the logger directly here, but instead utilize the
32+
// bus for demo purposes and trigger a logging event (its handler is
33+
// further below)
34+
bus.write(["log", `starting counter #${id}`]);
35+
// temporarily disable the counter button
36+
disabled.write(true);
37+
// animate by feeding an async iterable into the counter's value channel
38+
await into(
39+
value,
40+
// concatenate multiple async iterables to animate value
41+
concat(
42+
// count [0..100]
43+
range(101, delay),
44+
// short wait
45+
(async function* () {
46+
yield await delayed(100, 500);
47+
})(),
48+
// (faster) countdown to 0
49+
range(100, -1, -10, 10)
50+
)
51+
);
52+
// re-enable counter button
53+
disabled.write(false);
54+
// another logging event
55+
bus.write(["log", `counter #${id} done`]);
56+
}
57+
);
58+
59+
// stop the entire event bus if the `stop` event has been triggered. once
60+
// the bus channel is closed, all further events will be ignored and all
61+
// topic subscriptions should have been closed too (automatically)
62+
consumeWith(bus.subscribeTopic<StopAllEvent>("stop"), async () => {
63+
await bus.write(["log", "stopping event bus..."]);
64+
bus.close();
65+
});
66+
67+
// to avoid direct dependencies on the logger in other parts of the app
68+
// enable logging via sending events to the bus
69+
consumeWith(bus.subscribeTopic<LogEvent>("log"), ([_, msg]) =>
70+
logger.write(`${FMT_yyyyMMdd()} ${FMT_HHmmss()}: ${msg}`)
71+
);
72+
73+
bus.write(["log", "eventbus ready..."]);
74+
75+
return bus;
76+
};

examples/csp-bus/src/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { defSystem } from "@thi.ng/system";
2+
import type { App } from "./api.js";
3+
import { initCounters } from "./counter.js";
4+
import { initEvents } from "./events.js";
5+
import { initLogger } from "./logger.js";
6+
import { initUI } from "./ui.js";
7+
8+
// initialize the app components in dependency order
9+
// see thi.ng/system readme for details
10+
defSystem<App>({
11+
logger: { factory: initLogger },
12+
bus: { factory: initEvents, deps: ["logger"] },
13+
ui: { factory: initUI, deps: ["bus", "counters", "logger"] },
14+
counters: {
15+
factory: async ({ bus }) => initCounters([50, 10, 25], bus),
16+
deps: ["bus"],
17+
},
18+
}).start();

examples/csp-bus/src/logger.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { channel } from "@thi.ng/csp";
2+
3+
// app logger component is a simple CSP channel
4+
export const initLogger = async () => channel<string>();

0 commit comments

Comments
 (0)