1
Fork 0

typescript(monadic): feat: basics of outputs

Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
Matei Adriel 2020-05-12 10:45:48 +03:00 committed by prescientmoon
parent 9e6c150e5f
commit 9e3da82f0d
Signed by: prescientmoon
SSH key fingerprint: SHA256:UUF9JT2s8Xfyv76b8ZuVL7XrmimH4o49p4b+iexbVH4
5 changed files with 67 additions and 33 deletions

View file

@ -9,7 +9,7 @@ type TodoState = {
type TodoAction = 'complete'; type TodoAction = 'complete';
const todo = makeComponent( const todo = makeComponent(
({ name, done }: TodoState, dispatch: Dispatcher<TodoAction>) => { ({ name, done }: TodoState, { dispatch }) => {
return html` return html`
<div> <div>
Name: ${name} Completed: ${done} Name: ${name} Completed: ${done}

View file

@ -40,6 +40,7 @@
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },
"dependencies": { "dependencies": {
"fp-ts": "^2.6.0",
"helpers": "^0.0.6" "helpers": "^0.0.6"
} }
} }

View file

@ -1,25 +1,36 @@
import { mapRecord } from './Record'; import { mapRecord } from './Record';
import { values } from './helpers'; import { values } from './helpers';
import O from 'fp-ts/es6/Option';
import { constant } from 'fp-ts/es6/function';
/** /**
* Helper type for dispatcher functions * Helper type for dispatcher functions
*/ */
export type Dispatcher<A> = (action: A) => void; export type Dispatcher<A> = (action: A) => void;
export type Communicate<
T,
A,
O,
N extends string,
C extends ChildrenConfigs<N>
> = {
dispatch: Dispatcher<A>;
child: <K extends N>(key: K, name: string, input: C[K]['input']) => T;
raise: (event: O) => void;
};
export type ComponentConfig< export type ComponentConfig<
T, T,
S, S,
A, A,
O,
N extends string, N extends string,
C extends ChildrenConfigs<N> C extends ChildrenConfigs<N>
> = { > = {
render: ( render: (state: S, communicate: Communicate<T, A, O, N, C>) => T;
state: S,
dispatch: Dispatcher<A>,
child: <K extends N>(key: K, name: string, input: C[K]['input']) => T
) => T;
handleAction: (action: A, state: S) => S; handleAction: (action: A, state: S) => S;
children: ChildrenTemplates<T, S, N, C>; children: ChildrenTemplates<T, S, A, O, N, C>;
}; };
type GenericLens<T, U> = { type GenericLens<T, U> = {
@ -27,17 +38,26 @@ type GenericLens<T, U> = {
set: (v: T, n: U) => T; set: (v: T, n: U) => T;
}; };
type Child<I = unknown, S = unknown> = { type Child<I = unknown, S = unknown, O = unknown> = {
input: I; input: I;
state: S; state: S;
output: O;
}; };
type ChildrenConfigs<N extends string> = Record<N, Child>; type ChildrenConfigs<N extends string> = Record<N, Child>;
type ChildrenTemplates<T, S, N extends string, C extends ChildrenConfigs<N>> = { type ChildrenTemplates<
T,
S,
A,
O,
N extends string,
C extends ChildrenConfigs<N>
> = {
[K in N]: { [K in N]: {
lens: (input: C[K]['input']) => GenericLens<S, C[K]['state']>; lens: (input: C[K]['input']) => GenericLens<S, C[K]['state']>;
component: ComponentConfig<T, C[K]['state'], unknown, string, {}>; component: ComponentConfig<T, C[K]['state'], O, unknown, string, {}>;
handleOutput: (input: C[K]['input'], output: C[K]['output']) => O.Option<A>;
}; };
}; };
@ -45,7 +65,14 @@ type Children<T, S, N extends string, C extends ChildrenConfigs<N>> = {
[K in N]: Record< [K in N]: Record<
string, string,
{ {
component: Component<T, C[K]['state'], unknown, string, {}>; component: Component<
T,
C[K]['state'],
unknown,
C[K]['output'],
string,
{}
>;
lens: GenericLens<S, C[K]['state']>; lens: GenericLens<S, C[K]['state']>;
} }
>; >;
@ -55,16 +82,17 @@ export class Component<
T, T,
S, S,
A, A,
O,
N extends string, N extends string,
C extends ChildrenConfigs<N> C extends ChildrenConfigs<N>
> { > {
private childrenMap: Children<T, S, N, C>; public childrenMap: Children<T, S, N, C>;
public constructor( public constructor(
protected state: S, protected state: S,
private config: ComponentConfig<T, S, A, N, C>, private config: ComponentConfig<T, S, A, O, N, C>,
private pushDownwards: (state: S) => void private pushDownwards: (state: S) => void
) { ) {
this.childrenMap = mapRecord(this.config.children, () => ({})); this.childrenMap = mapRecord(this.config.children, constant({}));
} }
protected pushStateUpwards(state: S) { protected pushStateUpwards(state: S) {
@ -76,10 +104,10 @@ export class Component<
} }
public getTemplate(): T { public getTemplate(): T {
return this.config.render( return this.config.render(this.state, {
this.state, dispatch: value => this.dispatch(value),
value => this.dispatch(value), raise: _ => undefined,
(key, name, input) => { child: (key, name, input) => {
const hasName = Reflect.has(this.childrenMap[key], name); const hasName = Reflect.has(this.childrenMap[key], name);
if (!hasName) { if (!hasName) {
@ -97,8 +125,8 @@ export class Component<
} }
return this.childrenMap[key][name].component.getTemplate(); return this.childrenMap[key][name].component.getTemplate();
} },
); });
} }
public dispatch(action: A) { public dispatch(action: A) {
@ -111,10 +139,8 @@ export class Component<
/** /**
* Get a list of all the children of the component * Get a list of all the children of the component
*/ */
private children() { private children(): this['childrenMap'][N][string][] {
return values(this.childrenMap).flatMap(record => return values(this.childrenMap).flatMap(record => values(record)) as any;
values(record)
) as Children<T, S, N, C>[N][string][];
} }
} }
@ -129,15 +155,17 @@ export const makeComponent = <
T, T,
S, S,
A, A,
O,
N extends string, N extends string,
C extends ChildrenConfigs<N> C extends ChildrenConfigs<N>
>( >(
render: ComponentConfig<T, S, A, N, C>['render'], render: ComponentConfig<T, S, A, O, N, C>['render'],
handleAction: ComponentConfig<T, S, A, N, C>['handleAction'], handleAction: ComponentConfig<T, S, O, A, N, C>['handleAction'],
children: ComponentConfig<T, S, A, N, C>['children'] children: ComponentConfig<T, S, A, O, N, C>['children']
) => ({ render, handleAction, children }); ) => ({ render, handleAction, children });
export const mkChild = <T, PS, I, S>( export const mkChild = <T, PS, A, I, S, O>(
lens: (input: I) => GenericLens<PS, S>, lens: (input: I) => GenericLens<PS, S>,
component: ComponentConfig<T, S, unknown, string, {}> component: ComponentConfig<T, S, A, O, string, {}>,
) => ({ lens, component }); handleOutput: (input: I, output: O) => O.Option<A> = constant(O.none)
) => ({ lens, component, handleOutput });

View file

@ -1,13 +1,13 @@
import { ComponentConfig, Component } from './Component'; import { ComponentConfig, Component } from './Component';
export type EnvConfig<T, S, A> = { export type EnvConfig<T, S, A, O> = {
render: (template: T, parent: HTMLElement) => void; render: (template: T, parent: HTMLElement) => void;
parent: HTMLElement; parent: HTMLElement;
component: ComponentConfig<T, S, A, string, {}>; component: ComponentConfig<T, S, A, O, string, {}>;
initialState: S; initialState: S;
}; };
export const runUi = <T, S, A>(config: EnvConfig<T, S, A>) => { export const runUi = <T, S, A, O>(config: EnvConfig<T, S, A, O>) => {
const reRender = () => config.render(component.getTemplate(), config.parent); const reRender = () => config.render(component.getTemplate(), config.parent);
const component = new Component(config.initialState, config.component, _ => { const component = new Component(config.initialState, config.component, _ => {

View file

@ -3448,6 +3448,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6" combined-stream "^1.0.6"
mime-types "^2.1.12" mime-types "^2.1.12"
fp-ts@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.6.0.tgz#55dc9e82612a44a7bfed3484b18c535514d63e51"
integrity sha512-4EKVa3EOP3NoDwXCLgSCT2t+E2wPCWDXCuj3wEcQ1ovkLfdCMxKxCuElpXptIPBmtA+KbjnUl5Ta/1U3jsCmkg==
fragment-cache@^0.2.1: fragment-cache@^0.2.1:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"