@vnejs/plugins.text.wall 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import "@vnejs/contracts.text.wall";
2
2
  import { regPlugin } from "@vnejs/shared";
3
- import { PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.text.wall";
3
+ import { PLUGIN_NAME, SUBSCRIBE_EVENTS, CONSTANTS } from "@vnejs/contracts.text.wall";
4
4
  import { TextWall } from "./modules/wall.js";
5
- regPlugin(PLUGIN_NAME, { events: SUBSCRIBE_EVENTS }, [TextWall]);
5
+ import { TextWallRecalc } from "./modules/recalc.js";
6
+ import { TextWallExec } from "./modules/exec.js";
7
+ import { TextWallEmitBefore } from "./modules/emit.before.js";
8
+ regPlugin(PLUGIN_NAME, { events: SUBSCRIBE_EVENTS, constants: CONSTANTS }, [TextWallEmitBefore, TextWallExec, TextWallRecalc, TextWall]);
@@ -0,0 +1,9 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import type { TextEmitBeforePayload } from "@vnejs/contracts.text";
3
+ import type { ModuleGlobalStateTextWall } from "@vnejs/contracts.text.wall";
4
+ import type { TextWallPluginConstants, TextWallPluginEvents, TextWallPluginParams, TextWallPluginSettings } from "../types.js";
5
+ export declare class TextWallEmitBefore extends ModuleCore<TextWallPluginEvents, TextWallPluginConstants, TextWallPluginSettings, TextWallPluginParams, ModuleGlobalStateTextWall> {
6
+ name: string;
7
+ init: () => Promise<unknown[]> | undefined;
8
+ onTextEmitBefore: ({ isExtend }?: TextEmitBeforePayload) => Promise<unknown[] | undefined>;
9
+ }
@@ -0,0 +1,21 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import { getTextSpeakerInfo } from "@vnejs/contracts.text";
3
+ export class TextWallEmitBefore extends ModuleCore {
4
+ name = "text.wall.emit.before";
5
+ init = () => this.emit(this.EVENTS.TEXT.EMIT_BEFORE_REG, { handler: this.onTextEmitBefore });
6
+ onTextEmitBefore = async ({ isExtend = false } = {}) => {
7
+ const state = this.globalState["text.wall"];
8
+ if (isExtend || !state.isStarted || this.shared.isTextWallSetStateInProcess)
9
+ return;
10
+ if (state.shouldSkip)
11
+ return this.emit(this.EVENTS.TEXT_WALL.STATE_UPDATE, { shouldSkip: false });
12
+ const { texts, tokens, uid } = this.globalState.text;
13
+ const speakerState = this.globalState["text.speaker"];
14
+ const { speaker } = speakerState;
15
+ const speakerInfo = await this.emitClone(getTextSpeakerInfo(speakerState, speaker));
16
+ const wall = await this.emitClone(state.wall);
17
+ const wallElement = await this.emitClone({ texts, tokens, uid, speaker, speakerInfo });
18
+ await this.emit(this.EVENTS.TEXT_WALL.STATE_UPDATE, { wall: [...wall, wallElement] });
19
+ await this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.globalState["text.wall"].wall });
20
+ };
21
+ }
@@ -0,0 +1,10 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import type { ModuleGlobalStateTextWall } from "@vnejs/contracts.text.wall";
3
+ import type { LineExecHandlerArg } from "@vnejs/module.core";
4
+ import type { TextWallPluginConstants, TextWallPluginEvents, TextWallPluginParams, TextWallPluginSettings } from "../types.js";
5
+ export declare class TextWallExec extends ModuleCore<TextWallPluginEvents, TextWallPluginConstants, TextWallPluginSettings, TextWallPluginParams, ModuleGlobalStateTextWall> {
6
+ name: string;
7
+ init: () => Promise<unknown[]> | undefined;
8
+ onLineExec: ({ line }?: LineExecHandlerArg) => Promise<void>;
9
+ emitAction: (action: string) => Promise<unknown[]> | undefined;
10
+ }
@@ -0,0 +1,21 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import { tokenizeExecLine } from "@vnejs/helpers";
3
+ export class TextWallExec extends ModuleCore {
4
+ name = "text.wall.exec";
5
+ init = () => this.emit(this.EVENTS.SCENARIO.LINE_EXEC_REG, { module: "text.wall", handler: this.onLineExec });
6
+ onLineExec = async ({ line = "" } = {}) => {
7
+ const [action = ""] = tokenizeExecLine(line).map(String);
8
+ await this.emitAction(action);
9
+ await this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.globalState["text.wall"].wall });
10
+ this.emitNext();
11
+ };
12
+ emitAction = (action) => {
13
+ const event = this.EVENTS.TEXT_WALL.STATE_UPDATE;
14
+ if (action === this.CONST.TEXT_WALL.EXEC_ACTIONS.CLEAR)
15
+ return this.emit(event, { wall: [], shouldSkip: true });
16
+ if (action === this.CONST.TEXT_WALL.EXEC_ACTIONS.START)
17
+ return this.emit(event, { wall: [], isStarted: true, shouldSkip: true });
18
+ if (action === this.CONST.TEXT_WALL.EXEC_ACTIONS.END)
19
+ return this.emit(event, { wall: [], isStarted: false, shouldSkip: true });
20
+ };
21
+ }
@@ -0,0 +1,17 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import type { TokenizedChar } from "@vnejs/contracts.text";
3
+ import type { ModuleGlobalStateTextWall, WallElement } from "@vnejs/contracts.text.wall";
4
+ import type { TextWallPluginConstants, TextWallPluginEvents, TextWallPluginParams, TextWallPluginSettings } from "../types.js";
5
+ export declare class TextWallRecalc extends ModuleCore<TextWallPluginEvents, TextWallPluginConstants, TextWallPluginSettings, TextWallPluginParams, ModuleGlobalStateTextWall> {
6
+ name: string;
7
+ subscribe: () => void;
8
+ onTextRecalc: () => Promise<void>;
9
+ recalcWallObjToken: (wallObj: WallElement) => Promise<{
10
+ texts: import("@vnejs/contracts.text").TextItem[];
11
+ uid: string;
12
+ speaker: string;
13
+ speakerInfo: import("@vnejs/contracts.text").TextSpeakerEntry;
14
+ tokens: TokenizedChar[];
15
+ }>;
16
+ getRealText: (text: string, args: Record<string, unknown>) => Promise<string>;
17
+ }
@@ -0,0 +1,22 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import { tokenizeText } from "@vnejs/helpers";
3
+ export class TextWallRecalc extends ModuleCore {
4
+ name = "text.wall.recalc";
5
+ subscribe = () => {
6
+ this.on(this.EVENTS.TEXT.RECALC, this.onTextRecalc);
7
+ };
8
+ onTextRecalc = async () => {
9
+ const state = this.globalState["text.wall"];
10
+ if (!state.wall.length)
11
+ return;
12
+ await this.emit(this.EVENTS.TEXT_WALL.STATE_UPDATE, { wall: await Promise.all(state.wall.map(this.recalcWallObjToken)) });
13
+ await this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.globalState["text.wall"].wall });
14
+ };
15
+ recalcWallObjToken = async (wallObj) => {
16
+ const tokens = [];
17
+ for (let i = 0; i < wallObj.texts.length; i++)
18
+ tokens.push(...tokenizeText(await this.getRealText(wallObj.texts[i].text, wallObj.texts[i].args)));
19
+ return { ...wallObj, tokens };
20
+ };
21
+ getRealText = (text, args) => this.emitOne(this.EVENTS.TEXT.REPLACE, { text, args, label: this.globalState.scenario.label, state: this.globalState });
22
+ }
@@ -1,27 +1,13 @@
1
1
  import { ModuleCore } from "@vnejs/module.core";
