diff --git a/src/command/commands/update.js b/src/command/commands/update.js index bac00602..ad06308e 100644 --- a/src/command/commands/update.js +++ b/src/command/commands/update.js @@ -1,29 +1,73 @@ +import { collectCandidates } from '../../utils/get'; import { Command } from './base'; export class UpdateCommand extends Command { constructor(element, changes, options) { super(options.historyId); + this.element = element; + this.elementId = element.id; + + this.parent = element.parent; + this.context = element.context; + this.changes = changes; this.options = options; this.previousProps = this._createPreviousState(this.changes); } execute() { - this.element.apply(this.changes, { - ...this.options, - historyId: false, - }); + const element = this._resolveElement(false); + if (!element) return; + + element.apply(this.changes, { ...this.options, historyId: false }); } undo() { - this.element.apply(this.previousProps, { + const element = this._resolveElement(true); + if (!element) return; + + element.apply(this.previousProps, { ...this.options, mergeStrategy: 'replace', historyId: false, }); } + _resolveElement(isUndo) { + if (this.element && !this.element.destroyed) { + return this.element; + } + + const targetId = + isUndo && this.changes?.id ? this.changes.id : this.elementId; + if (this.parent && !this.parent.destroyed) { + const candidates = collectCandidates( + this.parent, + (node) => node.id === targetId, + ); + if (candidates[0]) { + this.element = candidates[0]; + return candidates[0]; + } + } + + if (this.context?.viewport && !this.context.viewport.destroyed) { + const candidates = collectCandidates( + this.context.viewport, + (node) => node.id === targetId, + ); + if (candidates[0]) { + this.element = candidates[0]; + this.parent = this.element.parent; + return candidates[0]; + } + } + + console.debug(`UpdateCommand: Element with ID ${targetId} not found`); + return null; + } + _createPreviousState(changes) { const slice = {}; if (!changes) { diff --git a/src/display/mixins/Animationsizeable.js b/src/display/mixins/Animationsizeable.js index 3a6166de..a30ff9d2 100644 --- a/src/display/mixins/Animationsizeable.js +++ b/src/display/mixins/Animationsizeable.js @@ -22,6 +22,10 @@ export const AnimationSizeable = (superClass) => { duration: animationDuration / 1000, ease: 'power2.inOut', onUpdate: () => { + if (this.destroyed) { + this.killTweens(); + return; + } this._applyPlacement({ placement: this.props.placement, margin: this.props.margin, diff --git a/src/display/mixins/Childrenable.js b/src/display/mixins/Childrenable.js index 4fcca114..520af6ea 100644 --- a/src/display/mixins/Childrenable.js +++ b/src/display/mixins/Childrenable.js @@ -11,7 +11,7 @@ export const Childrenable = (superClass) => { const MixedClass = class extends superClass { _applyChildren(relevantChanges, options) { let { children: childrenChanges } = relevantChanges; - let elements = [...this.children]; + const elements = [...this.children]; childrenChanges = validateAndPrepareChanges( elements, @@ -19,14 +19,6 @@ export const Childrenable = (superClass) => { mapDataSchema, ); - if (options.mergeStrategy === 'replace') { - elements.forEach((element) => { - this.removeChild(element); - element.destroy({ children: true }); - }); - elements = []; - } - for (const childChange of childrenChanges) { const idx = findIndexByPriority(elements, childChange); let element = null; @@ -40,6 +32,14 @@ export const Childrenable = (superClass) => { } element.apply(childChange, options); } + + if (options.mergeStrategy === 'replace') { + elements.forEach((element) => { + if (!element.type) return; // Don't remove children that are not managed by patchmap (e.g. raw PIXI objects) + this.removeChild(element); + element.destroy({ children: true }); + }); + } } _onChildUpdate(childId, changes, mergeStrategy) { diff --git a/src/display/mixins/Componentsable.js b/src/display/mixins/Componentsable.js index 572675e7..178ec34a 100644 --- a/src/display/mixins/Componentsable.js +++ b/src/display/mixins/Componentsable.js @@ -11,7 +11,7 @@ export const Componentsable = (superClass) => { const MixedClass = class extends superClass { _applyComponents(relevantChanges, options) { let { components: componentsChanges } = relevantChanges; - let components = [...this.children]; + const components = [...this.children]; componentsChanges = validateAndPrepareChanges( components, @@ -19,14 +19,6 @@ export const Componentsable = (superClass) => { componentArraySchema, ); - if (options.mergeStrategy === 'replace') { - components.forEach((component) => { - this.removeChild(component); - component.destroy({ children: true }); - }); - components = []; - } - for (const componentChange of componentsChanges) { const idx = findIndexByPriority(components, componentChange); let component = null; @@ -43,6 +35,14 @@ export const Componentsable = (superClass) => { options, ); } + + if (options.mergeStrategy === 'replace') { + components.forEach((component) => { + if (!component.type) return; // Don't remove components that are not managed by patchmap (e.g. raw PIXI objects) + this.removeChild(component); + component.destroy({ children: true }); + }); + } } _onChildUpdate(childId, changes, mergeStrategy) {