vscroll
Version:
Virtual scroll engine
186 lines • 6.02 kB
JavaScript
import { Scroller } from './scroller';
import { runStateMachine } from './workflow-transducer';
import { Reactive } from './classes/reactive';
import { AdapterProcess, CommonProcess, ProcessStatus as Status } from './processes/index';
import { WORKFLOW, validate } from './inputs/index';
export class Workflow {
constructor(params) {
const { element, datasource, consumer, run, Routines } = params;
const validationResult = validate(params, WORKFLOW);
if (!validationResult.isValid) {
throw new Error(`Invalid Workflow params: ${validationResult.errors.join(', ')}.`);
}
this.isInitialized = false;
this.disposed = false;
this.initTimer = null;
this.adapterRun$ = new Reactive();
this.cyclesDone = 0;
this.cyclesDone$ = new Reactive(0);
this.interruptionCount = 0;
this.errors = [];
this.offScroll = () => null;
this.propagateChanges = run;
this.stateMachineMethods = {
run: this.runProcess(),
interrupt: this.interrupt.bind(this),
done: this.done.bind(this),
onError: this.onError.bind(this)
};
this.scroller = new Scroller({
element,
datasource,
consumer,
workflow: this.getUpdater(),
Routines
});
if (this.scroller.settings.initializeDelay) {
this.initTimer = setTimeout(() => {
this.initTimer = null;
this.init();
}, this.scroller.settings.initializeDelay);
}
else {
this.init();
}
}
init() {
this.scroller.init(this.adapterRun$);
// set up scroll event listener
const { routines } = this.scroller;
const onScrollHandler = event => this.callWorkflow({
process: CommonProcess.scroll,
status: Status.start,
payload: { event }
});
this.offScroll = routines.onScroll(onScrollHandler);
// run the Workflow
this.isInitialized = true;
this.callWorkflow({
process: CommonProcess.init,
status: Status.start
});
}
changeItems(items) {
this.propagateChanges(items);
}
callWorkflow(processSubject) {
if (!this.isInitialized) {
return;
}
const { process, status } = processSubject;
// if the scroller is paused, any process other than "pause" and "reset" should be blocked
if (this.scroller.state.paused.get()) {
if (![AdapterProcess.pause, AdapterProcess.reset].includes(process)) {
this.scroller.logger.log('scroller is paused: ' + process + ' process is ignored');
return;
}
}
if (process && process.startsWith('adapter') && status !== Status.next) {
this.adapterRun$.set(processSubject);
}
this.process(processSubject);
}
getUpdater() {
return {
call: this.callWorkflow.bind(this),
onDataChanged: this.changeItems.bind(this)
};
}
process(data) {
const { status, process, payload } = data;
if (this.scroller.settings.logProcessRun) {
this.scroller.logger.log(() => {
const _fire = this.scroller.settings.logColor
? ['%cfire%c', 'color: #cc7777;', 'color: #000000;']
: ['fire'];
return [..._fire, process, `"${status}"`, ...(payload !== void 0 ? [payload] : [])];
});
}
this.scroller.logger.logProcess(data);
if (process === CommonProcess.end) {
this.scroller.finalize();
}
runStateMachine({
input: data,
methods: this.stateMachineMethods
});
}
runProcess() {
return ({ run, process, name }) => (...args) => {
if (this.scroller.settings.logProcessRun) {
this.scroller.logger.log(() => {
const _run = this.scroller.settings.logColor
? ['%crun%c', 'color: #333399;', 'color: #000000;']
: ['run'];
return [..._run, process || name, ...args];
});
}
run(this.scroller, ...args);
};
}
onError(process, payload) {
const message = (payload && String(payload.error)) || '';
const { time, cycle } = this.scroller.state;
this.errors.push({
process,
message,
time,
loop: cycle.loopIdNext
});
this.scroller.logger.logError(message);
}
interrupt({ process, finalize, datasource }) {
if (finalize) {
const { workflow, logger } = this.scroller;
// we are going to create a new reference for the scroller.workflow object
// calling the old version of the scroller.workflow by any outstanding async processes will be skipped
workflow.call = (_) => logger.log('[skip wf call]');
workflow.call.interrupted = true;
this.scroller.workflow = this.getUpdater();
this.interruptionCount++;
logger.log(() => `workflow had been interrupted by the ${process} process (${this.interruptionCount})`);
}
if (datasource) {
// Scroller re-initialization case
const reInit = () => {
this.scroller.logger.log('new Scroller instantiation');
const scroller = new Scroller({ datasource, scroller: this.scroller });
this.scroller.dispose();
this.scroller = scroller;
this.scroller.init();
};
if (this.scroller.state.cycle.busy.get()) {
// todo: think about immediate re-initialization even is there are pending processes
this.scroller.adapter.relax(reInit.bind(this));
}
else {
reInit();
}
}
}
done() {
const { state, logger } = this.scroller;
this.cyclesDone++;
this.cyclesDone$.set(this.cyclesDone);
logger.logCycle(false);
state.endWorkflowCycle(this.cyclesDone + 1);
this.finalize();
}
dispose() {
this.scroller.logger.log(() => 'disposing workflow');
if (this.initTimer) {
clearTimeout(this.initTimer);
}
this.offScroll();
this.adapterRun$.dispose();
this.cyclesDone$.dispose();
this.scroller.dispose(true);
Object.getOwnPropertyNames(this).forEach(prop => {
delete this[prop];
});
this.isInitialized = false;
this.disposed = true;
}
finalize() { }
}
//# sourceMappingURL=workflow.js.map