UNPKG

bobflux-monitor

Version:

Component for time travelling in bobflux/fun-model application state history.

184 lines (170 loc) 6.99 kB
import * as b from 'bobril'; import * as f from 'fun-model'; import * as stringHelpers from '../helpers/string'; import button, { ButtonType } from './button'; import textarea, { textareaStyles } from './textarea'; import rows from './rows'; const containerStyle = b.styleDef({ position: 'absolute', top: 0, right: 0, backgroundColor: '#ddd', fontFamily: 'Open sans, Sans-serif', zIndex: 1000 }); const openedContainerStyle = b.styleDef({ bottom: 0 }); const notScrollingStyle = b.styleDef({ overflow: 'auto', overflowX: 'hidden' }); const scrollingStyle = b.styleDef({ overflow: 'scroll', overflowX: 'hidden', position: 'absolute', top: 148, right: 0, bottom: 0 }); export interface IStateStamp { change: string; state: f.IState; time: Date; frames: number; } export interface IMonitorPanelData { isOpen: boolean; stateStamps: IStateStamp[]; playToggled: (value: boolean) => void; } interface ICtx extends b.IBobrilCtx { data: IMonitorPanelData; stateText?: string; setFocusForCopy: boolean; interval: number; isPlaying: boolean; } const copyContainer = b.styleDef({ padding: 3 }); export function monitorGenericFactory<TState extends f.IState>(cursor: f.ICursor<TState>) { return b.createComponent({ id: 'bobflux-monitor-panel', init(ctx: ICtx) { ctx.interval = 500; ctx.isPlaying = false; }, render(ctx: ICtx, me: b.IBobrilNode) { b.style(me, containerStyle); b.style(me, ctx.data.isOpen && openedContainerStyle); const state = f.getState(cursor); me.children = [ button({ title: ctx.data.isOpen ? 'HIDE >' : '<', type: ctx.data.isOpen ? ButtonType.Open : ButtonType.Close, onClick: () => { ctx.data.isOpen = !ctx.data.isOpen; b.invalidate(ctx); } }), !!ctx.data.isOpen && [ b.styledDiv( [ textarea({ value: ctx.stateText, setFocus: ctx.setFocusForCopy, style: textareaStyles.copyState, float: 'left', placeholder: 'Paste the state... [ctrl+v]', onChange: (value: string) => { ctx.stateText = value; b.invalidate(ctx); }, onKeyDown: (event: b.IKeyDownUpEvent) => { if (event.ctrl && event.which === 67) { ctx.stateText = ''; b.invalidate(); } } }), b.withKey( button({ title: 'GO', type: ButtonType.Go, isDisabled: !ctx.stateText, onClick: () => { if (!ctx.stateText) return; f.setState(cursor, new Function(`return ${ctx.stateText};`)()); b.invalidate(); } }), 'go-copy') ], copyContainer), b.styledDiv( [ textarea({ value: ctx.interval.toString(), style: textareaStyles.intervalStyle, float: 'left', placeholder: 'miliseconds', onChange: (value: string) => { if (!isNaN(parseFloat(value))) ctx.interval = parseFloat(value); } }), b.withKey( button({ title: 'PLAY', isDisabled: !ctx.data.stateStamps.length || ctx.isPlaying, onClick: () => { play(ctx, cursor); } }), 'play') ], copyContainer), b.styledDiv( rows({ rows: ctx.data.stateStamps.map((stateStamp, index) => { return { header: index.toString(), info: stateStamp.time.toLocaleTimeString(), frames: stateStamp.frames, isActive: state === stateStamp.state, onGo: () => { f.setState(cursor, stateStamp.state); b.invalidate(); }, onCopy: () => { ctx.stateText = stringHelpers.allToString(stateStamp.state); ctx.setFocusForCopy = true; b.invalidate(ctx); } }; }).reverse() }), ctx.data.isOpen ? scrollingStyle : notScrollingStyle) ] ]; ctx.setFocusForCopy = false; } }); }; export default monitorGenericFactory; function play<TState extends f.IState>(ctx: ICtx, cursor: f.ICursor<TState>, startIndex = -1) { if (startIndex === -1) { ctx.isPlaying = true; ctx.data.playToggled(true); const state = f.getState(cursor); startIndex = ctx.data.stateStamps.map(stamp => stamp.state).indexOf(state); if (startIndex === ctx.data.stateStamps.length - 1) { startIndex = -1; } } if (startIndex < ctx.data.stateStamps.length - 1) { const newIndex = startIndex + 1; f.setState(cursor, ctx.data.stateStamps[newIndex].state); b.invalidate(); setTimeout(() => play(ctx, cursor, newIndex), ctx.interval); } else { ctx.isPlaying = false; ctx.data.playToggled(false); b. invalidate(ctx); } }