import { InformationSource } from "./reporter/information_source.js";
/**
* StateTracker
* Tracks states and brings undo/redo functionality
*/
class StateTracker extends InformationSource {
/**
* @param {Reporter} reporter
* @param {Object} settings
*/
constructor(reporter, settings) {
super(reporter, settings);
this.maxStateCount = Number( settings.maxStateCount || 5 );
this.addEvent('statechange');
this.resetState();
}
/**
* @protected
* @type {Array<any>}
*/
_stateRegister;
/**
* @protected
* @type {number}
*/
_cursor;
get cursor() {
return this._cursor;
}
get state() {
return this._stateRegister[ this._cursor ];
}
resetState() {
// console.log('reset')
this._stateRegister = [];
this._cursor = 0;
}
cleanUpStateItem( item ) {
if (typeof item.cleanUp === 'function') {
item.cleanUp({
state: item,
stateRegister: this._stateRegister,
maxStateCount: this.maxStateCount
});
}
}
/**
* Add a new state to the register
* @param {Object} data
* @param {any} data.newState
* @param {(false|true|'silent')} [data.moveCursor = true]
*/
async addState({ newState, moveCursor = true }) {
// console.log(this.label, 'addState', newState );
this._stateRegister = this._stateRegister.slice(
// Math.max( 0, this._cursor - this.maxStateCount + 1),
0,
this._cursor + 1
);
if ( this._stateRegister.length > this.maxStateCount) {
//this.log(this.label, 'too many states', this._stateRegister.length);
for ( let i = 0 ; i < this._stateRegister.length - this.maxStateCount ; i += 1) {
let removeItem = this._stateRegister.shift()
this.report({
msg: `Popping old state ${removeItem.label || removeItem}`,
level: 'debug'
});
this.cleanUpStateItem( removeItem );
// when a configurator was "only" moved, the configuration object isnt changed
// so dont destroy it, in that case (compare with _stateRegister is ok, bc removeItem was
// spliced off)
// console.log('config still at index', this._stateRegister.findIndex( state => state.configuration.id === newState.configuration.id))
// console.log( newState.configuration.id)
// console.log( this._stateRegister.map( state => state.configuration.id ))
//if (removeItem.configuration && this._stateRegister.find( state => state.configuration === newState.configuration) === undefined ) {
// console.log('destroy config')
//removeItem.configuration.destroy();
//removeItem.configuration = undefined;
//removeItem = undefined;
//}
removeItem = null;
}
}
this._stateRegister.push(newState);
// console.log(this._stateRegister.length)
if (moveCursor === true) {
await this.setCursor(this._stateRegister.length - 1);
}
else if (moveCursor === 'silent') {
this._cursor = this._stateRegister.length - 1;
}
else {
// do nothing
}
}
/**
* @param {number} newPosition
* @returns {Promise<Object>}
*/
async setCursor(newPosition) {
if (!this._stateRegister[newPosition]) {
throw new Error(`${this.label} unset cursor position ${newPosition}`);
}
this._cursor = newPosition;
await this.onStateCursorMoved(this._stateRegister[newPosition]);
}
overwritePreviousState() {
const head = this._stateRegister.length-1;
const headMinusOne = this._stateRegister.length-2;
// console.log('head', this._stateRegister[head])
// console.log('head-1', this._stateRegister[headMinusOne])
// const newState = Object.assign(
// {},
// this._stateRegister[headMinusOne],
// this._stateRegister[head]
// )
// console.log(newState);
// this._stateRegister[headMinusOne] = newState;
// this._stateRegister.pop();
// console.log(this._stateRegister);
// this.setCursor(this._cursor = newPosition;)
this._stateRegister.splice(headMinusOne,1);
this._cursor = headMinusOne;
}
/** @param {any} selectedState */
onStateCursorMoved(selectedState) {
throw new Error('Interface not implemented in extending class');
}
/**
* Go back to the previous configuration by
* decreasing the cursor position
* @param {number} [steps =1]
* @returns {Promise<Object>}
*/
undo(steps = 1) {
return this.setCursor(Math.max(0, this._cursor - steps));
}
/**
* Whether an undo is currently possible
* @returns {Boolean}
*/
get canUndo() {
// console.log(`${this.label} can redo`, this._cursor, this._stateRegister.length);
return this._cursor > 0;
}
/**
* Go forward in the configuration timeline
* by increasing the cursor position
* @param {number} steps
* @returns {Promise<Object>}
*/
redo(steps = 1) {
return this.setCursor(Math.min(this._stateRegister.length - 1, this._cursor + steps));
}
/**
* Whether a redo is currently possible
* @returns {Boolean}
*/
get canRedo() {
// console.log(`${this.label} can redo`, this._cursor, this._stateRegister.length);
return this._cursor < this._stateRegister.length - 1;
}
destroy() {
this.resetState();
if ( typeof super.destroy === 'function' ) {
super.destroy()
}
}
}
export { StateTracker };