2
2
  import type { ModuleGlobalStateRegistry } from "@vnejs/module.core";
3
- import type { TextEmitBeforePayload, TokenizedChar } from "@vnejs/contracts.text";
4
- import type { ModuleGlobalStateTextWall, WallElement } from "@vnejs/contracts.text.wall";
5
- import type { LineExecHandlerArg } from "@vnejs/module.core";
3
+ import type { ModuleGlobalStateTextWall } from "@vnejs/contracts.text.wall";
6
4
  import type { TextWallPluginConstants, TextWallPluginEvents, TextWallPluginParams, TextWallPluginSettings } from "../types.js";
7
5
  export declare class TextWall extends ModuleCore<TextWallPluginEvents, TextWallPluginConstants, TextWallPluginSettings, TextWallPluginParams, ModuleGlobalStateTextWall> {
8
6
  name: string;
9
- isSetStateInProcess: boolean;
10
7
  subscribe: () => void;
11
- init: () => Promise<[unknown[] | undefined, unknown[] | undefined]>;
12
- emitWallChanged: () => Promise<unknown[]> | undefined;
13
- onLineExec: ({ line }?: LineExecHandlerArg) => Promise<void>;
14
- onTextEmitBefore: ({ isExtend }?: TextEmitBeforePayload) => Promise<void>;
15
- onTextRecalc: () => Promise<unknown[] | undefined>;
8
+ init: () => void;
9
+ onStateUpdate: (payload: Partial<ModuleGlobalStateTextWall>) => void;
16
10
  onStateSet: (payload: ModuleGlobalStateRegistry) => Promise<void>;
17
11
  onStateClear: () => Promise<unknown[] | undefined>;
18
- getRealText: (text: string, args: Record<string, unknown>) => Promise<string>;
19
- recalcWallObjToken: (wallObj: WallElement) => Promise<{
20
- tokens: TokenizedChar[];
21
- texts: import("@vnejs/contracts.text").TextItem[];
22
- uid: string;
23
- speaker: string;
24
- speakerInfo: import("@vnejs/contracts.text").TextSpeakerEntry;
25
- }>;
26
12
  getDefaultState: () => ModuleGlobalStateTextWall;
27
13
  }
@@ -1,66 +1,24 @@
1
1
  import { ModuleCore } from "@vnejs/module.core";
