@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/dist/components/NPCDialog/NPCDialog.d.ts +1 -0
- package/dist/components/NPCDialog/NPCDialogText.d.ts +2 -1
- package/dist/long-bow.cjs.development.js +37 -18
- package/dist/long-bow.cjs.development.js.map +1 -1
- package/dist/long-bow.cjs.production.min.js +1 -1
- package/dist/long-bow.cjs.production.min.js.map +1 -1
- package/dist/long-bow.esm.js +37 -18
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/NPCDialog/NPCDialog.tsx +3 -0
- package/src/components/NPCDialog/NPCDialogText.tsx +43 -15
- package/src/components/NPCDialog/__test__/NPCDialogText.spec.tsx +68 -0
package/package.json
CHANGED
|
@@ -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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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 ===
|
|
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
|
+
});
|