import * as React from "react" import * as ReactDOM from "react-dom" import * as Immutable from "immutable" import * as i18next from 'i18next' import {C, Mode, Cont, CmdCommon, Context, make_C, unit, bind} from './core' import {div, a} from './html' import {bool} from './primitives' export type RepeatProps = { kind:"repeat", value:A, p:(_:A)=>C } & CmdCommon export type AllProps = { kind:"all", ps:Array> } & CmdCommon> export type AnyProps = { kind:"any", value:A, ps:Array<(_:A)=>C>, className:string } & CmdCommon export type NeverProps = { kind:"never", p:C } & CmdCommon export type RetractProps = { kind:"retract", inb:(_:A)=>B, out:(_:A)=>(_:B)=>A, p:(_:B)=>C, value:A } & CmdCommon export type DelayProps = { kind:"delay", dt:number, value:A, p:(_:A)=>C } & CmdCommon export type WaitProps = { kind:"wait", dt:number, value:A, p:(_:A)=>C } & CmdCommon export type RetryStrategy = "never" | "semi exponential" | { kind:"retry then show failure", times:number, on_failure: C } | { kind : "never" , on_failure: C } export type LiftPromiseProps = { kind:"lift promise", p:(_:B)=>Promise, retry_strategy:RetryStrategy, value:B } & CmdCommon export type SimpleMenuType = "side menu" | { kind:"tabs", max_tabs:number } type RepeatState = { current_value:A, frame_index:number } class Repeat extends React.Component,RepeatState> { constructor(props:RepeatProps,context:any) { super(props, context) this.state = { current_value: props.value, frame_index:1 } } stopped:boolean = false componentWillUnmount() { this.stopped = true } componentWillMount() { this.stopped = false } render() { this.props.debug_info && console.log("Render:", this.props.debug_info(), this.state.current_value) return this.props.p(this.state.current_value).comp(this.props.context)(callback => new_value => { if (this.stopped) return return this.setState({...this.state, frame_index:this.state.frame_index+1, current_value:new_value}, () => this.props.cont(callback)(new_value)) }) } } export let repeat = function(key?:string, dbg?:() => string) : ((p:(_:A)=>C) => (_:A) => C) { return p => initial_value => make_C(ctxt => cont => React.createElement>(Repeat, ({ kind:"repeat", debug_info:dbg, p:p as (_:A)=>C, value:initial_value, context:ctxt, cont:cont, key:key }))) } type AnyState = { ps:"creating"|Array } class Any extends React.Component,AnyState> { constructor(props:AnyProps,context:any) { super(props, context) this.state = { ps:"creating" } } componentWillReceiveProps(new_props:AnyProps) { this.setState({...this.state, ps:new_props.ps.map(p => p(new_props.value).comp(new_props.context)(callback => new_value => new_props.cont(callback)(new_value)))}) } componentWillMount() { this.setState({...this.state, ps:this.props.ps.map(p => p(this.props.value).comp(this.props.context)(callback => new_value => this.props.cont(callback)(new_value)))}) } render() { return
{ this.state.ps != "creating" ? this.state.ps : null }
} } export let any = function(key?:string, className?:string, dbg?:() => string) : ((ps:Array<(_:A)=>C>) => (_:A) => C) { return ps => initial_value => make_C(ctxt => cont => React.createElement>(Any, { kind:"any", debug_info:dbg, ps:ps, value:initial_value, context:ctxt, cont:cont, key:key, className:className })) } type NeverState = { p:"loading"|JSX.Element } class Never extends React.Component,NeverState> { constructor(props:NeverProps,context:any) { super(props, context) this.state = { p:"loading" } } componentWillReceiveProps(new_props:NeverProps) { this.setState({...this.state, p:new_props.p.comp(new_props.context)(callback => new_value => {})}) } componentWillMount() { this.setState({...this.state, p:this.props.p.comp(this.props.context)(callback => new_value => {})}) } render(): JSX.Element | JSX.Element[] { return this.state.p != "loading" ? this.state.p : [] } } export let never = function(p:C
, key?:string) : C { return make_C(ctxt => cont => React.createElement>(Never, { kind:"never", p:p, context:ctxt, cont:cont, key:key, debug_info:undefined })) } type AllState = { results:Immutable.Map, ps:"creating"|Array } class All extends React.Component,AllState> { constructor(props:AllProps,context:any) { super(props, context) this.state = { results:Immutable.Map(), ps:"creating" } } componentWillReceiveProps(new_props:AllProps) { this.setState({...this.state, ps:new_props.ps.map((p,p_i) => p.comp(new_props.context)(callback => result => this.setState({...this.state, results:this.state.results.set(p_i, result) }, () => { if (this.state.results.keySeq().toSet().equals(Immutable.Range(0, new_props.ps.length).toSet())) { let results = this.state.results.sortBy((r,r_i) => r_i).toArray() this.setState({...this.state, results:Immutable.Map()}, () => new_props.cont(callback)(results)) } }) ))}) } componentWillMount() { this.setState({...this.state, ps:this.props.ps.map((p,p_i) => p.comp(this.props.context)(callback => result => this.setState({...this.state, results:this.state.results.set(p_i, result) }, () => { if (this.state.results.keySeq().toSet().equals(Immutable.Range(0, this.props.ps.length).toSet())) { let results = this.state.results.sortBy((r,r_i) => r_i).toArray() this.setState({...this.state, results:Immutable.Map()}, () => this.props.cont(callback)(results)) } }) ))}) } render() { return
{ this.state.ps != "creating" ? this.state.ps : null }
} } export let all = function
(ps:Array>, key?:string, dbg?:() => string) : C> { return make_C(ctxt => cont => React.createElement>(All, { kind:"all", debug_info:dbg, ps:ps, context:ctxt, cont:cont, key:key })) } type RetractState = { p:"creating"|JSX.Element } class Retract extends React.Component,RetractState> { constructor(props:RetractProps,context:any) { super(props, context) this.state = { p:"creating" } } componentWillReceiveProps(new_props:RetractProps) { this.setState({...this.state, p:new_props.p(new_props.inb(new_props.value)).comp(new_props.context) (callback => new_value => new_props.cont(callback) (new_props.out(new_props.value)(new_value)))}) } componentWillMount() { this.setState({...this.state, p:this.props.p(this.props.inb(this.props.value)).comp(this.props.context) (callback => new_value => this.props.cont(callback) (this.props.out(this.props.value)(new_value)))}) } render(): JSX.Element | JSX.Element[] { return this.state.p != "creating" ? this.state.p : [] } } export let retract = function(key?:string, dbg?:() => string) : ((inb:(_:A)=>B, out:(_:A)=>(_:B)=>A, p:(_:B)=>C) => (_:A) => C) { return (inb, out, p) => (initial_value:A) => make_C(ctxt => (cont:Cont) => React.createElement>(Retract, { kind:"retract", debug_info:dbg, inb:inb as (_:A)=>any, out:out as (_:A)=>(_:any)=>A, p:p, value:initial_value, context:ctxt, cont:cont, key:key })) } type LiftPromiseState = { result:"busy"|"error"| { kind:"failing", failure_renderer:JSX.Element } | A, input:any, retry_count: number } class LiftPromise extends React.Component,LiftPromiseState> { constructor(props:LiftPromiseProps,context:any) { super(props, context) this.state = { result:"busy", input:props.value, retry_count: 0 } } componentWillReceiveProps(new_props:LiftPromiseProps) { // if (this.state.result != "busy" && this.state.result != "error") { // this.props.debug_info && console.log("New props (ignored):", this.props.debug_info(), this.state.input, new_props.value) // return // } this.props.debug_info && console.log("New props:", this.props.debug_info(), this.state.input, new_props.value) this.setState({...this.state, input:new_props.value}, () => this.load(new_props)) } wait_time:number = 500 stopped:boolean = false load(props:LiftPromiseProps) { if (this.stopped) return this.setState({...this.state, result:"busy"}, () => props.p(this.state.input).then(x => { this.wait_time = 500 if (this.props.debug_info) console.log("Promise done:", this.props.debug_info()) if (this.stopped) return this.setState({...this.state, result:x}, () => props.cont(() => null)(x)) }) .catch(() => { if (props.retry_strategy == "never") { if (this.stopped) return this.setState({...this.state, result:"error"}) } else if (props.retry_strategy == "semi exponential") { this.wait_time = Math.floor(Math.max(this.wait_time * 1.5, 2500)) setTimeout(() => this.load(props), this.wait_time) } else if (props.retry_strategy.kind == "retry then show failure") { if (this.stopped) return if (this.state.retry_count < props.retry_strategy.times) { this.setState({...this.state, retry_count: this.state.retry_count+1 }) setTimeout(() => this.load(props), this.wait_time) } else { let failedJSX : JSX.Element = props.retry_strategy.on_failure.comp(props.context)(props.cont) this.setState({...this.state, retry_count:0, result:{ kind:"failing", failure_renderer:failedJSX } }) } } else if (props.retry_strategy.kind == "never") { if (this.stopped) return let failedJSX : JSX.Element = props.retry_strategy.on_failure.comp(props.context)(props.cont) this.setState({...this.state, result:{ kind:"failing", failure_renderer:failedJSX } }) } })) } componentWillUnmount() { this.stopped = true } componentWillMount() { this.stopped = false this.props.debug_info && console.log("Mount:", this.props.debug_info()) this.load(this.props) } render(): JSX.Element | JSX.Element[] { this.props.debug_info && console.log("Render:", this.props.debug_info()) return this.state.result == "busy" ?
{i18next.t("busy")}
: this.state.result == "error" ?
{i18next.t("error")}
: this.state.result != undefined && this.state.result.hasOwnProperty('kind') && (this.state.result as any).kind === "failing" ? (this.state.result as any).failure_renderer : [] } } export let lift_promise = function(p:(_:A) => Promise, retry_strategy:RetryStrategy, key?:string, dbg?:() => string) : ((_:A)=>C) { return x => make_C(ctxt => cont => React.createElement>(LiftPromise, { kind:"lift promise", debug_info:dbg, value:x, retry_strategy:retry_strategy, p:p, context:ctxt, cont:cont, key:key })) } type DelayState
= { status:"dirty"|"waiting", value:A, last_command:JSX.Element } class Delay extends React.Component,DelayState> { constructor(props:DelayProps,context:any) { super(props, context) this.state = { status:"dirty", value:props.value, last_command:props.p(props.value).comp(props.context)(props.cont) } } running:boolean = false componentWillMount() { //console.log("starting delay thread") if (this.running) return this.running = true var self = this let process = () => setTimeout(() => { //console.log("delay is ticking", self.state.status, self.state.value) if (self.state.status == "dirty") { //console.log("delay is submitting the data to save") if (!this.running) return self.setState({...self.state, status:"waiting", last_command:self.props.p(self.state.value).comp(this.props.context)(callback => new_value => { //console.log("calling the continuation of dirty", self.state.value) self.props.cont(callback)(new_value) })}) process() } else { if (self.running) process() } }, self.props.dt) process() } componentWillUnmount() { //console.log("stopping delay thread") this.running = false } componentWillReceiveProps(new_props:DelayProps) { //console.log("Delay received new props and is going back to dirty") this.setState({...this.state, value: new_props.value, status:"dirty"}) } render() { return this.state.last_command } } export let delay = function(dt:number, key?:string, dbg?:() => string) : (p:(_:A)=>C) => ((_:A) => C) { return p => initial_value => make_C(ctxt => cont => React.createElement>(Delay, { kind:"delay", debug_info:dbg, dt:dt, p:p as (_:A)=>C, value:initial_value, context:ctxt, cont:cont, key:key })) } type WaitState = { status:"open"|"closed", last_command:JSX.Element } class Wait extends React.Component,WaitState> { constructor(props:WaitProps,context:any) { super(props, context) this.state = { status:"open", last_command: null //last_command:props.p(props.value).comp(props.context)(props.cont) } } running:boolean = false end_process() { if (!this.running) return //console.log('Ending process') this.setState({...this.state, status: "closed", last_command: this.props.p(this.props.value).comp(this.props.context)(callback => new_value => this.props.cont(callback)(new_value))}) } process() { //console.log('Starting Wait process') } componentWillMount() { //console.log("starting wait thread") if (this.running) return this.running = true //console.log('Starting first waiting') setTimeout(() => this.end_process(),this.props.dt) // var self = this // let process = () => // setTimeout(() => { // console.log("wait is ticking", self.state.status, self.state.value) // if (self.state.status == "dirty") { // console.log("wait is submitting the data to save") // if (!this.running) return // self.setState({...self.state, status:"waiting", last_command:self.props.p(self.state.value).comp(this.props.context)(callback => new_value => { // console.log("calling the continuation of dirty", self.state.value) // self.props.cont(callback)(new_value) // })}) // process() // } else { // if (self.running) // process() // } // }, self.props.dt) // process() } componentWillUnmount() { //console.log("stopping wait thread") this.running = false } componentDidUpdate(prevProps: WaitProps,prevState: WaitState) { if (prevState.status == 'closed' && this.state.status == "open") { //console.log('Here we start the process') setTimeout(() => this.end_process(),this.props.dt) } } componentWillReceiveProps(new_props:WaitProps) { // console.log("Wait received new props and is going to wait") // let process = () => console.log('start process') || setTimeout(() => { // console.log('the process is ending') // this.setState({...this.state, status:"closed", last_command: this.props.p(this.state.value).comp(this.props.context)(callback => new_value => { // this.props.cont(callback)(new_value) // })}) // } // ,this.props.dt) // if (this.state.status == "closed") { // this.setState({...this.state, value: new_props.value, status:"waiting"}, () => process()) // } // else this.setState({...this.state, value: new_props.value}) this.setState({...this.state, status: 'open'}) } render() { //console.log(this.props.value) return this.state.last_command } } export let waiting = function(dt:number, key?:string, dbg?:() => string) : (p:(_:A)=>C) => ((_:A) => C) { return p => initial_value => make_C(ctxt => cont => React.createElement>(Wait, { kind:"wait", debug_info:dbg, dt:dt, p:p as (_:A)=>C, value:initial_value, context:ctxt, cont:cont, key:key })) } export let mk_submenu_entry = function(label:string, children:Array>) : MenuEntrySubMenu { return { kind:"sub menu", label:label, children:children } } export let mk_menu_entry = function(v:A) : MenuEntryValue { return { kind:"item", value:v } } export type MenuEntryValue = { kind:"item", value:A } export type MenuEntrySubMenu = { kind:"sub menu", label:string, children:Array> } export type MenuEntry = MenuEntryValue | MenuEntrySubMenu export let simple_menu = function(type:SimpleMenuType, to_string:(_:A)=>string, key?:string, dbg?:() => string) : ((items:Array>, p:(_:A)=>C, selected_item?:A, selected_sub_menu?:string) => C) { type ShownRange = { first:number, amount:number } type MenuState = { selected:{ kind:"nothing" } | { kind:"item", value:A } sub_selected : { kind:"nothing" } | { kind:"sub menu", label:string } last_action:{kind:"init"|"selection"}|{kind:"p", p_res:B }, shown_range:undefined|ShownRange } let content_menu_class:string, content_class:string, menu_class:string, entries_class:string, entry_class:string, sub_entry_class:string if (type == "side menu") { content_menu_class = "monadic-content-with-menu" content_class = "monadic-content" menu_class = "monadic-content-menu" entries_class = "monadic-content-menu__entries" entry_class = "monadic-content-menu__entry" sub_entry_class = "monadic-content-menu__sub-entry" } else { content_menu_class = "monadic-content-with-tabs" content_class = "monadic-content" menu_class = "monadic-tabs" entries_class = "monadic-tabs__entries" entry_class = "monadic-tabs__entry" sub_entry_class = "monadic-tabs__sub-entry" } return (items_array, p, selected_item:undefined|A, selected_sub_menu:undefined|string) => { let items = Immutable.List>(items_array) let entries : (s:MenuState) => Array<(_:MenuState) => C> = (s:MenuState) => (type != "side menu" && s.shown_range.first > 0 ? [s => div(`${entry_class} monadic-prev-tab`)(a("<"))({...s, shown_range:{...s.shown_range, first:s.shown_range.first-1}})] : []).concat( items.map((item, i) => { return (s:MenuState) => item.kind == "item" ? div(`${entry_class} ${s.selected.kind == "item" && item.value == s.selected.value ? ` ${entry_class}--active` : ""}`, to_string(item.value))( a(to_string(item.value), undefined, undefined, false, undefined) )({...s, sub_selected:{ kind:"nothing" }, selected:item, last_action:{kind:"selection"} }) : any(item.label)([ (s:MenuState) => div(`${entry_class} `, item.label)( a(item.label, undefined, undefined, false, undefined) )({...s, sub_selected:item, last_action:{kind:"selection"} }) ].concat( (s.sub_selected.kind == "sub menu" && item.label == s.sub_selected.label) || (s.selected.kind == "item" && item.children.some(c => s.selected.kind == "item" && c.value == s.selected.value)) ? item.children.map(c => (s:MenuState) => div(`${sub_entry_class} ${s.selected.kind == "item" && c.value == s.selected.value ? ` ${sub_entry_class}--active` : ""}`, to_string(c.value))( a(to_string(c.value), undefined, undefined, false, undefined) )({...s, sub_selected:item, selected:c, last_action:{kind:"selection"} }) ) : [] ))(s) }).filter((i, i_i) => type == "side menu" || i_i >= s.shown_range.first && (i_i - s.shown_range.first) < s.shown_range.amount) .concat( type != "side menu" && s.shown_range.first + s.shown_range.amount < items.count() ? [s => div(`${entry_class} monadic-next-tab`)(a(">"))({...s, shown_range:{...s.shown_range, first:s.shown_range.first+1}})] : []) .toArray()) return repeat()( div()( any(undefined, content_menu_class)( [ div(menu_class, menu_class)( s => any(undefined, entries_class)(entries(s))(s)), div(content_class, content_class)( (s:MenuState) => s.selected.kind == "item" ? p(s.selected.value).then(undefined, (p_res:B) => unit({...s, last_action:{ kind:"p", p_res:p_res }})) : unit(s).never()) ] ) ))({ selected:selected_item == undefined ? { kind:"nothing" } : { kind:"item", value:selected_item }, sub_selected:selected_sub_menu == undefined ? { kind:"nothing" } : { kind:"sub menu", label:selected_sub_menu }, last_action:{ kind:"init" }, shown_range:type=="side menu" ? undefined : { first:0, amount:type.max_tabs } }) .filter(s => s.last_action.kind != "p") .map(s => s.last_action.kind == "p" && s.last_action.p_res) } } export let custom = function(key?:string, dbg?:() => string) : (render:(ctxt:()=>Context) => (_:Cont) => JSX.Element) => C { return (render) => make_C(ctxt => cont => render(ctxt)(cont)) } export let hide = (f_name:string, f:C) => repeat()(visible => bool("edit", "plus/minus")(visible))(false).then(`${f_name} toggle`, visible => !visible ? unit(null) : f.then(`visible ${f_name}`, _ => unit(null)))