import * as React from "react" import * as ReactDOM from "react-dom" import * as Immutable from "immutable" import * as Option from "./option" import {never} from './combinators' import {Route, Url} from './router' export type CmdCommon = { cont:Cont, context:()=>Context, key:string, debug_info:() => string } export type UnitProps = { kind:"unit", value:A } & CmdCommon export type BindProps = { kind:"bind", once:boolean, p:C, k:(_:B) => C, className:string } & CmdCommon export type MapProps = { kind:"map", p:C, f:(_:A)=>B } & CmdCommon export type FilterProps = { kind:"filter", p:C, f:(_:A)=>boolean } & CmdCommon export type ShouldComponentUpdateProps = { kind:"should component update", p:(_:A) => C, f:(_:A)=>boolean, v:A } & CmdCommon export type Mode = "edit"|"view" export type Context = { logic_frame:number, force_reload:(callback?:()=>void) => C current_page:C // pages:Immutable.Stack> set_page:(x:T, new_page:Route, callback?:()=>void) => C set_url:(x:T, new_url:Url, callback?:()=>void) => C push_route:(new_route:Route<{}>, callback?:()=>void) => C set_routes:(routes:Array>, callback?:()=>void) => C // push_page:(new_page:ApplicationPage, callback?:()=>void) => C // pop_page:(callback?:()=>void) => C } export type Cont = (callback:() => void) => (_:A) => void export type C = { comp:(ctxt:() => Context) => (cont:Cont) => JSX.Element then:(key:string, k:(_:A)=>C, className?:string, dbg?:()=>string)=>C // bind_once:(key:string, k:(_:A)=>C, dbg?:()=>string)=>C never:(key?:string)=>C ignore:(key?:string)=>C ignore_with:(x:B)=>C map:(f:(_:A)=>B, key?:string, dbg?:()=>string)=>C, filter:(f:(_:A)=>boolean, key?:string, dbg?:()=>string)=>C } export function make_C(comp:(ctxt:()=>Context) => (cont:Cont) => JSX.Element) : C { return { comp:comp, then:function(this:C, key:string, k:(_:A)=>C, className?:string, dbg?:()=>string) : C { return bind(key, this, k, className, dbg) }, map:function(this:C, f:(_:A)=>B, key?:string, dbg?:()=>string) : C { return map(key, dbg)(f)(this) }, filter:function(this:C, f:(_:A)=>boolean, key?:string, dbg?:()=>string) : C { return filter(key, dbg)(f)(this) }, // bind_once:function(this:C, key:string, k:(_:A)=>C, dbg?:()=>string) : C { // return bind_once(key, this, k, dbg) // }, never:function(this:C, key?:string) : C { return never(this, key) }, ignore_with:function(this:C, x:B) : C { return this.then(``, _ => unit(x)) }, ignore:function(this:C, key?:string) : C { return this.then(key, _ => unit(null)) } } } type UnitState = {} class Unit extends React.Component,UnitState> { constructor(props:UnitProps,context:any) { super(props, context) this.state = {} } componentWillReceiveProps(new_props:UnitProps) { new_props.debug_info && console.log("New props:", new_props.debug_info(), new_props.value) new_props.cont(() => {})(new_props.value) } componentWillMount() { this.props.debug_info && console.log("Component will mount:", this.props.debug_info(), this.props.value) this.props.cont(() => {})(this.props.value) } render(): JSX.Element[] { this.props.debug_info && console.log("Render:", this.props.debug_info()) return [] } } export let unit = function(x:A, key?:string, dbg?:() => string) : C { return make_C(ctxt => cont => (React.createElement>(Unit, { kind:"unit", debug_info:dbg, value:x, context:ctxt, cont:cont, key:key }))) } export type JoinProps = { p:C> } & CmdCommon export type JoinState = { p_inner:"waiting"|JSX.Element, p_outer:JSX.Element } class Join extends React.Component,JoinState> { constructor(props:JoinProps,context:any) { super(props, context) this.state = { p_inner:"waiting", p_outer:props.p.comp(props.context)(cont => p_inner => this.setState({...this.state, p_inner:p_inner.comp(this.props.context)(cb => x => this.props.cont(cb)(x))})) } } componentWillReceiveProps(new_props:JoinProps) { new_props.debug_info && console.log("New join props:", new_props.debug_info()) this.setState({ p_outer:new_props.p.comp(new_props.context)(cont => p_inner => this.setState({...this.state, p_inner:p_inner.comp(new_props.context)(cb => x => new_props.cont(cb)(x))})) }) } render() { return
{ this.state.p_outer } { this.state.p_inner == "waiting" ? [] : this.state.p_inner }
} } let join = function
(p:C>, key?:string, dbg?:() => string) : C { return make_C(ctxt => cont => React.createElement>(Join, { p:p, context:ctxt, cont: cont, debug_info:dbg, key:key })) } type BindState = { k:"waiting for p"|JSX.Element, p:"creating"|JSX.Element } class Bind extends React.Component,BindState> { constructor(props:BindProps,context:any) { super(props, context) this.state = { k:"waiting for p", p:"creating" } } componentWillReceiveProps(new_props:BindProps) { this.props.debug_info && console.log("New props:", this.props.debug_info()) if (this.props.once) this.setState({...this.state, p:"creating" }) else this.setState({...this.state, p:new_props.p.comp(new_props.context)(callback => x => this.setState({...this.state, k:new_props.k(x).comp(new_props.context)(callback => x => new_props.cont(callback)(x))}, callback) )}) } componentWillMount() { this.setState({...this.state, p:this.props.p.comp(this.props.context)(callback => x => this.setState({...this.state, k:this.props.k(x).comp(this.props.context)(callback => x => this.props.cont(callback)(x))}, callback) )}) } render() { this.props.debug_info && console.log("Render:", this.props.debug_info()) return
{ (this.state.k == "waiting for p" || !this.props.once) && this.state.p != "creating" ? this.state.p : [] } { this.state.k != "waiting for p" ? this.state.k : [] }
} } export let bind = function(key:string, p:C
, k:((_:A)=>C), className?:string, dbg?:() => string) : C { let q = p.map(k, `${key}_map`, dbg); return join(q, `${key}_join`, dbg); // return make_C(ctxt => cont => // (React.createElement>(Bind, // { kind:"bind", debug_info:dbg, p:p, k:k, once:false, cont:cont, context:ctxt, key:key, className:className }))) } type MapState = { p:"creating"|JSX.Element } class Map extends React.Component,MapState> { constructor(props:MapProps,context:any) { super(props, context) this.state = { p:"creating" } } componentWillReceiveProps(new_props:MapProps) { this.props.debug_info && console.log("New props:", this.props.debug_info()) this.setState({...this.state, p:new_props.p.comp(new_props.context)(callback => x => new_props.cont(callback)(new_props.f(x)))}) } componentWillMount() { this.setState({...this.state, p:this.props.p.comp(this.props.context)(callback => x => this.props.cont(callback)(this.props.f(x)))}) } render() { this.props.debug_info && console.log("Render:", this.props.debug_info()) return this.state.p != "creating" ? this.state.p : [] } } export let map = function(key?:string, dbg?:() => string) : ((_:(_:A) => B) => (_:C) => C) { return f => p => make_C(ctxt => cont => React.createElement>(Map, { kind:"map", debug_info:dbg, p:p, f:f, context:ctxt, cont:cont, key:key })) } type FilterState = { p:"creating"|JSX.Element } class Filter extends React.Component,FilterState> { constructor(props:FilterProps,context:any) { super(props, context) this.state = { p:"creating" } } componentWillReceiveProps(new_props:FilterProps) { this.props.debug_info && console.log("New props:", this.props.debug_info()) this.setState({...this.state, p:new_props.p.comp(new_props.context)(callback => x => { if (new_props.f(x)) { new_props.cont(callback)(x) } })}) } componentWillMount() { this.setState({...this.state, p:this.props.p.comp(this.props.context)(callback => x => { if (this.props.f(x)) { this.props.cont(callback)(x) } })}) } render() { this.props.debug_info && console.log("Render:", this.props.debug_info()) return this.state.p != "creating" ? this.state.p : [] } } export let filter = function(key?:string, dbg?:() => string) : ((_:(_:A) => boolean) => (_:C) => C) { return f => p => make_C(ctxt => cont => React.createElement>(Filter, { kind:"filter", debug_info:dbg, p:p, f:f, context:ctxt, cont:cont, key:key })) } type ShouldComponentUpdateState = { } class ShouldComponentUpdate extends React.Component,ShouldComponentUpdateState> { constructor(props:ShouldComponentUpdateProps,context:any) { super(props, context) } shouldComponentUpdate(next_props:ShouldComponentUpdateProps) : boolean { return next_props.f(next_props.v) } componentWillReceiveProps(next_props:ShouldComponentUpdateProps) { this.props.debug_info && console.log("New props:", this.props.debug_info()) } render() { this.props.debug_info && console.log("Render:", this.props.debug_info()) // let s = this.props.v as any // console.log(`rendering should component update with props ${this.props.debug_info()}`, JSON.stringify(s)) return this.props.p(this.props.v).comp(this.props.context)(cbk => y => this.props.cont(cbk)(y)) } } export let should_component_update = function(key?:string, dbg?:() => string) : ((_:(_:A) => boolean) => (_:(_:A) => C) => (_:A) => C) { return f => p => v => make_C(ctxt => cont => React.createElement>(ShouldComponentUpdate, { kind:"should component update", debug_info:dbg, p:p, f:f, context:ctxt, cont:cont, key:key, v:v })) } export type SimpleApplicationProps = { p:C, cont:(_:A)=>void } export type SimpleApplicationState = { context:Context } export class SimpleApplication extends React.Component, SimpleApplicationState> { constructor(props:SimpleApplicationProps, context:any) { super(props, context) this.state = { context:this.context_from_props(this.props, unit(null)) } } context_from_props(props:SimpleApplicationProps, p:C) : Context { let self = this return { current_page:p, logic_frame:0, force_reload:(callback) => make_C(ctxt => inner_callback => this.setState({...this.state, context:{...this.state.context, logic_frame:this.state.context.logic_frame+1}}, () => inner_callback(callback)(null)) || null), set_page:function(x:T, new_page:Route, callback?:()=>void) { return unit(null) }, set_url:function(x:T, new_url:Url, callback?:()=>void) { return unit(null) }, push_route:function(route, callback?:()=>void) { return unit(null) }, set_routes:function(routes, callback?:()=>void) { return unit(null) } } } render() { return
{ this.props.p.comp(() => this.state.context)(callback => x => this.props.cont(x)) }
} } export let simple_application = function
(p:C, cont:(_:A)=>void) : JSX.Element { return React.createElement>(SimpleApplication, { p:p, cont:cont }) }