@rpg-engine/long-bow 0.3.44 → 0.3.45

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.
Files changed (103) hide show
  1. package/dist/components/Abstractions/SlotsContainer.d.ts +1 -0
  2. package/dist/components/Button.d.ts +3 -2
  3. package/dist/components/Chat/Chat.d.ts +12 -2
  4. package/dist/components/Chatdeprecated/ChatDeprecated.d.ts +13 -0
  5. package/dist/components/CheckButton.d.ts +1 -2
  6. package/dist/components/CircularController/CircularController.d.ts +10 -0
  7. package/dist/components/CraftBook/CraftBook.d.ts +15 -0
  8. package/dist/components/CraftBook/MockItems.d.ts +2 -0
  9. package/dist/components/DraggableContainer.d.ts +1 -0
  10. package/dist/components/DropdownSelectorContainer.d.ts +13 -0
  11. package/dist/components/Equipment/EquipmentSet.d.ts +8 -0
  12. package/dist/components/Input.d.ts +1 -0
  13. package/dist/components/Item/Inventory/ItemContainer.d.ts +12 -0
  14. package/dist/components/Item/Inventory/ItemQuantitySelector.d.ts +7 -0
  15. package/dist/components/Item/Inventory/ItemSlot.d.ts +9 -0
  16. package/dist/components/NPCDialog/NPCDialogText.d.ts +4 -2
  17. package/dist/components/RadioInput/RadioButton.d.ts +8 -0
  18. package/dist/components/RadioInput/RadioInput.d.ts +13 -0
  19. package/dist/components/RadioInput/instruments.d.ts +4 -0
  20. package/dist/components/RangeSlider.d.ts +1 -0
  21. package/dist/components/Spellbook/QuickSpells.d.ts +10 -0
  22. package/dist/components/Spellbook/Spell.d.ts +11 -0
  23. package/dist/components/Spellbook/Spellbook.d.ts +15 -0
  24. package/dist/components/Spellbook/SpellbookShortcuts.d.ts +10 -0
  25. package/dist/components/Spellbook/constants.d.ts +3 -0
  26. package/dist/components/Spellbook/mockSpells.d.ts +2 -0
  27. package/dist/components/itemSelector/ItemSelector.d.ts +14 -0
  28. package/dist/constants/uiDevices.d.ts +1 -0
  29. package/dist/index.d.ts +8 -0
  30. package/dist/long-bow.cjs.development.js +7464 -515
  31. package/dist/long-bow.cjs.development.js.map +1 -1
  32. package/dist/long-bow.cjs.production.min.js +1 -1
  33. package/dist/long-bow.cjs.production.min.js.map +1 -1
  34. package/dist/long-bow.esm.js +7458 -518
  35. package/dist/long-bow.esm.js.map +1 -1
  36. package/dist/mocks/equipmentSet.mocks.d.ts +15 -2
  37. package/dist/mocks/skills.mocks.d.ts +2 -121
  38. package/dist/stories/ChatDeprecated.stories.d.ts +5 -0
  39. package/dist/stories/CircullarController.stories.d.ts +5 -0
  40. package/dist/stories/CraftBook.stories.d.ts +4 -0
  41. package/dist/stories/DropdownSelectorContainer.stories.d.ts +5 -0
  42. package/dist/stories/ItemQuantitySelector.stories.d.ts +5 -0
  43. package/dist/stories/ItemSelector.stories.d.ts +4 -0
  44. package/dist/stories/QuickSpells.stories.d.ts +5 -0
  45. package/dist/stories/RadioInput.stories.d.ts +5 -0
  46. package/dist/stories/Spellbook.stories.d.ts +5 -0
  47. package/package.json +4 -1
  48. package/src/components/Abstractions/SlotsContainer.tsx +3 -0
  49. package/src/components/Button.tsx +18 -8
  50. package/src/components/Chat/Chat.tsx +105 -105
  51. package/src/components/Chatdeprecated/ChatDeprecated.tsx +200 -0
  52. package/src/components/CheckButton.tsx +1 -1
  53. package/src/components/CircularController/CircularController.tsx +162 -0
  54. package/src/components/CraftBook/CraftBook.tsx +230 -0
  55. package/src/components/CraftBook/MockItems.ts +46 -0
  56. package/src/components/DraggableContainer.tsx +4 -1
  57. package/src/components/Dropdown.tsx +7 -1
  58. package/src/components/DropdownSelectorContainer.tsx +42 -0
  59. package/src/components/Equipment/EquipmentSet.tsx +46 -0
  60. package/src/components/Input.tsx +6 -2
  61. package/src/components/Item/Inventory/ItemContainer.tsx +104 -6
  62. package/src/components/Item/Inventory/ItemQuantitySelector.tsx +142 -0
  63. package/src/components/Item/Inventory/ItemSlot.tsx +234 -34
  64. package/src/components/NPCDialog/NPCDialog.tsx +4 -28
  65. package/src/components/NPCDialog/NPCDialogText.tsx +75 -15
  66. package/src/components/NPCDialog/img/press-button.gif +0 -0
  67. package/src/components/RadioInput/RadioButton.tsx +98 -0
  68. package/src/components/RadioInput/RadioInput.tsx +99 -0
  69. package/src/components/RadioInput/instruments.ts +16 -0
  70. package/src/components/RangeSlider.tsx +37 -14
  71. package/src/components/SkillsContainer.tsx +1 -1
  72. package/src/components/Spellbook/QuickSpells.tsx +120 -0
  73. package/src/components/Spellbook/Spell.tsx +201 -0
  74. package/src/components/Spellbook/Spellbook.tsx +144 -0
  75. package/src/components/Spellbook/SpellbookShortcuts.tsx +77 -0
  76. package/src/components/Spellbook/constants.ts +12 -0
  77. package/src/components/Spellbook/mockSpells.ts +60 -0
  78. package/src/components/TimeWidget/TimeWidget.tsx +1 -0
  79. package/src/components/TradingMenu/TradingItemRow.tsx +43 -6
  80. package/src/components/TradingMenu/TradingMenu.tsx +1 -1
  81. package/src/components/itemSelector/ItemSelector.tsx +136 -0
  82. package/src/components/shared/SpriteFromAtlas.tsx +4 -1
  83. package/src/constants/uiDevices.ts +5 -0
  84. package/src/hooks/useOutsideAlerter.ts +2 -2
  85. package/src/index.tsx +8 -0
  86. package/src/mocks/atlas/items/items.json +6086 -314
  87. package/src/mocks/atlas/items/items.png +0 -0
  88. package/src/mocks/equipmentSet.mocks.ts +49 -4
  89. package/src/mocks/itemContainer.mocks.ts +54 -6
  90. package/src/mocks/skills.mocks.ts +8 -2
  91. package/src/stories/Chat.stories.tsx +20 -3
  92. package/src/stories/ChatDeprecated.stories.tsx +170 -0
  93. package/src/stories/CircullarController.stories.tsx +33 -0
  94. package/src/stories/CraftBook.stories.tsx +40 -0
  95. package/src/stories/DropdownSelectorContainer.stories.tsx +41 -0
  96. package/src/stories/EquipmentSet.stories.tsx +10 -0
  97. package/src/stories/ItemContainer.stories.tsx +84 -15
  98. package/src/stories/ItemQuantitySelector.stories.tsx +26 -0
  99. package/src/stories/ItemSelector.stories.tsx +77 -0
  100. package/src/stories/QuickSpells.stories.tsx +38 -0
  101. package/src/stories/RadioInput.stories.tsx +35 -0
  102. package/src/stories/RangeSlider.stories.tsx +10 -9
  103. package/src/stories/Spellbook.stories.tsx +107 -0