2
- import { tokenizeExecLine, tokenizeText } from "@vnejs/helpers";
3
- import { getTextSpeakerInfo } from "@vnejs/contracts.text";
4
2
  export class TextWall extends ModuleCore {
5
3
  name = "text.wall";
6
- isSetStateInProcess = false;
7
4
  subscribe = () => {
8
- this.on(this.EVENTS.TEXT.RECALC, this.onTextRecalc);
5
+ this.on(this.EVENTS.TEXT_WALL.STATE_UPDATE, this.onStateUpdate);
9
6
  this.on(this.EVENTS.STATE.CLEAR, this.onStateClear);
10
7
  this.on(this.EVENTS.STATE.SET, this.onStateSet);
11
8
  };
12
- init = () => Promise.all([
13
- this.emit(this.EVENTS.SCENARIO.LINE_EXEC_REG, { module: this.name, handler: this.onLineExec }),
14
- this.emit(this.EVENTS.TEXT.EMIT_BEFORE_REG, { handler: this.onTextEmitBefore }),
15
- ]);
16
- emitWallChanged = () => this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.state.wall });
17
- onLineExec = async ({ line = "" } = {}) => {
18
- const [action = ""] = tokenizeExecLine(line).map(String);
19
- if (action === "clear")
20
- this.updateState({ wall: [], shouldSkip: true });
21
- if (action === "start")
22
- this.updateState({ wall: [], isStarted: true, shouldSkip: true });
23
- if (action === "end")
24
- this.updateState({ wall: [], isStarted: false, shouldSkip: true });
25
- await this.emitWallChanged();
26
- this.emitNext();
27
- };
28
- onTextEmitBefore = async ({ isExtend = false } = {}) => {
29
- if (isExtend || !this.state.isStarted || this.isSetStateInProcess)
30
- return;
31
- if (this.state.shouldSkip)
32
- return this.updateState({ shouldSkip: false });
33
- const { texts, tokens, uid } = this.globalState.text;
34
- const speakerState = this.globalState["text.speaker"];
35
- const { speaker } = speakerState;
36
- const speakerInfo = await this.emitClone(getTextSpeakerInfo(speakerState, speaker));
37
- const wall = await this.emitClone(this.state.wall);
38
- const wallElement = await this.emitClone({ texts, tokens, uid, speaker, speakerInfo });
39
- this.updateState({ wall: [...wall, wallElement] });
40
- await this.emitWallChanged();
41
- };
42
- onTextRecalc = async () => {
43
- if (!this.state.wall.length)
44
- return;
45
- this.updateState({ wall: await Promise.all(this.state.wall.map(this.recalcWallObjToken)) });
46
- return this.emitWallChanged();
9
+ init = () => {
10
+ this.shared.isTextWallSetStateInProcess = false;
47
11
  };
12
+ onStateUpdate = (payload) => this.updateState(payload);
48
13
  onStateSet = async (payload) => {
49
- this.isSetStateInProcess = true;
14
+ this.shared.isTextWallSetStateInProcess = true;
50
15
  await this.onSetStateClone(payload);
51
- await this.emitWallChanged();
52
- this.isSetStateInProcess = false;
16
+ await this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.state.wall });
17
+ this.shared.isTextWallSetStateInProcess = false;
53
18
  };
54
19
  onStateClear = async () => {
55
20
  this.setDefaultState();
56
- return this.emitWallChanged();
57
- };
58
- getRealText = (text, args) => this.emitOne(this.EVENTS.TEXT.REPLACE, { text, args, label: this.globalState.scenario.label, state: this.globalState });
59
- recalcWallObjToken = async (wallObj) => {
60
- const tokens = [];
61
- for (let i = 0; i < wallObj.texts.length; i++)
62
- tokens.push(...tokenizeText(await this.getRealText(wallObj.texts[i].text, wallObj.texts[i].args)));
63
- return { ...wallObj, tokens };
21
+ return this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.state.wall });
64
22
  };
65
23
  getDefaultState = () => ({ wall: [], isStarted: false, shouldSkip: false });
66
24
  }
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ModuleCoreConstants, ModuleCoreEvents, ModuleCoreParams, ModuleCoreSettings } from "@vnejs/module.core";
2
2
  import type { PluginName as TextPluginName, SubscribeEvents as TextSubscribeEvents } from "@vnejs/contracts.text";
3
- import type { PluginName, SubscribeEvents } from "@vnejs/contracts.text.wall";
3
+ import type { PluginName, SubscribeEvents, Constants } from "@vnejs/contracts.text.wall";
4
4
  export type TextWallPluginEvents = ModuleCoreEvents & Record<PluginName, SubscribeEvents> & Record<TextPluginName, TextSubscribeEvents>;
5
- export type TextWallPluginConstants = ModuleCoreConstants;
5
+ export type TextWallPluginConstants = ModuleCoreConstants & Record<PluginName, Constants>;
6
6
  export type TextWallPluginSettings = ModuleCoreSettings;
7
7
  export type TextWallPluginParams = ModuleCoreParams;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vnejs/plugins.text.wall",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  import "@vnejs/contracts.text.wall";
2
2
 
3
3
  import { regPlugin } from "@vnejs/shared";
