import * as React from "react"; import * as ReactDOM from "react-dom"; export enum BatchUpdateType { UNSET = -1, GENERAL = 0, CHANNEL_TREE = 1 } interface UpdateBatch { enabled: boolean; enable_count: number; update: { c: any; s: any; b: () => void; }[]; force: { c: any; b: () => void; }[]; } const generate_batch = () => { return { enabled: false, enable_count: 0, update: [], force: [] }}; let update_batches: {[key: number]:UpdateBatch} = { 0: generate_batch(), 1: generate_batch() }; (window as any).update_batches = update_batches; export function BatchUpdateAssignment(type: BatchUpdateType) { return function (constructor: Function) { if(!ReactComponentBase.prototype.isPrototypeOf(constructor.prototype)) throw "Class/object isn't an instance of ReactComponentBase"; const didMount = constructor.prototype.componentDidMount; constructor.prototype.componentDidMount = function() { if(typeof this.update_batch === "undefined") this.update_batch = update_batches[type]; if(typeof didMount === "function") didMount.call(this, arguments); }; } } export abstract class ReactComponentBase extends React.Component { private update_batch: UpdateBatch; private batch_component_id: number; private batch_component_force_id: number; constructor(props: Properties) { super(props); this.batch_component_id = -1; this.batch_component_force_id = -1; this.state = this.defaultState(); this.initialize(); } protected initialize() { } protected defaultState() : State { return {} as State; } setState( state: ((prevState: Readonly, props: Readonly) => (Pick | State | null)) | (Pick | State | null), callback?: () => void ): void { if(typeof this.update_batch !== "undefined" && this.update_batch.enabled) { const obj = { c: this, s: Object.assign(this.update_batch.update[this.batch_component_id]?.s || {}, state), b: callback }; if(this.batch_component_id === -1) this.batch_component_id = this.update_batch.update.push(obj) - 1; else this.update_batch.update[this.batch_component_id] = obj; } else { super.setState(state, callback); } } forceUpdate(callback?: () => void): void { if(typeof this.update_batch !== "undefined" && this.update_batch.enabled) { const obj = { c: this, b: callback }; if(this.batch_component_force_id === -1) this.batch_component_force_id = this.update_batch.force.push(obj) - 1; else this.update_batch.force[this.batch_component_force_id] = obj; } else { super.forceUpdate(callback); } } protected classList(...classes: (string | undefined)[]) { return [...classes].filter(e => typeof e === "string" && e.length > 0).join(" "); } protected hasChildren() { const type = typeof this.props.children; if(type === "undefined") return false; return Array.isArray(this.props.children) ? this.props.children.length > 0 : true; } } export function batch_updates(type: BatchUpdateType) { const batch = update_batches[type]; if(typeof batch === "undefined") throw "unknown batch type"; batch.enabled = true; batch.enable_count++; } export function flush_batched_updates(type: BatchUpdateType, force?: boolean) { const batch = update_batches[type]; if(typeof batch === "undefined") throw "unknown batch type"; if(--batch.enable_count > 0 && !force) return; if(batch.enable_count < 0) throw "flush_batched_updates called more than batch_updates!"; const updates = batch.update; const forces = batch.force; batch.update = []; batch.force = []; batch.enabled = batch.enable_count > 0; ReactDOM.unstable_batchedUpdates(() => { { let index = updates.length; while(index--) { /* fastest way to iterate */ const update = updates[index]; update.c.batch_component_id = -1; update.c.setState(update.s, update.b); } } { let index = forces.length; while(index--) { /* fastest way to iterate */ const update = forces[index]; update.c.batch_component_force_id = -1; update.c.forceUpdate(update.b); } } }); }