@@ -3,15 +3,18 @@ import {
3
3
  IItem,
4
4
  IItemContainer,
5
5
  ItemContainerType,
6
+ ItemRarities,
6
7
  ItemSlotType,
7
8
  ItemType,
8
9
  } from '@rpg-engine/shared';
9
10
 
10
11
  import { observer } from 'mobx-react-lite';
11
- import React, { useEffect, useState } from 'react';
12
+ import React, { useEffect, useRef, useState } from 'react';
13
+ import Draggable from 'react-draggable';
12
14
  import styled from 'styled-components';
13
15
  import { v4 as uuidv4 } from 'uuid';
14
16
  import { uiFonts } from '../../../constants/uiFonts';
17
+ import { IPosition } from '../../../types/eventTypes';
15
18
  import { RelativeListMenu } from '../../RelativeListMenu';
16
19
  import { Ellipsis } from '../../shared/Ellipsis';
17
20
  import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
@@ -52,6 +55,22 @@ interface IProps {
52
55
  itemContainerType: ItemContainerType | null,
53
56
  item: IItem
54
57
  ) => void;
58
+ onDragStart: (
59
+ item: IItem,
60
+ slotIndex: number,
61
+ itemContainerType: ItemContainerType | null
62
+ ) => void;
63
+ onDragEnd: (quantity?: number) => void;
64
+ onOutsideDrop?: (item: IItem, position: IPosition) => void;
65
+ dragScale?: number;
66
+ checkIfItemCanBeMoved: () => boolean;
67
+ checkIfItemShouldDragEnd?: () => boolean;
68
+ openQuantitySelector?: (maxQuantity: number, callback: () => void) => void;
69
+ onPlaceDrop: (
70
+ item: IItem | null,
71
+ slotIndex: number,
72
+ itemContainerType: ItemContainerType | null
73
+ ) => void;
55
74
  atlasJSON: any;