4
- import { PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.text.wall";
4
+ import { PLUGIN_NAME, SUBSCRIBE_EVENTS, CONSTANTS } from "@vnejs/contracts.text.wall";
5
5
 
6
6
  import { TextWall } from "./modules/wall.js";
7
+ import { TextWallRecalc } from "./modules/recalc.js";
8
+ import { TextWallExec } from "./modules/exec.js";
9
+ import { TextWallEmitBefore } from "./modules/emit.before.js";
7
10
 
8
- regPlugin(PLUGIN_NAME, { events: SUBSCRIBE_EVENTS }, [TextWall]);
11
+ regPlugin(PLUGIN_NAME, { events: SUBSCRIBE_EVENTS, constants: CONSTANTS }, [TextWallEmitBefore, TextWallExec, TextWallRecalc, TextWall]);
@@ -0,0 +1,37 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+
3
+ import type { TextEmitBeforePayload } from "@vnejs/contracts.text";
4
+ import { getTextSpeakerInfo } from "@vnejs/contracts.text";
5
+ import type { ModuleGlobalStateTextWall, WallElement } from "@vnejs/contracts.text.wall";
6
+
7
+ import type { TextWallPluginConstants, TextWallPluginEvents, TextWallPluginParams, TextWallPluginSettings } from "../types.js";
8
+
9
+ export class TextWallEmitBefore extends ModuleCore<
10
+ TextWallPluginEvents,
11
+ TextWallPluginConstants,
12
+ TextWallPluginSettings,
13
+ TextWallPluginParams,
14
+ ModuleGlobalStateTextWall
15
+ > {
16
+ name = "text.wall.emit.before";
17
+
18
+ init = () => this.emit(this.EVENTS.TEXT.EMIT_BEFORE_REG, { handler: this.onTextEmitBefore });
19
+
20
+ onTextEmitBefore = async ({ isExtend = false }: TextEmitBeforePayload = {}) => {
21
+ const state = this.globalState["text.wall"];
22
+
23
+ if (isExtend || !state.isStarted || this.shared.isTextWallSetStateInProcess) return;
24
+ if (state.shouldSkip) return this.emit(this.EVENTS.TEXT_WALL.STATE_UPDATE, { shouldSkip: false });
25
+
26
+ const { texts, tokens, uid } = this.globalState.text;
27
+ const speakerState = this.globalState["text.speaker"];
28
+ const { speaker } = speakerState;
29
+ const speakerInfo = await this.emitClone(getTextSpeakerInfo(speakerState, speaker));
30
+
31
+ const wall = await this.emitClone<WallElement[]>(state.wall);
32
+ const wallElement = await this.emitClone<WallElement>({ texts, tokens, uid, speaker, speakerInfo });
33
+
34
+ await this.emit(this.EVENTS.TEXT_WALL.STATE_UPDATE, { wall: [...wall, wallElement] });
35
+ await this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.globalState["text.wall"].wall });
36
+ };
37
+ }
@@ -0,0 +1,37 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import { tokenizeExecLine } from "@vnejs/helpers";
3
+
4
+ import type { ModuleGlobalStateTextWall } from "@vnejs/contracts.text.wall";
5
+ import type { LineExecHandlerArg } from "@vnejs/module.core";
6
+
7
+ import type { TextWallPluginConstants, TextWallPluginEvents, TextWallPluginParams, TextWallPluginSettings } from "../types.js";
8
+
9
+ export class TextWallExec extends ModuleCore<
10
+ TextWallPluginEvents,
11
+ TextWallPluginConstants,
12
+ TextWallPluginSettings,
13
+ TextWallPluginParams,
14
+ ModuleGlobalStateTextWall
15
+ > {
16
+ name = "text.wall.exec";
17
+
18
+ init = () => this.emit(this.EVENTS.SCENARIO.LINE_EXEC_REG, { module: "text.wall", handler: this.onLineExec });
19
+
20
+ onLineExec = async ({ line = "" }: LineExecHandlerArg = {}) => {
21
+ const [action = ""] = tokenizeExecLine(line).map(String);
22
+
23
+ await this.emitAction(action);
24
+
25
+ await this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.globalState["text.wall"].wall });
26
+
27
+ this.emitNext();
28
+ };
29
+
30
+ emitAction = (action: string) => {
31
+ const event = this.EVENTS.TEXT_WALL.STATE_UPDATE;
32
+
33
+ if (action === this.CONST.TEXT_WALL.EXEC_ACTIONS.CLEAR) return this.emit(event, { wall: [], shouldSkip: true });
34
+ if (action === this.CONST.TEXT_WALL.EXEC_ACTIONS.START) return this.emit(event, { wall: [], isStarted: true, shouldSkip: true });
35
+ if (action === this.CONST.TEXT_WALL.EXEC_ACTIONS.END) return this.emit(event, { wall: [], isStarted: false, shouldSkip: true });
36
+ };
37
+ }
@@ -0,0 +1,40 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import { tokenizeText } from "@vnejs/helpers";
3
+
4
+ import type { TokenizedChar } from "@vnejs/contracts.text";
5
+ import type { ModuleGlobalStateTextWall, WallElement } from "@vnejs/contracts.text.wall";
6
+
7
+ import type { TextWallPluginConstants, TextWallPluginEvents, TextWallPluginParams, TextWallPluginSettings } from "../types.js";
8
+
9
+ export class TextWallRecalc extends ModuleCore<
10
+ TextWallPluginEvents,
11
+ TextWallPluginConstants,
12
+ TextWallPluginSettings,
13
+ TextWallPluginParams,
14
+ ModuleGlobalStateTextWall
15
+ > {
16
+ name = "text.wall.recalc";
17
+
18
+ subscribe = () => {
19
+ this.on(this.EVENTS.TEXT.RECALC, this.onTextRecalc);
20
+ };
21
+
22
+ onTextRecalc = async () => {
23
+ const state = this.globalState["text.wall"];
24
+
25
+ if (!state.wall.length) return;
26
+
27
+ await this.emit(this.EVENTS.TEXT_WALL.STATE_UPDATE, { wall: await Promise.all(state.wall.map(this.recalcWallObjToken)) });
28
+ await this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.globalState["text.wall"].wall });
29
+ };
30
+
31
+ recalcWallObjToken = async (wallObj: WallElement) => {
32
+ const tokens: TokenizedChar[] = [];
33
+
34
+ for (let i = 0; i < wallObj.texts.length; i++) tokens.push(...tokenizeText(await this.getRealText(wallObj.texts[i].text, wallObj.texts[i].args)));
35
+
36
+ return { ...wallObj, tokens };
37
+ };
38
+ getRealText = (text: string, args: Record<string, unknown>) =>
39
+ this.emitOne(this.EVENTS.TEXT.REPLACE, { text, args, label: this.globalState.scenario.label, state: this.globalState }) as Promise<string>;
40
+ }
@@ -1,11 +1,7 @@
1
1
  import { ModuleCore } from "@vnejs/module.core";
