/** Copyright Stewart Allen -- All Rights Reserved */ import { broker } from "../moto/broker.js"; class History { #pos = -1; #stack = []; #tracking = false; constructor() { broker.subscribe({ app_ready() { this.#tracking = true }, drag_start(selected) { this.push('position', selected.map(model => ({ model, pos: model.position().toArray() }))); }, drag_end(selected) { this.update(selected.map(model => ({ model, pos: model.position().toArray() }))); }, move(data) { const { set, dx, dy, dz } = data; this.push('move', { set, dx: -dx, dy: -dy, dz: -dz }, { set, dx, dy, dz }); }, rotate(data) { const { set, dx, dy, dz } = data; this.push('rotate', { set, dx: -dx, dy: -dy, dz: -dz }, { set, dx, dy, dz }); }, scale(data) { const { set, dx, dy, dz } = data; this.push('scale', { set, dx: 1/dx, dy: 1/dy, dz: 1/dz }, { set, dx, dy, dz }); }, }, this); } push(type, undo, redo) { this.#stack.length = ++this.#pos; this.#stack.push({ type, undo, redo }); } update(redo, undo) { let peek = this.#stack.peek(); if (peek) { Object.assign(peek, { redo: redo || peek.redo, undo: undo || peek.undo }); } } do(type, data) { switch (type) { case 'move': { let { set, dx, dy, dz } = data; for (let grp of set) { grp.move(dx, dy, dz); } break; } case 'position': { for (let { model, pos } of data) { model.position(...pos); } break; } case 'rotate': { let { set, dx, dy, dz } = data; for (let grp of set) { grp.rotate(dx, dy, dz); } break; } case 'scale': { let { set, dx, dy, dz } = data; for (let grp of set) { grp.scale(dx, dy, dz); } break; } default: console.warn(`invalid record type: ${type}`); } } undo() { if (this.#pos >= 0) { const rec = this.#stack[this.#pos--]; this.do(rec.type, rec.undo); broker.publish("history_undo", rec); } } redo() { if (this.#stack.length && this.#pos >= -1 && this.#pos < this.#stack.length - 1) { const rec = this.#stack[++this.#pos]; this.do(rec.type, rec.redo); broker.publish("history_redo", rec); } } } export const history = new History();