Skip to content

Commit 8d98ad9

Browse files
committed
Ensure we handle state and history the same across actions
1 parent eb1ea6b commit 8d98ad9

File tree

11 files changed

+231
-105
lines changed

11 files changed

+231
-105
lines changed

src/actions/Canvas.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,9 @@ export class Canvas extends Action {
223223
}
224224

225225
if (updateState === true) {
226-
this.engine.state('canvas').push(statement);
226+
this.engine.state({
227+
canvas: [...this.engine.state('canvas'), statement]
228+
});
227229
}
228230

229231
if (this.mode === 'background' || this.mode === 'character' || this.mode === 'displayable') {
@@ -236,9 +238,12 @@ export class Canvas extends Action {
236238
override async willRevert(): Promise<void> {
237239
this.containerSelector = `[data-component="canvas-container"][canvas="${this.name}"][mode="${this.mode}"]`;
238240
this.element = document.querySelector(this.containerSelector);
239-
this.object = this.element.props.object;
240241

241-
return Promise.resolve();
242+
if (this.element === null) {
243+
throw new Error(`Canvas element "${this.name}" (mode: ${this.mode}) not found in the DOM.`);
244+
}
245+
246+
this.object = this.element.props.object;
242247
}
243248

244249
override async revert(): Promise<void> {
@@ -247,20 +252,25 @@ export class Canvas extends Action {
247252
}
248253

249254
override async didRevert(): Promise<ActionRevertResult> {
250-
for (let i = this.engine.state('canvas').length - 1; i >= 0; i--) {
251-
const last = this.engine.state('canvas')[i];
252-
const [show, canvas, name, mode] = last.split(' ');
253-
if (name === this.name && mode === this.mode) {
254-
this.engine.state('canvas').splice(i, 1);
255-
break;
256-
}
257-
}
255+
let foundState = false;
256+
this.engine.state({
257+
canvas: this.engine.state('canvas').filter((item: string) => {
258+
if (!foundState) {
259+
const [, , name, mode] = item.split(' ');
260+
if (name === this.name && mode === this.mode) {
261+
foundState = true;
262+
return false;
263+
}
264+
}
265+
return true;
266+
})
267+
});
258268

259-
for (let i = this.engine.history('canvas').length - 1; i >= 0; i--) {
260-
const last = this.engine.history('canvas')[i];
261-
const [show, canvas, name, mode] = last.split(' ');
269+
const history = this.engine.history('canvas') as string[];
270+
for (let i = history.length - 1; i >= 0; i--) {
271+
const [, , name] = history[i].split(' ');
262272
if (name === this.name) {
263-
this.engine.history('canvas').splice(i, 1);
273+
history.splice(i, 1);
264274
break;
265275
}
266276
}

src/actions/HideCanvas.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Action from './../lib/Action';
22
import { Util } from '@aegis-framework/artemis';
3-
import { ActionApplyResult, ActionRevertResult } from '../lib/types';
3+
import { FancyError } from './../lib/FancyError';
4+
import { ActionApplyResult, ActionRevertResult, ActionInstance } from '../lib/types';
45

56
export class HideCanvas extends Action {
67

@@ -31,6 +32,18 @@ export class HideCanvas extends Action {
3132
}
3233
}
3334

35+
override async willApply(): Promise<void> {
36+
if (this.element === null) {
37+
FancyError.show('action:hide_canvas:not_shown', {
38+
name: this.name,
39+
statement: `<code class='language=javascript'>"${this._statement}"</code>`,
40+
label: this.engine.state('label'),
41+
step: this.engine.state('step')
42+
});
43+
throw new Error('Attempted to hide a canvas that was not being shown.');
44+
}
45+
}
46+
3447
override async apply(): Promise<void> {
3548
const { object } = this.element.props;
3649

@@ -61,14 +74,19 @@ export class HideCanvas extends Action {
6174
}
6275

6376
override async didApply(): Promise<ActionApplyResult> {
64-
for (let i = this.engine.state('canvas').length - 1; i >= 0; i--) {
65-
const last = this.engine.state('canvas')[i];
66-
const [show, canvas, name, mode] = last.split(' ');
67-
if (name === this.name) {
68-
this.engine.state('canvas').splice(i, 1);
69-
break;
70-
}
71-
}
77+
let found = false;
78+
this.engine.state({
79+
canvas: this.engine.state('canvas').filter((item: string) => {
80+
if (!found) {
81+
const [, , name] = item.split(' ');
82+
if (name === this.name) {
83+
found = true;
84+
return false;
85+
}
86+
}
87+
return true;
88+
})
89+
});
7290
return { advance: true };
7391
}
7492

@@ -78,10 +96,13 @@ export class HideCanvas extends Action {
7896
const last = canvasHistory[i];
7997
const [, , name] = last.split(' ');
8098
if (name === this.name) {
81-
canvasHistory.splice(i, 1);
82-
await this.engine.run(last, false);
99+
const action = this.engine.prepareAction(last, { cycle: 'Application' }) as ActionInstance | null;
100+
if (action !== null) {
101+
await action.willApply();
102+
await action.apply();
103+
await action.didApply({ updateHistory: false, updateState: true });
104+
}
83105
return;
84-
85106
}
86107
}
87108
}

src/actions/HideCharacterLayer.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,14 @@ export class HideCharacterLayer extends Action {
105105
this.element.remove();
106106
}
107107

108-
const parentAsComponent = this.parent.get(0);
109-
const stateLayers = parentAsComponent.state.layers;
110-
delete stateLayers[this.layer];
111-
112-
parentAsComponent.setState({
113-
layers: stateLayers
114-
});
108+
const parentAsComponent = this.parent.get(0) as (HTMLElement & { state?: { layers?: Record<string, unknown> }; setState?: (state: Record<string, unknown>) => void }) | undefined;
109+
if (parentAsComponent?.state && parentAsComponent?.setState) {
110+
const stateLayers = parentAsComponent.state.layers || {};
111+
const { [this.layer]: _, ...remainingLayers } = stateLayers;
112+
parentAsComponent.setState({
113+
layers: remainingLayers
114+
});
115+
}
115116
}
116117

117118
override async didApply({ updateHistory = true, updateState = true } = {}): Promise<ActionApplyResult> {

src/actions/HideImage.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Action from './../lib/Action';
2+
import { FancyError } from './../lib/FancyError';
23
import { ActionApplyResult, ActionRevertResult } from '../lib/types';
34

45
export class HideImage extends Action {
@@ -27,6 +28,18 @@ export class HideImage extends Action {
2728
this.classes = this.classes.filter((c) => (c !== 'at' && c !== 'with'));
2829
}
2930

31+
override async willApply(): Promise<void> {
32+
if (!this.element.exists()) {
33+
FancyError.show('action:hide_image:not_shown', {
34+
asset: this.asset,
35+
statement: `<code class='language=javascript'>"${this._statement}"</code>`,
36+
label: this.engine.state('label'),
37+
step: this.engine.state('step')
38+
});
39+
throw new Error('Attempted to hide an image that was not being shown.');
40+
}
41+
}
42+
3043
override async apply(): Promise<void> {
3144
const currentPosition = this.element.data('position');
3245
const position = (this._statement as string).match(/at\s(\S*)/);

src/actions/HideVideo.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Action from './../lib/Action';
22
import Video from './Video';
3-
import { ActionApplyResult, ActionRevertResult } from '../lib/types';
3+
import { ActionApplyResult, ActionRevertResult, ActionInstance } from '../lib/types';
44

55
export class HideVideo extends Action {
66

@@ -54,24 +54,34 @@ export class HideVideo extends Action {
5454
}
5555

5656
override async didApply(): Promise<ActionApplyResult> {
57-
for (let i = this.engine.state('videos').length - 1; i >= 0; i--) {
58-
const last = this.engine.state('videos')[i];
59-
const [show, video, name, mode] = last.split(' ');
60-
if (name === this.name) {
61-
this.engine.state('videos').splice(i, 1);
62-
break;
63-
}
64-
}
57+
let found = false;
58+
this.engine.state({
59+
videos: this.engine.state('videos').filter((item: string) => {
60+
if (!found) {
61+
const [, , name] = item.split(' ');
62+
if (name === this.name) {
63+
found = true;
64+
return false;
65+
}
66+
}
67+
return true;
68+
})
69+
});
6570
return { advance: true };
6671
}
6772

6873
override async revert(): Promise<void> {
69-
for (let i = this.engine.history('video').length - 1; i >= 0; i--) {
70-
const last = this.engine.history('video')[i];
71-
const [show, video, name, mode] = last.split(' ');
74+
const history = this.engine.history('video') as string[];
75+
for (let i = history.length - 1; i >= 0; i--) {
76+
const last = history[i];
77+
const [, , name] = last.split(' ');
7278
if (name === this.name) {
73-
this.engine.history('video').splice(i, 1);
74-
await this.engine.run(last, false);
79+
const action = this.engine.prepareAction(last, { cycle: 'Application' }) as ActionInstance | null;
80+
if (action !== null) {
81+
await action.willApply();
82+
await action.apply();
83+
await action.didApply({ updateHistory: false, updateState: true });
84+
}
7585
return;
7686
}
7787
}

src/actions/Play.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,13 @@ export class Play extends Action {
417417
const currentState = this.engine.state(this.type);
418418

419419
if (typeof this.mediaKey !== 'undefined') {
420-
this.engine.history(this.type).pop();
420+
const history = this.engine.history(this.type) as string[];
421+
for (let i = history.length - 1; i >= 0; i--) {
422+
if (history[i] === this._statement) {
423+
history.splice(i, 1);
424+
break;
425+
}
426+
}
421427
const filteredState = currentState.filter((m: MediaStateItem) => m.statement !== this._statement);
422428
this.engine.state({ [this.type]: filteredState });
423429
} else if (this.player instanceof Array) {

src/actions/ShowCharacter.ts

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Action from './../lib/Action';
22
import { $_ } from '@aegis-framework/artemis';
3+
import { FancyError } from './../lib/FancyError';
34
import { ActionApplyResult, ActionRevertResult, ActionInstance, CharacterHistoryItem } from '../lib/types';
45

56
export class ShowCharacter extends Action {
@@ -81,12 +82,36 @@ export class ShowCharacter extends Action {
8182
}
8283

8384
} else {
84-
// TODO: Add Fancy Error when the specified character does not exist
8585
this.sprite = '';
8686
this.classes = [];
8787
}
8888
}
8989

90+
override async willApply(): Promise<void> {
91+
if (typeof this.character === 'undefined') {
92+
FancyError.show('action:show_character:character_not_found', {
93+
asset: this.asset,
94+
availableCharacters: Object.keys(this.engine.characters()),
95+
statement: `<code class='language=javascript'>"${this._statement}"</code>`,
96+
label: this.engine.state('label'),
97+
step: this.engine.state('step')
98+
});
99+
throw new Error(`Character "${this.asset}" not found.`);
100+
}
101+
102+
if (typeof this.image === 'undefined') {
103+
FancyError.show('action:show_character:sprite_not_found', {
104+
asset: this.asset,
105+
sprite: this.sprite,
106+
availableSprites: this.character.sprites ? Object.keys(this.character.sprites) : [],
107+
statement: `<code class='language=javascript'>"${this._statement}"</code>`,
108+
label: this.engine.state('label'),
109+
step: this.engine.state('step')
110+
});
111+
throw new Error(`Sprite "${this.sprite}" not found for character "${this.asset}".`);
112+
}
113+
}
114+
90115
override async apply(): Promise<void> {
91116
// show [character] [expression] at [position] with [animation] [infinite]
92117
// 0 1 2 3 4 5 6 7
@@ -467,16 +492,14 @@ export class ShowCharacter extends Action {
467492
}
468493
return;
469494
} else {
470-
if (typeof this.image === 'object') {
471-
for (let j = characterLayerHistory.length - 1; j >= 0; j--) {
472-
const { parent } = characterLayerHistory[j];
473-
if (typeof parent === 'string') {
474-
const [, , _asset] = parent.split(' ');
475-
476-
if (_asset === this.asset) {
477-
characterLayerHistory.splice(j, 1);
478-
break;
479-
}
495+
for (let j = characterLayerHistory.length - 1; j >= 0; j--) {
496+
const { parent } = characterLayerHistory[j];
497+
if (typeof parent === 'string') {
498+
const [, , _asset] = parent.split(' ');
499+
500+
if (_asset === this.asset) {
501+
characterLayerHistory.splice(j, 1);
502+
break;
480503
}
481504
}
482505
}
@@ -498,27 +521,32 @@ export class ShowCharacter extends Action {
498521
// If the script didn't return on the for cycle above, it means either the
499522
// history didn't have any items left or, the character was not found.
500523
// In that case, we simply remove the character from the state.
501-
this.engine.state({
524+
const updatedState: Record<string, unknown> = {
502525
characters: [
503526
...this.engine.state('characters').filter((item: any) => {
504527
if (typeof item === 'string') {
505-
const [show, character, asset, sprite] = item.split(' ');
528+
const [, , asset] = item.split(' ');
506529
return asset !== this.asset;
507530
}
508531
return false;
509532
}),
510533
],
511-
characterLayers: [
534+
};
535+
536+
if (this.engine.setting('ExperimentalFeatures') === true) {
537+
updatedState.characterLayers = [
512538
...this.engine.state('characterLayers').filter((item: any) => {
513539
if (typeof item === 'string') {
514-
const [show, character, asset, sprite] = item.split(' ');
515-
const [id, layer] = asset.split(':');
540+
const [, , asset] = item.split(' ');
541+
const [id] = asset.split(':');
516542
return id !== this.asset;
517543
}
518544
return false;
519545
}),
520-
]
521-
});
546+
];
547+
}
548+
549+
this.engine.state(updatedState);
522550
}
523551

524552
override async didRevert(): Promise<ActionRevertResult> {

0 commit comments

Comments
 (0)