2
2
  import type { ModuleGlobalStateRegistry } from "@vnejs/module.core";
3
- import { tokenizeExecLine, tokenizeText } from "@vnejs/helpers";
4
3
 
5
- import type { TextEmitBeforePayload, TokenizedChar } from "@vnejs/contracts.text";
6
- import { getTextSpeakerInfo } from "@vnejs/contracts.text";
7
- import type { ModuleGlobalStateTextWall, WallElement } from "@vnejs/contracts.text.wall";
8
- import type { LineExecHandlerArg } from "@vnejs/module.core";
4
+ import type { ModuleGlobalStateTextWall } from "@vnejs/contracts.text.wall";
9
5
 
10
6
  import type { TextWallPluginConstants, TextWallPluginEvents, TextWallPluginParams, TextWallPluginSettings } from "../types.js";
11
7
 
@@ -18,84 +14,31 @@ export class TextWall extends ModuleCore<
18
14
  > {
19
15
  name = "text.wall";
20
16
 
21
- isSetStateInProcess = false;
22
-
23
17
  subscribe = () => {
24
- this.on(this.EVENTS.TEXT.RECALC, this.onTextRecalc);
18
+ this.on(this.EVENTS.TEXT_WALL.STATE_UPDATE, this.onStateUpdate);
25
19
 
26
20
  this.on(this.EVENTS.STATE.CLEAR, this.onStateClear);
27
21
  this.on(this.EVENTS.STATE.SET, this.onStateSet);
28
22
  };
29
23
 
30
- init = () =>
31
- Promise.all([
32
- this.emit(this.EVENTS.SCENARIO.LINE_EXEC_REG, { module: this.name, handler: this.onLineExec }),
33
- this.emit(this.EVENTS.TEXT.EMIT_BEFORE_REG, { handler: this.onTextEmitBefore }),
34
- ]);
35
-
36
- emitWallChanged = () => this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.state.wall });
37
-
38
- onLineExec = async ({ line = "" }: LineExecHandlerArg = {}) => {
39
- const [action = ""] = tokenizeExecLine(line).map(String);
40
-
41
- if (action === "clear") this.updateState({ wall: [], shouldSkip: true });
42
- if (action === "start") this.updateState({ wall: [], isStarted: true, shouldSkip: true });
43
- if (action === "end") this.updateState({ wall: [], isStarted: false, shouldSkip: true });
44
-
45
- await this.emitWallChanged();
46
-
47
- this.emitNext();
24
+ init = () => {
25
+ this.shared.isTextWallSetStateInProcess = false;
48
26
  };
49
27
 
50
- onTextEmitBefore = async ({ isExtend = false }: TextEmitBeforePayload = {}) => {
51
- if (isExtend || !this.state.isStarted || this.isSetStateInProcess) return;
52
- if (this.state.shouldSkip) return this.updateState({ shouldSkip: false });
53
-
54
- const { texts, tokens, uid } = this.globalState.text;
55
- const speakerState = this.globalState["text.speaker"];
56
- const { speaker } = speakerState;
57
- const speakerInfo = await this.emitClone(getTextSpeakerInfo(speakerState, speaker));
58
-
59
- const wall = await this.emitClone<WallElement[]>(this.state.wall);
60
- const wallElement = await this.emitClone<WallElement>({ texts, tokens, uid, speaker, speakerInfo });
61
-
62
- this.updateState({ wall: [...wall, wallElement] });
63
-
64
- await this.emitWallChanged();
65
- };
66
-
67
- onTextRecalc = async () => {
68
- if (!this.state.wall.length) return;
69
-
70
- this.updateState({ wall: await Promise.all(this.state.wall.map(this.recalcWallObjToken)) });
71
-
72
- return this.emitWallChanged();
73
- };
28
+ onStateUpdate = (payload: Partial<ModuleGlobalStateTextWall>) => this.updateState(payload);
74
29
 
75
30
  onStateSet = async (payload: ModuleGlobalStateRegistry) => {
76
- this.isSetStateInProcess = true;
31
+ this.shared.isTextWallSetStateInProcess = true;
77
32
 
78
33
  await this.onSetStateClone(payload);
79
- await this.emitWallChanged();
34
+ await this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.state.wall });
80
35
 
81
- this.isSetStateInProcess = false;
36
+ this.shared.isTextWallSetStateInProcess = false;
82
37
  };
83
-
84
38
  onStateClear = async () => {
85
39
  this.setDefaultState();
86
40
 
87
- return this.emitWallChanged();
88
- };
89
-
90
- getRealText = (text: string, args: Record<string, unknown>) =>
91
- this.emitOne(this.EVENTS.TEXT.REPLACE, { text, args, label: this.globalState.scenario.label, state: this.globalState }) as Promise<string>;
92
-
93
- recalcWallObjToken = async (wallObj: WallElement) => {
94
- const tokens: TokenizedChar[] = [];
95
-
96
- for (let i = 0; i < wallObj.texts.length; i++) tokens.push(...tokenizeText(await this.getRealText(wallObj.texts[i].text, wallObj.texts[i].args)));
97
-
98
- return { ...wallObj, tokens };
41
+ return this.emit(this.EVENTS.TEXT_WALL.CHANGED, { wall: this.state.wall });
99
42
  };