56
75
  atlasIMG: any;
57
76
  isContextMenuDisabled?: boolean;
@@ -70,39 +89,57 @@ export const ItemSlot: React.FC<IProps> = observer(
70
89
  atlasJSON,
71
90
  atlasIMG,
72
91
  isContextMenuDisabled = false,
92
+ onDragEnd,
93
+ onDragStart,
94
+ onPlaceDrop,
95
+ onOutsideDrop: onDrop,
96
+ checkIfItemCanBeMoved,
97
+ openQuantitySelector,
98
+ checkIfItemShouldDragEnd,
99
+ dragScale,
73
100
  }) => {
74
101
  const [isTooltipVisible, setTooltipVisible] = useState(false);
75
102
 
76
103
  const [isContextMenuVisible, setIsContextMenuVisible] = useState(false);
77
104
 
105
+ const [isFocused, setIsFocused] = useState(false);
106
+ const [wasDragged, setWasDragged] = useState(false);
107
+ const [dragPosition, setDragPosition] = useState<IPosition>({ x: 0, y: 0 });
108
+ const [dropPosition, setDropPosition] = useState<IPosition | null>(null);
109
+ const dragContainer = useRef<HTMLDivElement>(null);
110
+
78
111
  const [contextActions, setContextActions] = useState<IContextMenuItem[]>(
79
112
  []
80
113
  );
81
114
 
82
115
  useEffect(() => {
116
+ setDragPosition({ x: 0, y: 0 });
117
+ setIsFocused(false);
118
+
83
119
  if (item) {
84
120
  setContextActions(generateContextMenu(item, containerType));
85
121
  }
86
122
  }, [item]);
87
123
 
88
- const getStackInfo = (itemId: string, stackQty: number) => {
89
- // if (itemToRender?.isStackable && itemToRender?.stackQty) {
124
+ useEffect(() => {
125
+ if (onDrop && item && dropPosition) {
126
+ onDrop(item, dropPosition);
127
+ }
128
+ }, [dropPosition]);
90
129
 
130
+ const getStackInfo = (itemId: string, stackQty: number) => {
91
131
  const isFractionalStackQty = stackQty % 1 !== 0;
92
132
  const isLargerThan999 = stackQty > 999;
93
133
 
134
+ let qtyClassName = 'regular';
135
+ if (isLargerThan999) qtyClassName = 'small';
136
+ if (isFractionalStackQty) qtyClassName = 'xsmall';
137
+
94
138
  if (stackQty > 1) {
95
139
  return (
96
140
  <ItemQtyContainer key={`qty-${itemId}`}>
97
141
  <Ellipsis maxLines={1} maxWidth="48px">
98
- <ItemQty
99
- className={
100
- isFractionalStackQty || isLargerThan999 ? 'small' : 'regular'
101
- }
102
- >
103
- {' '}
104
- {stackQty}{' '}
105
- </ItemQty>
142
+ <ItemQty className={qtyClassName}> {stackQty} </ItemQty>
106
143
  </Ellipsis>
107
144
  </ItemQtyContainer>
108
145
  );
@@ -112,6 +149,7 @@ export const ItemSlot: React.FC<IProps> = observer(
112
149
 
113
150
  const renderItem = (itemToRender: IItem | null) => {
114
151
  const element = [];
152
+
115
153
  if (itemToRender?.texturePath) {
116
154
  element.push(
117
155
  <ErrorBoundary key={itemToRender._id}>
@@ -119,7 +157,14 @@ export const ItemSlot: React.FC<IProps> = observer(
119
157
  key={itemToRender._id}
120
158
  atlasIMG={atlasIMG}
121
159
  atlasJSON={atlasJSON}
122
- spriteKey={getItemTextureKeyPath(itemToRender, atlasJSON)}
160
+ spriteKey={getItemTextureKeyPath(
161
+ {
162
+ key: itemToRender.texturePath,
163
+ texturePath: itemToRender.texturePath,
164
+ stackQty: itemToRender.stackQty || 1,
165
+ },
166
+ atlasJSON
167
+ )}
123
168
  imgScale={3}
124
169
  />
125
170
  </ErrorBoundary>
@@ -142,13 +187,21 @@ export const ItemSlot: React.FC<IProps> = observer(
142
187
  itemToRender.allowedEquipSlotType?.includes(slotSpriteMask!)
143
188
  ) {
144
189
  const element = [];
190
+
145
191
  element.push(
146
192
  <ErrorBoundary key={itemToRender._id}>
147
193
  <SpriteFromAtlas
148
194
  key={itemToRender._id}
149
195
  atlasIMG={atlasIMG}
150
196
  atlasJSON={atlasJSON}
151
- spriteKey={getItemTextureKeyPath(itemToRender, atlasJSON)}
197
+ spriteKey={getItemTextureKeyPath(
198
+ {
199
+ key: itemToRender.texturePath,
200
+ texturePath: itemToRender.texturePath,
201
+ stackQty: itemToRender.stackQty || 1,
202
+ },
203
+ atlasJSON
204
+ )}
152
205
  imgScale={3}
153
206
  />
154
207
  </ErrorBoundary>
@@ -189,29 +242,144 @@ export const ItemSlot: React.FC<IProps> = observer(
189
242
  }
190
243
  };
191
244
 
245
+ const resetItem = () => {
246
+ setTooltipVisible(false);
247
+ setWasDragged(false);
248
+ };
249
+
250
+ const onSuccesfulDrag = (quantity?: number) => {
251
+ resetItem();
252
+
253
+ if (quantity === -1) setDragPosition({ x: 0, y: 0 });
254
+ else if (item) {
255
+ onDragEnd(quantity);
256
+ resetItem();
257
+ }
258
+ };
259
+
192
260
  return (
193
261
  <Container
262
+ item={item}
194
263
  className="rpgui-icon empty-slot"
195
- onMouseOver={event =>
196
- onMouseOver(event, slotIndex, item, event.clientX, event.clientY)
197
- }
198
- onMouseOut={() => {
199
- if (onMouseOut) onMouseOut();
264
+ onMouseUp={() => {
265
+ const data = item ? item : null;
266
+ if (onPlaceDrop) onPlaceDrop(data, slotIndex, containerType);
200
267
  }}
201
- onMouseEnter={() => setTooltipVisible(true)}
202
- onMouseLeave={() => setTooltipVisible(false)}
203
- onClick={() => {
204
- setTooltipVisible(false);
205
-
206
- if (item) {
207
- if (!isContextMenuDisabled) {
208
- setIsContextMenuVisible(!isContextMenuVisible);
209
- }
268
+ onTouchEnd={e => {
269
+ const { clientX, clientY } = e.changedTouches[0];
270
+ const simulatedEvent = new MouseEvent('mouseup', {
271
+ clientX,
272
+ clientY,
273
+ bubbles: true,
274
+ });
210
275
 
211
- onClick(item.type, containerType, item);
212
- }
276
+ document
277
+ .elementFromPoint(clientX, clientY)
278
+ ?.dispatchEvent(simulatedEvent);
213
279
  }}
214
280
  >
281
+ <Draggable
282
+ defaultClassName={item ? 'draggable' : 'empty-slot'}
283
+ scale={dragScale}
284
+ onStop={(e, data) => {
285
+ if (wasDragged && item) {
286
+ //@ts-ignore
287
+ const classes: string[] = Array.from(e.target?.classList);
288
+
289
+ const isOutsideDrop =
290
+ classes.some(elm => {
291
+ return elm.includes('rpgui-content');
292
+ }) || classes.length === 0;
293
+
294
+ if (isOutsideDrop) {
295
+ setDropPosition({
296
+ x: data.x,
297
+ y: data.y,
298
+ });
299
+ }
300
+
301
+ setWasDragged(false);
302
+
303
+ const target = dragContainer.current;
304
+ if (!target || !wasDragged) return;
305
+
306
+ const style = window.getComputedStyle(target);
307
+ const matrix = new DOMMatrixReadOnly(style.transform);
308
+ const x = matrix.m41;
309
+ const y = matrix.m42;
310
+
311
+ setDragPosition({ x, y });
312
+
313
+ setTimeout(() => {
314
+ if (checkIfItemCanBeMoved()) {
315
+ if (checkIfItemShouldDragEnd && !checkIfItemShouldDragEnd())
316
+ return;
317
+
318
+ if (
319
+ item.stackQty &&
320
+ item.stackQty !== 1 &&
321
+ openQuantitySelector
322
+ )
323
+ openQuantitySelector(item.stackQty, onSuccesfulDrag);
324
+ else onSuccesfulDrag(item.stackQty);
325
+ } else {
326
+ resetItem();
327
+ setIsFocused(false);
328
+ setDragPosition({ x: 0, y: 0 });
329
+ }
330
+ }, 100);
331
+ } else if (item) {
332
+ if (!isContextMenuDisabled)
333
+ setIsContextMenuVisible(!isContextMenuVisible);
334
+
335
+ onClick(item.type, containerType, item);
336
+ }
337
+ }}
338
+ onStart={() => {
339
+ if (!item) {
340
+ return;
341
+ }
342
+
343
+ if (onDragStart) {
344
+ onDragStart(item, slotIndex, containerType);
345
+ }
346
+ }}
347
+ onDrag={(_e, data) => {
348
+ if (
349
+ Math.abs(data.x - dragPosition.x) > 5 ||
350
+ Math.abs(data.y - dragPosition.y) > 5
351
+ ) {
352
+ setWasDragged(true);
353
+ setIsFocused(true);
354
+ }
355
+ }}
356
+ position={dragPosition}
357
+ cancel=".empty-slot"
358
+ >
359
+ <ItemContainer
360
+ ref={dragContainer}
361
+ isFocused={isFocused}
362
+ onMouseOver={event => {
363
+ onMouseOver(event, slotIndex, item, event.clientX, event.clientY);
364
+ }}
365
+ onMouseOut={() => {
366
+ if (onMouseOut) onMouseOut();
367
+ }}
368
+ onMouseEnter={() => {
369
+ setTooltipVisible(true);
370
+ }}
371
+ onMouseLeave={() => {
372
+ setTooltipVisible(false);
373
+ }}
374
+ >
375
+ {onRenderSlot(item)}
376
+ </ItemContainer>
377
+ </Draggable>
378
+
379
+ {isTooltipVisible && item && !isFocused && (
380
+ <ItemTooltip label={item.name} />
381
+ )}
382
+
215
383
  {!isContextMenuDisabled && isContextMenuVisible && contextActions && (
216
384
  <RelativeListMenu
217
385
  options={contextActions}
@@ -226,31 +394,60 @@ export const ItemSlot: React.FC<IProps> = observer(
226
394
  }}
227
395
  />
228
396
  )}
229
-
230
- {isTooltipVisible && item && <ItemTooltip label={item.name} />}
231
-
232
- {onRenderSlot(item)}
233
397
  </Container>
234
398
  );
235
399
  }
236
400
  );
237
401
 
238
- const Container = styled.div`
402
+ const rarityColor = (item: IItem | null) => {
403
+ switch (item?.rarity) {
404
+ case ItemRarities.Uncommon:
405
+ return 'rgba(13, 193, 13, 0.6)';
406
+ case ItemRarities.Rare:
407
+ return 'rgba(8, 104, 187, 0.6)';
408
+ case ItemRarities.Epic:
409
+ return 'rgba(191, 0, 255, 0.6)';
410
+ case ItemRarities.Legendary:
411
+ return 'rgba(255, 191, 0,0.6)';
412
+ default:
413
+ return 'unset';
414
+ }
415
+ };
416
+
417
+ interface ContainerTypes {
418
+ item: IItem | null;
419
+ }
420
+
421
+ const Container = styled.div<ContainerTypes>`
239
422
  margin: 0.1rem;
240
423
  .sprite-from-atlas-img {
241
424
  position: relative;
242
425
  top: 1.5rem;
243
426
  left: 1.5rem;
427
+ border-color: ${({ item }) => rarityColor(item)};
428
+ box-shadow: ${({ item }) => `0 0 5px 2px ${rarityColor(item)}`} inset, ${({
429
+ item,
430
+ }) => `0 0 4px 3px ${rarityColor(item)}`};
431
+ //background-color: ${({ item }) => rarityColor(item)};
244
432
  }
245
433
  position: relative;
246
434
  `;
247
435
 
436
+ const ItemContainer = styled.div<{ isFocused?: boolean }>`
437
+ width: 100%;
438
+ height: 100%;
439
+ position: relative;
440
+
441
+ ${props => props.isFocused && 'z-index: 100; pointer-events: none;'}
442
+ `;
443
+
248
444
  const ItemQtyContainer = styled.div`
249
445
  position: relative;
250
446
  width: 85%;
251
447
  height: 16px;
252
448
  top: 25px;
253
449
  left: 2px;
450
+ pointer-events: none;
254
451
 
255
452
  display: flex;
256
453
  justify-content: flex-end;
@@ -263,4 +460,7 @@ const ItemQty = styled.span`
263
460
  &.small {
264
461
  font-size: ${uiFonts.size.xsmall};
265
462
  }
463
+ &.xsmall {
464
+ font-size: ${uiFonts.size.xxsmall};
465
+ }
266
466
  `;
@@ -1,14 +1,13 @@
1
- import React, { useState } from 'react';
1
+ import React from 'react';
2
2
  import styled from 'styled-components';
3
3
  import { RPGUIContainer, RPGUIContainerTypes } from '../RPGUIContainer';
4
- import aliceDefaultThumbnail from './img/npcDialog/npcThumbnails/alice.png';
5
- import pressSpaceGif from './img/space.gif';
6
4
  import { NPCDialogText } from './NPCDialogText';
7
5
  import {
8
6
  IQuestionDialog,
9
7
  IQuestionDialogAnswer,
10
8
  QuestionDialog,
11
9
  } from './QuestionDialog/QuestionDialog';
10
+ import aliceDefaultThumbnail from './img/npcDialog/npcThumbnails/alice.png';
12
11
 
13
12
  export enum NPCDialogType {
14
13
  TextOnly = 'TextOnly',
@@ -34,14 +33,10 @@ export const NPCDialog: React.FC<INPCDialogProps> = ({
34
33
  questions,
35
34
  answers,
36
35
  }) => {
37
- const [showGoNextIndicator, setShowGoNextIndicator] = useState<boolean>(
38
- false
39
- );
40
-
41
36
  return (
42
37
  <RPGUIContainer
43
38
  type={RPGUIContainerTypes.FramedGold}
44
- width={isQuestionDialog ? '600px' : '50%'}
39
+ width={isQuestionDialog ? '600px' : '80%'}
45
40
  height={'180px'}
46
41
  >
47
42
  {isQuestionDialog && questions && answers ? (
@@ -72,8 +67,7 @@ export const NPCDialog: React.FC<INPCDialogProps> = ({
72
67
  flex={type === NPCDialogType.TextAndThumbnail ? '70%' : '100%'}
73
68
  >
74
69
  <NPCDialogText
75
- onStartStep={() => setShowGoNextIndicator(false)}
76
- onEndStep={() => setShowGoNextIndicator(true)}
70
+ type={type}
77
71
  text={text || 'No text provided.'}
78
72
  onClose={() => {
79
73
  if (onClose) {
@@ -88,12 +82,6 @@ export const NPCDialog: React.FC<INPCDialogProps> = ({
88
82
  </ThumbnailContainer>
89
83
  )}
90
84
  </Container>
91
- {showGoNextIndicator && (
92
- <PressSpaceIndicator
93
- right={type === NPCDialogType.TextOnly ? '1rem' : '10.5rem'}
94
- src={pressSpaceGif}
95
- />
96
- )}
97
85
  </>
98
86
  )}
99
87
  </RPGUIContainer>
@@ -131,15 +119,3 @@ const NPCThumbnail = styled.img`
131
119
  height: 128px;
132
120
  width: 128px;
133
121
  `;
134
-
135
- interface IPressSpaceIndicatorProps {
136
- right: string;
137
- }
138
-
139
- const PressSpaceIndicator = styled.img<IPressSpaceIndicatorProps>`
140
- position: absolute;
141
- right: ${({ right }) => right};
142
- bottom: 1rem;
143
- height: 20.7px;
144
- image-rendering: -webkit-optimize-contrast;
145
- `;
@@ -1,13 +1,18 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useEffect, useRef, useState } from 'react';
2
2
  import styled from 'styled-components';
3
+ import { NPCDialogType } from '../..';
4
+ import { IS_MOBILE_OR_TABLET } from '../../constants/uiDevices';
3
5
  import { chunkString } from '../../libs/StringHelpers';
4
6
  import { DynamicText } from '../typography/DynamicText';
7
+ import pressButtonGif from './img/press-button.gif';
8
+ import pressSpaceGif from './img/space.gif';
5
9
 
6
10
  interface IProps {
7
11
  text: string;
8
12
  onClose: () => void;
9
- onEndStep: () => void;
10
- onStartStep: () => void;
13
+ onEndStep?: () => void;
14
+ onStartStep?: () => void;
15
+ type?: NPCDialogType;
11
16
  }
12
17
 
13
18
  export const NPCDialogText: React.FC<IProps> = ({
@@ -15,21 +20,43 @@ export const NPCDialogText: React.FC<IProps> = ({
15
20
  onClose,
16
21
  onEndStep,
17
22
  onStartStep,
23
+ type,
18
24
  }) => {
19
- const textChunks = chunkString(text, 200);
25
+ const windowSize = useRef([window.innerWidth, window.innerHeight]);
26
+ function maxCharacters(width: number) {
27
+ // Set the font size to 16 pixels
28
+ var fontSize = 11.2;
20
29
 
21
- const [chunkIndex, setChunkIndex] = useState<number>(0);
30
+ // Calculate the number of characters that can fit in one line
31
+ var charactersPerLine = Math.floor(width / 2 / fontSize);
32
+
33
+ // Calculate the number of lines that can fit in the div
34
+ var linesPerDiv = Math.floor(180 / fontSize);
35
+
36
+ // Calculate the maximum number of characters that can fit in the div
37
+ var maxCharacters = charactersPerLine * linesPerDiv;
38
+
39
+ // Return the maximum number of characters
40
+ return Math.round(maxCharacters / 5);
41
+ }
42
+
43
+ const textChunks = chunkString(text, maxCharacters(windowSize.current[0]));
22
44
 
45
+ const [chunkIndex, setChunkIndex] = useState<number>(0);
23
46
  const onHandleSpacePress = (event: KeyboardEvent) => {
24
47
  if (event.code === 'Space') {
25
- const hasNextChunk = textChunks?.[chunkIndex + 1] || false;
26
-
27
- if (hasNextChunk) {
28
- setChunkIndex(prev => prev + 1);
29
- } else {
30
- // if there's no more text chunks, close the dialog
31
- onClose();
32
- }
48
+ goToNextStep();
49
+ }
50
+ };
51
+
52
+ const goToNextStep = () => {
53
+ const hasNextChunk = textChunks?.[chunkIndex + 1] || false;
54
+
55
+ if (hasNextChunk) {
56
+ setChunkIndex(prev => prev + 1);
57
+ } else {
58
+ // if there's no more text chunks, close the dialog
59
+ onClose();
33
60
  }
34
61
  };
35
62
 
@@ -39,15 +66,48 @@ export const NPCDialogText: React.FC<IProps> = ({
39
66
  return () => document.removeEventListener('keydown', onHandleSpacePress);
40
67
  }, [chunkIndex]);
41
68
 
69
+ const [showGoNextIndicator, setShowGoNextIndicator] = useState<boolean>(
70
+ false
71
+ );
72
+
42
73
  return (
43
74
  <Container>
44
75
  <DynamicText
45
76
  text={textChunks?.[chunkIndex] || ''}
46
- onFinish={onEndStep}
47
- onStart={onStartStep}
77
+ onFinish={() => {
78
+ setShowGoNextIndicator(true);
79
+
80
+ onEndStep && onEndStep();
81
+ }}
82
+ onStart={() => {
83
+ setShowGoNextIndicator(false);
84
+
85
+ onStartStep && onStartStep();
86
+ }}
48
87
  />
88
+ {showGoNextIndicator && (
89
+ <PressSpaceIndicator
90
+ right={type === NPCDialogType.TextOnly ? '1rem' : '10.5rem'}
91
+ src={IS_MOBILE_OR_TABLET ? pressButtonGif : pressSpaceGif}
92
+ onClick={() => {
93
+ goToNextStep();
94
+ }}
95
+ />
96
+ )}
49
97
  </Container>
50
98
  );
51
99
  };
52
100
 
53
101
  const Container = styled.div``;
102
+
103
+ interface IPressSpaceIndicatorProps {
104
+ right: string;
105
+ }
106
+
107
+ const PressSpaceIndicator = styled.img<IPressSpaceIndicatorProps>`
108
+ position: absolute;
109
+ right: ${({ right }) => right};
110
+ bottom: 1rem;
111
+ height: 20.7px;
112
+ image-rendering: -webkit-optimize-contrast;
113
+ `;
@@ -0,0 +1,98 @@
1
+ import { capitalize } from 'lodash';
2
+ import React from 'react';
3
+ import styled from 'styled-components';
4
+ import { uiColors } from '../../constants/uiColors';
5
+ import { Ellipsis } from '../shared/Ellipsis';
6
+
7
+ export interface IRadioProps {
8
+ title:string,
9
+ subtitle:string,
10
+ instrument:string|null
11
+ setInstrument: (value: string) => void;
12
+ }
13
+
14
+ export const RadioButton: React.FC<IRadioProps> = ({
15
+ title,
16
+ subtitle,
17
+ instrument,
18
+ setInstrument,
19
+ }) => {
20
+ const isRadioSelected=(value:string):boolean=>instrument===value;
21
+
22
+ const handleClick = (e:string):void => {
23
+ setInstrument(e)
24
+ };
25
+
26
+ return (
27
+ <ItemWrapper onClick={() => handleClick(title)} >
28
+ <ItemIconContainer>
29
+ <SpriteContainer id='elemento-radio'>
30
+ <input
31
+ type='radio'
32
+ name='react-radio-btn'
33
+ className='rpgui-radio'
34
+ value={title}
35
+ checked={isRadioSelected(title)}
36
+ onChange={(e)=>(e)}
37
+ />
38
+ </SpriteContainer>
39
+ </ItemIconContainer>
40
+ <ItemNameContainer>
41
+ <NameValue onClick={() => handleClick(title)}>
42
+ <p>{title}</p>
43
+ <p>
44
+ <Ellipsis maxLines={1} maxWidth="250px">
45
+ {capitalize(subtitle)}
46
+ </Ellipsis>
47
+ </p>
48
+
49
+ </NameValue>
50
+ </ItemNameContainer>
51
+ </ItemWrapper>
52
+ );
53
+ };
54
+
55
+ const ItemWrapper = styled.div`
56
+ width: 100%;
57
+ margin: auto;
58
+ display: flex;
59
+ justify-content: space-between;
60
+ margin-bottom: 1rem;
61
+
62
+ &:hover {
63
+ background-color: ${uiColors.darkGray};
64
+ }
65
+ padding: 0.5rem;
66
+ `;
67
+
68
+
69
+ const ItemNameContainer = styled.div`
70
+ flex: 80%;
71
+ `;
72
+
73
+ const ItemIconContainer = styled.div`
74
+ display: flex;
75
+ justify-content: flex-start;
76
+ align-items: center;
77
+
78
+ flex: 0 0 58px;
79
+ `;
80
+
81
+ const SpriteContainer = styled.div`
82
+ input {
83
+ display:block !important;
84
+ &:hover {
85
+ cursor: pointer;
86
+ }
87
+ };
88
+ position: relative;
89
+ top: -0.5rem;
90
+ left: 0.5rem;
91
+ `;
92
+
93
+ const NameValue = styled.div`
94
+ p {
95
+ font-size: 0.75rem;
96
+ margin: 0;
97
+ }
98
+ `;