uci-utils-ready/src/ready.js

106 lines
3.2 KiB
JavaScript

import { from, fromEvent, combineLatest } from 'rxjs'
import { map, startWith, tap } from 'rxjs/operators'
import isObservable from 'is-observable'
import isPromise from 'p-is-promise'
import { createBoolean } from '../../to-boolean'
const toBool = createBoolean({undefined:true})
class Ready extends Map {
constructor(opts) {
super(opts.observables)
// TODO support setting registration in options
this.emitter = typeof opts.emitter.on ==='function' ? opts.emitter : null
this.condition = opts.condition || ( (ev) => toBool(ev) )
this.subscriptions = new Map()
this._state = {}
this._updateObserversList()
this.handler = opts.handler || ( ([r,s]) => {console.log('ready:',r,'states:',s,'\n---------------------------')})
}
get state() {return this._state}
addObserver(name, obs, condition ) {
if (!name) return false // name required
if (!(obs || this.emitter)) return false // some observable requried
if (typeof (obs ||{}).on ==='function') obs = fromEvent(obs,name) // it's an emitter
if (isPromise(obs)) obs = from(obs) // it's a promise
if (obs && !isObservable(obs) && typeof obs==='function' && arguments.length===2) {
condition = obs
obs = null
}
if (!obs && this.emitter) obs = fromEvent(this.emitter,name)
if (!obs || !isObservable(obs)) return false
this.set(name, obs
.pipe(
tap((ev) => console.log(name,'emitted/resolved=>',ev)),
map(condition||this.condition),
tap((b) => console.log('boolean:',b)),
startWith(false),
)
)
this._updateObserversList()
return true
}
removeObserver(names) {
if (!names) this.clear
else {
if (!Array.isArray(names)) names = [names]
names.forEach(name => {
this.delete(name)
})
}
this._updateObserversList()
}
subscribe(name, handler) {
if (typeof name ==='function') {
handler=name
name = null
}
this.subscriptions.set(name||'_primary', (name ? this.get(name):this._state).subscribe(handler||this.handler))
}
unsubscribe(name) {
this.subscriptions.get(name||'_primary').unsubscribe()
}
_updateObserversList() {
this._state = combineLatest(Array.from(this.values())).pipe(
// tap((states)=>console.log('states',states)),
map(states=> {
return [states.reduce((res,state) => {return res && state},true), Array.from(this.keys()).map((name,index) => [name,states[index]])]
})
)
}
}
// const toBoolean = (cond) =>
// pipe(
// map(cond),
// startWith(false),
// )
const rTrue=['t','true','y','yes','on','positive','up','enabled','affirmative','yea']
const rFalse=['f','false','n','no','off','negative','down','disabled','nope']
function castBoolean (opts={}) {
return (value => {
if (value===undefined) return opts.undefined
if (value===null) return opts.null
if (!isNaN(Number(value))) return Number(value) <1 ? false :true
if (typeof value==='string') {
value = value.trim()
value = value.toLowerCase()
if ((opts.rTrue || rTrue).includes(value)) return true
if ((opts.rFalse || rFalse).includes(value)) return false
}
return !!value
})
}
export default Ready
export { Ready }