100
43
 
101
44
  getDefaultState = (): ModuleGlobalStateTextWall => ({ wall: [], isStarted: false, shouldSkip: false });
@@ -0,0 +1,86 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+
3
+ import { spyEvent } from "@vnejs/test-utils";
4
+
5
+ import { TextWallEmitBefore } from "../modules/emit.before.js";
6
+ import { createTextWallTestModule, createTextWallWithModule, registerTextWallPluginVne, SUBSCRIBE_EVENTS, TEXT_EVENTS } from "./setup.js";
7
+
8
+ describe("TextWallEmitBefore", () => {
9
+ beforeEach(() => {
10
+ registerTextWallPluginVne();
11
+ });
12
+
13
+ it("init registers emit before handler", async () => {
14
+ const { module, observer } = createTextWallTestModule(TextWallEmitBefore, { subscribe: false });
15
+ const emitBeforeReg = spyEvent(observer, TEXT_EVENTS.EMIT_BEFORE_REG);
16
+
17
+ await (module as TextWallEmitBefore).init();
18
+
19
+ expect(emitBeforeReg).toHaveBeenCalledExactlyOnceWith({ handler: (module as TextWallEmitBefore).onTextEmitBefore });
20
+ });
21
+
22
+ it("skips when wall is not started", async () => {
23
+ const { module, observer } = createTextWallWithModule(TextWallEmitBefore);
24
+ const stateUpdate = spyEvent(observer, SUBSCRIBE_EVENTS.STATE_UPDATE);
25
+
26
+ await (module as TextWallEmitBefore).onTextEmitBefore();
27
+
28
+ expect(stateUpdate).not.toHaveBeenCalled();
29
+ });
30
+
31
+ it("skips when isExtend", async () => {
32
+ const { wall, module, observer } = createTextWallWithModule(TextWallEmitBefore);
33
+ const stateUpdate = spyEvent(observer, SUBSCRIBE_EVENTS.STATE_UPDATE);
34
+
35
+ wall.setState({ wall: [], isStarted: true, shouldSkip: false });
36
+
37
+ await (module as TextWallEmitBefore).onTextEmitBefore({ isExtend: true });
38
+
39
+ expect(stateUpdate).not.toHaveBeenCalled();
40
+ });
41
+
42
+ it("skips when set state is in process", async () => {
43
+ const { wall, module, observer, shared } = createTextWallWithModule(TextWallEmitBefore);
44
+ const stateUpdate = spyEvent(observer, SUBSCRIBE_EVENTS.STATE_UPDATE);
45
+
46
+ wall.setState({ wall: [], isStarted: true, shouldSkip: false });
47
+ shared.isTextWallSetStateInProcess = true;
48
+
49
+ await (module as TextWallEmitBefore).onTextEmitBefore();
50
+
51
+ expect(stateUpdate).not.toHaveBeenCalled();
52
+ });
53
+
54
+ it("clears shouldSkip without appending wall element", async () => {
55
+ const { wall, module, observer } = createTextWallWithModule(TextWallEmitBefore);
56
+ const changed = spyEvent(observer, SUBSCRIBE_EVENTS.CHANGED);
57
+
58
+ wall.setState({ wall: [], isStarted: true, shouldSkip: true });
59
+
60
+ await (module as TextWallEmitBefore).onTextEmitBefore();
61
+
62
+ expect(wall.state.shouldSkip).toBe(false);
63
+ expect(wall.state.wall).toEqual([]);
64
+ expect(changed).not.toHaveBeenCalled();
65
+ });
66
+
67
+ it("appends current text to wall when started", async () => {
68
+ const { wall, module, observer } = createTextWallWithModule(TextWallEmitBefore);
69
+ const changed = spyEvent(observer, SUBSCRIBE_EVENTS.CHANGED);
70
+
71
+ wall.setState({ wall: [], isStarted: true, shouldSkip: false });
72
+
73
+ await (module as TextWallEmitBefore).onTextEmitBefore();
74
+
75
+ expect(wall.state.wall).toEqual([
76
+ {
77
+ texts: [{ text: "line", args: {} }],
78
+ tokens: [],
79
+ uid: "1",
80
+ speaker: "hero",
81
+ speakerInfo: {},
82
+ },
83
+ ]);
84
+ expect(changed).toHaveBeenCalledOnce();
85
+ });
86
+ });
@@ -0,0 +1,49 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+
3
+ import { SUBSCRIBE_EVENTS as SCENARIO_EVENTS } from "@vnejs/contracts.core.scenario";
4
+ import { spyEvent } from "@vnejs/test-utils";
5
+
6
+ import { TextWallExec } from "../modules/exec.js";
7
+ import { CONSTANTS, createTextWallTestModule, createTextWallWithModule, registerTextWallPluginVne, SUBSCRIBE_EVENTS } from "./setup.js";
8
+
9
+ describe("TextWallExec", () => {
10
+ beforeEach(() => {
11
+ registerTextWallPluginVne();
12
+ });
13
+
14
+ it("init registers line exec handler", async () => {
15
+ const { module, observer } = createTextWallTestModule(TextWallExec, { subscribe: false });
16
+ const lineExecReg = spyEvent(observer, SCENARIO_EVENTS.LINE_EXEC_REG);
17
+
18
+ await (module as TextWallExec).init();
19
+
20
+ expect(lineExecReg).toHaveBeenCalledExactlyOnceWith({ module: "text.wall", handler: (module as TextWallExec).onLineExec });
21
+ });
22
+
23
+ it("start command enables wall capture", async () => {
24
+ const { wall, module, observer } = createTextWallWithModule(TextWallExec);
25
+ const changed = spyEvent(observer, SUBSCRIBE_EVENTS.CHANGED);
26
+ const next = spyEvent(observer, SCENARIO_EVENTS.NEXT);
27
+
28
+ await (module as TextWallExec).onLineExec({ line: CONSTANTS.EXEC_ACTIONS.START });
29
+
30
+ expect(wall.state.isStarted).toBe(true);
31
+ expect(changed).toHaveBeenCalledOnce();
32
+ expect(next).toHaveBeenCalledOnce();
33
+ });
34
+
35
+ it("clear command resets wall", async () => {
36
+ const { wall, module } = createTextWallWithModule(TextWallExec);
37
+
38
+ wall.setState({
39
+ wall: [{ texts: [], tokens: [], uid: "1", speaker: "hero", speakerInfo: {} }],
40
+ isStarted: true,
41
+ shouldSkip: false,
42
+ });
43
+
44
+ await (module as TextWallExec).onLineExec({ line: CONSTANTS.EXEC_ACTIONS.CLEAR });
45
+
46
+ expect(wall.state.wall).toEqual([]);
47
+ expect(wall.state.shouldSkip).toBe(true);
48
+ });
49
+ });
@@ -0,0 +1,50 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+
3
+ import { spyEvent } from "@vnejs/test-utils";
4
+
5
+ import { TextWallRecalc } from "../modules/recalc.js";
6
+ import { createTextWallWithModule, registerTextWallPluginVne, SUBSCRIBE_EVENTS } from "./setup.js";
7
+
8
+ describe("TextWallRecalc", () => {
9
+ beforeEach(() => {
10
+ registerTextWallPluginVne();
11
+ });
12
+
13
+ it("does nothing when wall is empty", async () => {
14
+ const { module, observer } = createTextWallWithModule(TextWallRecalc, { subscribe: true });
15
+ const stateUpdate = spyEvent(observer, SUBSCRIBE_EVENTS.STATE_UPDATE);
16
+ const changed = spyEvent(observer, SUBSCRIBE_EVENTS.CHANGED);
17
+
18
+ await (module as TextWallRecalc).onTextRecalc();
19
+
20
+ expect(stateUpdate).not.toHaveBeenCalled();
21
+ expect(changed).not.toHaveBeenCalled();
22
+ });
23
+
24
+ it("recalculates wall tokens and emits changed", async () => {
25
+ const { wall, module, observer } = createTextWallWithModule(TextWallRecalc, { subscribe: true });
26
+ const changed = spyEvent(observer, SUBSCRIBE_EVENTS.CHANGED);
27
+
28
+ wall.setState({
29
+ wall: [
30
+ {
31
+ texts: [{ text: "ab", args: {} }],
32
+ tokens: [],
33
+ uid: "1",
34
+ speaker: "hero",
35
+ speakerInfo: {},
36
+ },
37
+ ],
38
+ isStarted: true,
39
+ shouldSkip: false,
40
+ });
41
+
42
+ await (module as TextWallRecalc).onTextRecalc();
43
+
44
+ expect(wall.state.wall[0].tokens).toEqual([
45
+ { token: "a", marks: [] },
46
+ { token: "b", marks: [] },
47
+ ]);
48
+ expect(changed).toHaveBeenCalledOnce();
49
+ });
50
+ });
@@ -1,29 +1,68 @@
1
- import { CONSTANTS as TEXT_CONST, PLUGIN_NAME as TEXT, SUBSCRIBE_EVENTS as TEXT_EVENTS } from "@vnejs/contracts.text";
2
- import { PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.text.wall";
3
- import { createTestModule as baseCreateTestModule, registerCoreVne, registerVnePlugin, stubSilentEvent } from "@vnejs/test-utils";
1
+ import { PLUGIN_NAME as TEXT, SUBSCRIBE_EVENTS as TEXT_EVENTS } from "@vnejs/contracts.text";
2
+ import { CONSTANTS, PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.text.wall";
3
+ import {
4
+ createCoreGlobalState,
5
+ createTestModule as baseCreateTestModule,
6
+ registerCoreVne,
7
+ registerVnePlugin,
8
+ stubEvent,
9
+ stubSilentEvent,
10
+ stubVendorEvents,
11
+ } from "@vnejs/test-utils";
12
+
13
+ import { TextWall } from "../modules/wall.js";
4
14
 
5
15
  export const registerTextWallPluginVne = () => {
6
16
  registerCoreVne();
7
17
  registerVnePlugin(TEXT, { events: TEXT_EVENTS });
8
- registerVnePlugin(PLUGIN_NAME, { events: SUBSCRIBE_EVENTS });
18
+ registerVnePlugin(PLUGIN_NAME, { events: SUBSCRIBE_EVENTS, constants: CONSTANTS });
9
19
  };
10
20
 
11
21
  export const createTextWallTestModule = <T extends Parameters<typeof baseCreateTestModule>[0]>(
12
22
  ModuleClass: T,
13
23
  options: Parameters<typeof baseCreateTestModule>[1] = {},
14
24
  ) => {
25
+ const { state: optionState, ...rest } = options;
15
26
  const result = baseCreateTestModule(ModuleClass, {
16
- state: {
17
- text: { uid: "1", texts: [{ text: "line", args: {} }], tokens: [], speaker: "hero" },
18
- "text.speaker": { speakers: { hero: {} }, speaker: "hero" },
19
- ...(options.state ?? {}),
20
- },
21
- ...options,
27
+ ...rest,
28
+ state:
29
+ optionState ??
30
+ {
31
+ ...createCoreGlobalState(),
32
+ text: { uid: "1", texts: [{ text: "line", args: {} }], tokens: [], speaker: "hero" },
33
+ "text.speaker": { speaker: "hero", args: {}, speakersInfo: { hero: {} } },
34
+ },
22
35
  });
23
36
 
24
- stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.CHANGED);
37
+ const observer = result.module.observer ?? result.observer;
38
+
39
+ stubVendorEvents(observer);
40
+ stubEvent(observer, TEXT_EVENTS.REPLACE, ({ text }) => text);
41
+ stubSilentEvent(observer, SUBSCRIBE_EVENTS.CHANGED);
42
+
43
+ return { ...result, observer };
44
+ };
45
+
46
+ export const createTextWallWithModule = <T extends Parameters<typeof baseCreateTestModule>[0]>(
47
+ ModuleClass: T,
48
+ options: Parameters<typeof baseCreateTestModule>[1] = {},
49
+ ) => {
50
+ const wallResult = createTextWallTestModule(TextWall);
51
+ const siblingResult = createTextWallTestModule(ModuleClass, {
52
+ subscribe: false,
53
+ ...options,
54
+ observer: wallResult.observer,
55
+ state: wallResult.state,
56
+ shared: wallResult.shared,
57
+ });
25
58
 
26
- return result;
59
+ return {
60
+ wall: wallResult.module as TextWall,
61
+ module: siblingResult.module,
62
+ observer: wallResult.observer,
63
+ state: wallResult.state,
64
+ shared: wallResult.shared,
65
+ };
27
66
  };
28
67
 
29
- export { SUBSCRIBE_EVENTS, PLUGIN_NAME };
68
+ export { SUBSCRIBE_EVENTS, CONSTANTS, PLUGIN_NAME, TEXT_EVENTS };
@@ -1,8 +1,5 @@
1
1
  import { beforeEach, describe, expect, it } from "vitest";
2
2
 
3
- import { SUBSCRIBE_EVENTS as SCENARIO_EVENTS } from "@vnejs/contracts.core.scenario";
4
- import { spyEvent } from "@vnejs/test-utils";
5
-
6
3
  import { TextWall } from "../modules/wall.js";
7
4
  import { createTextWallTestModule, registerTextWallPluginVne, SUBSCRIBE_EVENTS } from "./setup.js";
8
5
 
@@ -11,26 +8,12 @@ describe("TextWall", () => {
11
8
  registerTextWallPluginVne();
12
9
  });
13
10
 
14
- it("start command enables wall capture", async () => {
15
- const { module, observer } = createTextWallTestModule(TextWall, { subscribe: false });
16
- const changed = spyEvent(observer, SUBSCRIBE_EVENTS.CHANGED);
17
- const next = spyEvent(observer, SCENARIO_EVENTS.NEXT);
11
+ it("STATE_UPDATE merges partial wall state", async () => {
12
+ const { module, observer } = createTextWallTestModule(TextWall);
18
13
 
19
- await (module as TextWall).onLineExec({ line: "start" });
14
+ await observer.emit(SUBSCRIBE_EVENTS.STATE_UPDATE, { isStarted: true, shouldSkip: true });
20
15
 
21
16
  expect((module as TextWall).state.isStarted).toBe(true);
22
- expect(changed).toHaveBeenCalledOnce();
23
- expect(next).toHaveBeenCalledOnce();
24
- });
25
-
26
- it("clear command resets wall", async () => {
27
- const { module, observer } = createTextWallTestModule(TextWall, { subscribe: false });
28
-
29
- (module as TextWall).setState({ wall: [{ texts: [], tokens: [], uid: "1", speaker: "hero", speakerInfo: {} }], isStarted: true, shouldSkip: false });
30
-
31
- await (module as TextWall).onLineExec({ line: "clear" });
32
-
33
- expect((module as TextWall).state.wall).toEqual([]);
34
17
  expect((module as TextWall).state.shouldSkip).toBe(true);
35
18
  });
36
19
  });
package/src/types.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { ModuleCoreConstants, ModuleCoreEvents, ModuleCoreParams, ModuleCoreSettings } from "@vnejs/module.core";
2
2
  import type { PluginName as TextPluginName, SubscribeEvents as TextSubscribeEvents } from "@vnejs/contracts.text";
3
- import type { PluginName, SubscribeEvents } from "@vnejs/contracts.text.wall";
3
+ import type { PluginName, SubscribeEvents, Constants } from "@vnejs/contracts.text.wall";
4
4
 
5
5
  export type TextWallPluginEvents = ModuleCoreEvents & Record<PluginName, SubscribeEvents> & Record<TextPluginName, TextSubscribeEvents>;
6
6
 
7
- export type TextWallPluginConstants = ModuleCoreConstants;
7
+ export type TextWallPluginConstants = ModuleCoreConstants & Record<PluginName, Constants>;
8
8
 
9
9
  export type TextWallPluginSettings = ModuleCoreSettings;
10
10