import type { Fn , IObjectOf } from "@thi.ng/api" ; import type { IAtom } from "@thi.ng/atom" ; import { defAtom } from "@thi.ng/atom/atom" ; import { defCursor } from "@thi.ng/atom/cursor" ; import { defView } from "@thi.ng/atom/view" ; import { start } from "@thi.ng/hdom/start" ; type CardFn = ( state : IAtom < any > ) => any ; let CARD_ID = 1 ; param card param state param title param parent function defcard ( card , state ? , title ? , parent ? ) { state = state || defAtom ( { } ) ; title = title || `devcard-${ CARD_ID ++ }` ; if ( ! parent ) { parent = document . createElement ( "div" ) ; document . body . appendChild ( parent ) ; } const json = defView ( state , [ ] , ( state ) => JSON . stringify ( state , null , 2 ) ) ; const root = card ( state ) ; start ( ( ) => [ "div.card" , [ "h3" , title ] , [ "div.body" , root , [ "pre" , json ] ] ] , { root : parent } ) ; } /**
 * Value slider component options (see function below)
 */ interface SliderOpts { min : number ; max : number ; step ? : number ; label : Fn < any , any > ; onchange ? : EventListener ; } param state param opts function slider ( state , opts ) { const attribs = { ... opts , type : "range" , oninput : ( e ) => { state . reset ( ( < HTMLInputElement > e . target ) . value ) ; opts . onchange && opts . onchange ( e ) ; } , } ; return ( ) => [ "div" , [ "div" , opts . label ( state . deref ( ) ) ] , [ "input" , { ... attribs , value : state . deref ( ) } ] , ] ; } interface BMIState { height : number ; weight : number ; bmi : number ; } param state function bmi ( state ) { const calc = ( updateWeight = false ) => { let { height , weight , bmi } = state . deref ( ) || { height : 0 , weight : 0 , bmi : 0 , } ; height *= 0.01 ; if ( updateWeight ) { state . resetIn ( [ "weight" ] , bmi * height * height ) ; } else { state . resetIn ( [ "bmi" ] , weight / ( height * height ) ) ; } } ; const thresh = [ [ 10 , "underweight" , "#cf3" ] , [ 18.5 , "normal" , "#7f0" ] , [ 25 , "overweight" , "#f90" ] , [ 30 , "obese" , "#f00" ] , ] ; const bmiClass = defView ( state , [ "bmi" ] , ( bmi ) => bmi > thresh [ 3 ] [ 0 ] ? thresh [ 3 ] [ 1 ] : bmi > thresh [ 2 ] [ 0 ] ? thresh [ 2 ] [ 1 ] : bmi > thresh [ 1 ] [ 0 ] ? thresh [ 1 ] [ 1 ] : thresh [ 0 ] [ 1 ] ) ; const bmiScale = ( x ) => ( ( x - 10 ) / 30 ) * 100 + "%" ; const bmiViz = defView ( state , [ "bmi" ] , ( bmi ) => [ "div" , [ "svg" , { width : "100%" , height : 30 , style : { "font-size" : "10px" } } , ... thresh . map ( ( [ t , _ , col ] ) => [ "rect" , { x : bmiScale ( t ) , y : 0 , width : "100%" , height : 30 , fill : col } , ] ) , ... thresh . map ( ( [ t , label ] ) => [ "text" , { x : bmiScale ( t + 0.5 ) , y : 12 } , label , ] ) , [ "circle" , { cx : bmiScale ( bmi ) , cy : 20 , r : 5 } ] , ] , ] ) ; const height = slider ( defCursor ( state , [ "height" ] ) , { min : 100 , max : 220 , label : ( v ) => `Height: ${ ~ ~ v }cm` , onchange : ( ) => calc ( ) , } ) ; const weight = slider ( defCursor ( state , [ "weight" ] ) , { min : 10 , max : 150 , label : ( v ) => `Weight: ${ ~ ~ v }kg` , onchange : ( ) => calc ( ) , } ) ; const bmi = slider ( defCursor ( state , [ "bmi" ] ) , { min : 10 , max : 50 , label : ( v ) => [ "span" , { class : bmiClass . deref ( ) } , `BMI: ${ ~ ~ v } (${ bmiClass . deref ( ) })` , ] , onchange : ( ) => calc ( true ) , } ) ; calc ( ) ; return ( ) => [ "div" , height , weight , bmi , [ "div" , bmiViz ] ] ; } /**
 * All what's left to do here is to instantiate a bunch of cards (apps):
 *
 * Option 1: Two defcard() instances, each with their own, individual state atom
 */ defcard ( bmi , defAtom ( { weight : 75 , height : 194 } ) , "BMI calculator" ) ; defcard ( bmi ) ; defcard ( ( ) => "The cards below are all attached to the same atom, but use cursors to subscribe to different branches within the nested state." ) ; /**
 * Option 2: defcard() instances using shared central state
 */ const db = defAtom < IObjectOf < Partial < BMIState > > > ( { card1 : { weight : 75 , height : 194 } , } ) ; defcard ( bmi , defCursor ( db , [ "card1" ] ) , "BMI calculator (shared)" ) ; defcard ( bmi , defCursor ( db , [ "card2" ] ) ) ; defcard ( ( state ) => { setInterval ( ( ) => state . resetIn ( [ "stats" , "now" ] , new Date ( ) . toISOString ( ) ) , 1000 ) ; return [ "div" , "The full shared state:" ] ; } , db ) ;