bobflux-monitor
Version:
Component for time travelling in bobflux/fun-model application state history.
184 lines (170 loc) • 6.99 kB
text/typescript
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);
}
}