@rpg-engine/long-bow 0.8.200 → 0.8.202

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.200",
3
+ "version": "0.8.202",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -18,6 +18,7 @@ export interface INPCDialogProps {
18
18
  text?: string;
19
19
  type: NPCDialogType;
20
20
  imagePath?: string;
21
+ isTranslated?: boolean;
21
22
  onClose?: () => void;
22
23
  isQuestionDialog?: boolean;
23
24
  answers?: IQuestionDialogAnswer[];
@@ -29,6 +30,7 @@ export const NPCDialog: React.FC<INPCDialogProps> = ({
29
30
  type,
30
31
  onClose,
31
32
  imagePath,
33
+ isTranslated = false,
32
34
  isQuestionDialog = false,
33
35
  questions,
34
36
  answers,
@@ -70,6 +72,7 @@ export const NPCDialog: React.FC<INPCDialogProps> = ({
70
72
  <NPCDialogText
71
73
  type={type}
72
74
  text={text || 'No text provided.'}
75
+ isTranslated={isTranslated}
73
76
  onClose={() => {
74
77
  if (onClose) {
75
78
  onClose();
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
2
  import styled from 'styled-components';
3
- import { NPCDialogType } from '../..';
3
+ import type { NPCDialogType } from './NPCDialog';
4
4
  import { IS_MOBILE_OR_TABLET } from '../../constants/uiDevices';
5
5
  import { chunkString } from '../../libs/StringHelpers';
6
6
  import { DynamicText } from '../typography/DynamicText';
@@ -13,6 +13,7 @@ interface IProps {
13
13
  onEndStep?: () => void;
14
14
  onStartStep?: () => void;
15
15
  type?: NPCDialogType;
16
+ isTranslated?: boolean;
16
17
  }
17
18
 
18
19
  export const NPCDialogText: React.FC<IProps> = ({
@@ -21,6 +22,7 @@ export const NPCDialogText: React.FC<IProps> = ({
21
22
  onEndStep,
22
23
  onStartStep,
23
24
  type,
25
+ isTranslated = false,
24
26
  }) => {
25
27
  const windowSize = useRef([window.innerWidth, window.innerHeight]);
26
28
  function maxCharacters(width: number) {
@@ -43,6 +45,11 @@ export const NPCDialogText: React.FC<IProps> = ({
43
45
  const textChunks = chunkString(text, maxCharacters(windowSize.current[0]));
44
46
 
45
47
  const [chunkIndex, setChunkIndex] = useState<number>(0);
48
+
49
+ useEffect(() => {
50
+ setChunkIndex(0);
51
+ }, [text]);
52
+
46
53
  const onHandleSpacePress = (event: KeyboardEvent) => {
47
54
  if (event.code === 'Space') {
48
55
  goToNextStep();
@@ -70,24 +77,37 @@ export const NPCDialogText: React.FC<IProps> = ({
70
77
  false
71
78
  );
72
79
 
80
+ useEffect(() => {
81
+ setShowGoNextIndicator(isTranslated);
82
+
83
+ if (isTranslated) {
84
+ onStartStep && onStartStep();
85
+ onEndStep && onEndStep();
86
+ }
87
+ }, [chunkIndex, isTranslated, onEndStep, onStartStep, text]);
88
+
73
89
  return (
74
90
  <Container>
75
- <DynamicText
76
- text={textChunks?.[chunkIndex] || ''}
77
- onFinish={() => {
78
- setShowGoNextIndicator(true);
79
-
80
- onEndStep && onEndStep();
81
- }}
82
- onStart={() => {
83
- setShowGoNextIndicator(false);
84
-
85
- onStartStep && onStartStep();
86
- }}
87
- />
91
+ {isTranslated ? (
92
+ <TextContainer>{textChunks?.[chunkIndex] || ''}</TextContainer>
93
+ ) : (
94
+ <DynamicText
95
+ text={textChunks?.[chunkIndex] || ''}
96
+ onFinish={() => {
97
+ setShowGoNextIndicator(true);
98
+
99
+ onEndStep && onEndStep();
100
+ }}
101
+ onStart={() => {
102
+ setShowGoNextIndicator(false);
103
+
104
+ onStartStep && onStartStep();
105
+ }}
106
+ />
107
+ )}
88
108
  {showGoNextIndicator && (
89
109
  <PressSpaceIndicator
90
- right={type === NPCDialogType.TextOnly ? '1rem' : '10.5rem'}
110
+ right={type === 'TextOnly' ? '1rem' : '10.5rem'}
91
111
  src={IS_MOBILE_OR_TABLET ? pressButtonGif : pressSpaceGif}
92
112
  onPointerDown={() => {
93
113
  goToNextStep();
@@ -100,6 +120,14 @@ export const NPCDialogText: React.FC<IProps> = ({
100
120
 
101
121
  const Container = styled.div``;
102
122
 
123
+ const TextContainer = styled.p`
124
+ font-size: 0.7rem !important;
125
+ color: white;
126
+ text-shadow: 1px 1px 0px #000000;
127
+ letter-spacing: 1.2px;
128
+ word-break: normal;
129
+ `;
130
+
103
131
  interface IPressSpaceIndicatorProps {
104
132
  right: string;
105
133
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ // @ts-nocheck
5
+ import React from 'react';
6
+ import ReactDOM from 'react-dom';
7
+ import { act } from 'react-dom/test-utils';
8
+ import { NPCDialogText } from '../NPCDialogText';
9
+
10
+ jest.mock('../img/press-button.gif', () => 'press-button.gif');
11
+ jest.mock('../img/space.gif', () => 'space.gif');
12
+
13
+ describe('NPCDialogText', () => {
14
+ let container;
15
+
16
+ beforeEach(() => {
17
+ container = document.createElement('div');
18
+ document.body.appendChild(container);
19
+ jest.useFakeTimers();
20
+ });
21
+
22
+ afterEach(() => {
23
+ ReactDOM.unmountComponentAtNode(container);
24
+ document.body.removeChild(container);
25
+ jest.useRealTimers();
26
+ jest.clearAllMocks();
27
+ });
28
+
29
+ it('renders the full translated text immediately and skips the typewriter delay', () => {
30
+ act(() => {
31
+ ReactDOM.render(
32
+ <NPCDialogText
33
+ text="Translated NPC dialog"
34
+ type={'TextOnly'}
35
+ isTranslated
36
+ onClose={jest.fn()}
37
+ />,
38
+ container
39
+ );
40
+ });
41
+
42
+ expect(container.textContent).toContain('Translated NPC dialog');
43
+ expect(container.querySelectorAll('img')).toHaveLength(1);
44
+ });
45
+
46
+ it('keeps the typewriter effect for untranslated text until the animation finishes', () => {
47
+ act(() => {
48
+ ReactDOM.render(
49
+ <NPCDialogText
50
+ text="Hi"
51
+ type={'TextOnly'}
52
+ onClose={jest.fn()}
53
+ />,
54
+ container
55
+ );
56
+ });
57
+
58
+ expect(container.textContent).toBe('');
59
+ expect(container.querySelectorAll('img')).toHaveLength(0);
60
+
61
+ act(() => {
62
+ jest.advanceTimersByTime(100);
63
+ });
64
+
65
+ expect(container.textContent).toContain('Hi');
66
+ expect(container.querySelectorAll('img')).toHaveLength(1);
67
+ });
68
+ });