@rpg-engine/long-bow 0.3.80 → 0.3.82

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 (149) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +181 -181
  3. package/dist/components/Abstractions/ModalPortal.d.ts +6 -0
  4. package/dist/long-bow.cjs.development.js +805 -807
  5. package/dist/long-bow.cjs.development.js.map +1 -1
  6. package/dist/long-bow.cjs.production.min.js +1 -1
  7. package/dist/long-bow.cjs.production.min.js.map +1 -1
  8. package/dist/long-bow.esm.js +808 -810
  9. package/dist/long-bow.esm.js.map +1 -1
  10. package/package.json +100 -100
  11. package/src/components/Abstractions/ModalPortal.tsx +22 -0
  12. package/src/components/Abstractions/SlotsContainer.tsx +48 -48
  13. package/src/components/Arrow/SelectArrow.tsx +69 -69
  14. package/src/components/Arrow/img/arrow01-left-clicked.png +0 -0
  15. package/src/components/Arrow/img/arrow01-left.png +0 -0
  16. package/src/components/Arrow/img/arrow01-right-clicked.png +0 -0
  17. package/src/components/Arrow/img/arrow01-right.png +0 -0
  18. package/src/components/Arrow/img/arrow02-left-clicked.png +0 -0
  19. package/src/components/Arrow/img/arrow02-left.png +0 -0
  20. package/src/components/Arrow/img/arrow02-right-clicked.png +0 -0
  21. package/src/components/Arrow/img/arrow02-right.png +0 -0
  22. package/src/components/Button.tsx +40 -40
  23. package/src/components/Character/CharacterSelection.tsx +96 -96
  24. package/src/components/CharacterStatus/CharacterStatus.tsx +120 -120
  25. package/src/components/Chat/Chat.tsx +196 -195
  26. package/src/components/Chatdeprecated/ChatDeprecated.tsx +198 -198
  27. package/src/components/CheckButton.tsx +65 -65
  28. package/src/components/CircularController/CircularController.tsx +248 -248
  29. package/src/components/CraftBook/CraftBook.tsx +241 -241
  30. package/src/components/CraftBook/MockItems.ts +77 -77
  31. package/src/components/DraggableContainer.tsx +156 -156
  32. package/src/components/Dropdown.tsx +90 -90
  33. package/src/components/DropdownSelectorContainer.tsx +42 -42
  34. package/src/components/Equipment/EquipmentSet.tsx +191 -191
  35. package/src/components/HistoryDialog.tsx +104 -104
  36. package/src/components/Input.tsx +15 -15
  37. package/src/components/Item/Cards/ItemInfo.tsx +252 -252
  38. package/src/components/Item/Cards/ItemInfoDisplay.tsx +128 -128
  39. package/src/components/Item/Cards/ItemInfoWrapper.tsx +62 -62
  40. package/src/components/Item/Cards/ItemTooltip.tsx +83 -85
  41. package/src/components/Item/Cards/MobileItemTooltip.tsx +149 -134
  42. package/src/components/Item/Inventory/ErrorBoundary.tsx +42 -42
  43. package/src/components/Item/Inventory/ItemContainer.tsx +218 -215
  44. package/src/components/Item/Inventory/ItemContainerTypes.ts +6 -6
  45. package/src/components/Item/Inventory/ItemQuantitySelector.tsx +138 -138
  46. package/src/components/Item/Inventory/ItemSlot.tsx +546 -544
  47. package/src/components/Item/Inventory/itemContainerHelper.ts +156 -156
  48. package/src/components/ListMenu.tsx +63 -63
  49. package/src/components/Multitab/Tab.tsx +66 -66
  50. package/src/components/Multitab/TabBody.tsx +13 -13
  51. package/src/components/Multitab/TabsContainer.tsx +97 -97
  52. package/src/components/NPCDialog/NPCDialog.tsx +121 -121
  53. package/src/components/NPCDialog/NPCDialogText.tsx +113 -113
  54. package/src/components/NPCDialog/NPCMultiDialog.tsx +159 -159
  55. package/src/components/NPCDialog/QuestionDialog/QuestionDialog.tsx +237 -237
  56. package/src/components/ProgressBar.tsx +92 -92
  57. package/src/components/PropertySelect/PropertySelect.tsx +106 -106
  58. package/src/components/QuestInfo/QuestInfo.tsx +233 -233
  59. package/src/components/QuestList.tsx +135 -135
  60. package/src/components/RPGUIContainer.tsx +47 -47
  61. package/src/components/RPGUIForceRenderStart.tsx +45 -45
  62. package/src/components/RPGUIRoot.tsx +14 -14
  63. package/src/components/RadioButton.tsx +53 -53
  64. package/src/components/RadioInput/RadioButton.tsx +96 -96
  65. package/src/components/RadioInput/RadioInput.tsx +102 -102
  66. package/src/components/RadioInput/instruments.ts +15 -15
  67. package/src/components/RangeSlider.tsx +78 -78
  68. package/src/components/RelativeListMenu.tsx +83 -83
  69. package/src/components/ScrollList.tsx +79 -79
  70. package/src/components/Shortcuts/Shortcuts.tsx +151 -151
  71. package/src/components/Shortcuts/ShortcutsSetter.tsx +132 -132
  72. package/src/components/Shortcuts/SingleShortcut.ts +62 -62
  73. package/src/components/SimpleProgressBar.tsx +62 -62
  74. package/src/components/SkillProgressBar.tsx +133 -133
  75. package/src/components/SkillsContainer.tsx +206 -206
  76. package/src/components/Spellbook/Spell.tsx +201 -201
  77. package/src/components/Spellbook/Spellbook.tsx +153 -153
  78. package/src/components/Spellbook/constants.ts +8 -8
  79. package/src/components/Spellbook/mockSpells.ts +60 -60
  80. package/src/components/StaticBook/StaticBook.tsx +103 -103
  81. package/src/components/TextArea.tsx +11 -11
  82. package/src/components/TimeWidget/DayNightPeriod/DayNightPeriod.tsx +35 -35
  83. package/src/components/TimeWidget/TimeWidget.tsx +65 -65
  84. package/src/components/TradingMenu/TradingItemRow.tsx +198 -198
  85. package/src/components/TradingMenu/TradingMenu.tsx +216 -216
  86. package/src/components/TradingMenu/items.mock.ts +48 -48
  87. package/src/components/Truncate.tsx +25 -25
  88. package/src/components/itemSelector/ItemSelector.tsx +136 -136
  89. package/src/components/shared/Column.tsx +16 -16
  90. package/src/components/shared/Ellipsis.tsx +65 -65
  91. package/src/components/shared/SpriteFromAtlas.tsx +104 -104
  92. package/src/components/typography/DynamicText.tsx +49 -49
  93. package/src/constants/uiColors.ts +20 -20
  94. package/src/constants/uiDevices.ts +3 -3
  95. package/src/constants/uiFonts.ts +12 -12
  96. package/src/hooks/useEventListener.ts +21 -21
  97. package/src/hooks/useOutsideAlerter.ts +25 -25
  98. package/src/index.tsx +40 -40
  99. package/src/libs/StringHelpers.ts +3 -3
  100. package/src/mocks/atlas/entities/entities.json +20215 -20215
  101. package/src/mocks/atlas/icons/icons.json +735 -735
  102. package/src/mocks/atlas/items/items.json +12086 -12086
  103. package/src/mocks/equipmentSet.mocks.ts +391 -391
  104. package/src/mocks/itemContainer.mocks.ts +563 -563
  105. package/src/mocks/skills.mocks.ts +128 -128
  106. package/src/stories/Arrow.stories.tsx +26 -26
  107. package/src/stories/Button.stories.tsx +36 -36
  108. package/src/stories/CharacterSelection.stories.tsx +45 -45
  109. package/src/stories/CharacterStatus.stories.tsx +29 -29
  110. package/src/stories/Chat.stories.tsx +187 -187
  111. package/src/stories/ChatDeprecated.stories.tsx +170 -170
  112. package/src/stories/CheckButton.stories.tsx +48 -48
  113. package/src/stories/CircullarController.stories.tsx +37 -37
  114. package/src/stories/CraftBook.stories.tsx +42 -42
  115. package/src/stories/DayNightPeriod.stories.tsx +27 -27
  116. package/src/stories/DraggableContainer.stories.tsx +28 -28
  117. package/src/stories/Dropdown.stories.tsx +46 -46
  118. package/src/stories/DropdownSelectorContainer.stories.tsx +41 -41
  119. package/src/stories/EquipmentSet.stories.tsx +65 -65
  120. package/src/stories/HistoryDialog.stories.tsx +61 -61
  121. package/src/stories/ItemContainer.stories.tsx +200 -200
  122. package/src/stories/ItemInfoDisplay.stories.tsx +33 -33
  123. package/src/stories/ItemQuantitySelector.stories.tsx +26 -26
  124. package/src/stories/ItemSelector.stories.tsx +77 -77
  125. package/src/stories/ItemTradingComponent.stories.tsx +35 -35
  126. package/src/stories/ListMenu.stories.tsx +56 -56
  127. package/src/stories/Multitab.stories.tsx +51 -51
  128. package/src/stories/NPCDialog.stories.tsx +130 -130
  129. package/src/stories/NPCMultiDialog.stories.tsx +71 -71
  130. package/src/stories/ProgressBar.stories.tsx +23 -23
  131. package/src/stories/PropertySelect.stories.tsx +40 -40
  132. package/src/stories/QuestInfo.stories.tsx +107 -107
  133. package/src/stories/QuestList.stories.tsx +82 -82
  134. package/src/stories/RPGUIContainers.stories.tsx +42 -42
  135. package/src/stories/RadioButton.stories.tsx +49 -49
  136. package/src/stories/RadioInput.stories.tsx +34 -34
  137. package/src/stories/RangeSlider.stories.tsx +64 -64
  138. package/src/stories/ScrollList.stories.tsx +85 -85
  139. package/src/stories/Shortcuts.stories.tsx +39 -39
  140. package/src/stories/SimpleProgressBar.stories.tsx +22 -22
  141. package/src/stories/SkillProgressBar.stories.tsx +34 -34
  142. package/src/stories/SkillsContainer.stories.tsx +35 -35
  143. package/src/stories/Spellbook.stories.tsx +104 -104
  144. package/src/stories/StaticBook.stories.tsx +32 -32
  145. package/src/stories/Text.stories.tsx +42 -42
  146. package/src/stories/TimeWidget.stories.tsx +27 -27
  147. package/src/stories/TradingMenu.stories.tsx +47 -47
  148. package/src/types/eventTypes.ts +4 -4
  149. package/src/types/index.d.ts +2 -2
@@ -1,544 +1,546 @@
1
- import {
2
- getItemTextureKeyPath,
3
- IEquipmentSet,
4
- IItem,
5
- IItemContainer,
6
- ItemContainerType,
7
- ItemRarities,
8
- ItemSlotType,
9
- ItemType,
10
- } from '@rpg-engine/shared';
11
-
12
- import { observer } from 'mobx-react-lite';
13
- import React, { useEffect, useRef, useState } from 'react';
14
- import Draggable from 'react-draggable';
15
- import styled from 'styled-components';
16
- import { v4 as uuidv4 } from 'uuid';
17
- import { uiFonts } from '../../../constants/uiFonts';
18
- import { IPosition } from '../../../types/eventTypes';
19
- import { RelativeListMenu } from '../../RelativeListMenu';
20
- import { Ellipsis } from '../../shared/Ellipsis';
21
- import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
22
- import { ItemTooltip } from '../Cards/ItemTooltip';
23
- import { MobileItemTooltip } from '../Cards/MobileItemTooltip';
24
- import { ErrorBoundary } from './ErrorBoundary';
25
- import { generateContextMenu, IContextMenuItem } from './itemContainerHelper';
26
-
27
- export const EquipmentSlotSpriteByType: any = {
28
- Neck: 'accessories/corruption-necklace.png',
29
- LeftHand: 'swords/broad-sword.png',
30
- Ring: 'rings/iron-ring.png',
31
- Head: 'helmets/viking-helmet.png',
32
- Torso: 'armors/iron-armor.png',
33
- Legs: 'legs/studded-legs.png',
34
- Feet: 'boots/iron-boots.png',
35
- Inventory: 'containers/bag.png',
36
- RightHand: 'shields/plate-shield.png',
37
- Accessory: 'ranged-weapons/arrow.png',
38
- };
39
-
40
- interface IProps {
41
- slotIndex: number;
42
- item: IItem | null;
43
- itemContainer?: IItemContainer | null;
44
- itemContainerType: ItemContainerType | null;
45
- slotSpriteMask?: ItemSlotType | null;
46
- onSelected: (selectedOption: string, item: IItem) => void;
47
- onMouseOver: (
48
- event: any,
49
- slotIndex: number,
50
- item: IItem | null,
51
- x: number,
52
- y: number
53
- ) => void;
54
- onMouseOut?: () => void;
55
- onPointerDown: (
56
- ItemType: ItemType,
57
- itemContainerType: ItemContainerType | null,
58
- item: IItem
59
- ) => void;
60
- onDragStart: (
61
- item: IItem,
62
- slotIndex: number,
63
- itemContainerType: ItemContainerType | null
64
- ) => void;
65
- onDragEnd: (quantity?: number) => void;
66
- onOutsideDrop?: (item: IItem, position: IPosition) => void;
67
- dragScale?: number;
68
- checkIfItemCanBeMoved: () => boolean;
69
- checkIfItemShouldDragEnd?: () => boolean;
70
- openQuantitySelector?: (maxQuantity: number, callback: () => void) => void;
71
- onPlaceDrop: (
72
- item: IItem | null,
73
- slotIndex: number,
74
- itemContainerType: ItemContainerType | null
75
- ) => void;
76
- atlasJSON: any;
77
- atlasIMG: any;
78
- isContextMenuDisabled?: boolean;
79
- isSelectingShortcut?: boolean;
80
- equipmentSet?: IEquipmentSet | null;
81
- }
82
-
83
- export const ItemSlot: React.FC<IProps> = observer(
84
- ({
85
- slotIndex,
86
- item,
87
- itemContainerType: containerType,
88
- slotSpriteMask,
89
- onMouseOver,
90
- onMouseOut,
91
- onPointerDown,
92
- onSelected,
93
- atlasJSON,
94
- atlasIMG,
95
- isContextMenuDisabled = false,
96
- onDragEnd,
97
- onDragStart,
98
- onPlaceDrop,
99
- onOutsideDrop: onDrop,
100
- checkIfItemCanBeMoved,
101
- openQuantitySelector,
102
- checkIfItemShouldDragEnd,
103
- dragScale,
104
- isSelectingShortcut,
105
- equipmentSet,
106
- }) => {
107
- const [isTooltipVisible, setTooltipVisible] = useState(false);
108
- const [isTooltipMobileVisible, setIsTooltipMobileVisible] = useState(false);
109
-
110
- const [isContextMenuVisible, setIsContextMenuVisible] = useState(false);
111
-
112
- const [isFocused, setIsFocused] = useState(false);
113
- const [wasDragged, setWasDragged] = useState(false);
114
- const [dragPosition, setDragPosition] = useState<IPosition>({ x: 0, y: 0 });
115
- const [dropPosition, setDropPosition] = useState<IPosition | null>(null);
116
- const dragContainer = useRef<HTMLDivElement>(null);
117
-
118
- const [contextActions, setContextActions] = useState<IContextMenuItem[]>(
119
- []
120
- );
121
-
122
- useEffect(() => {
123
- setDragPosition({ x: 0, y: 0 });
124
- setIsFocused(false);
125
-
126
- if (item) {
127
- setContextActions(generateContextMenu(item, containerType));
128
- }
129
- }, [item]);
130
-
131
- useEffect(() => {
132
- if (onDrop && item && dropPosition) {
133
- onDrop(item, dropPosition);
134
- }
135
- }, [dropPosition]);
136
-
137
- const getStackInfo = (itemId: string, stackQty: number) => {
138
- const isFractionalStackQty = stackQty % 1 !== 0;
139
- const isLargerThan999 = stackQty > 999;
140
-
141
- let qtyClassName = 'regular';
142
- if (isLargerThan999) qtyClassName = 'small';
143
- if (isFractionalStackQty) qtyClassName = 'xsmall';
144
-
145
- if (stackQty > 1) {
146
- return (
147
- <ItemQtyContainer key={`qty-${itemId}`}>
148
- <Ellipsis maxLines={1} maxWidth="48px">
149
- <ItemQty className={qtyClassName}> {stackQty} </ItemQty>
150
- </Ellipsis>
151
- </ItemQtyContainer>
152
- );
153
- }
154
- return undefined;
155
- };
156
-
157
- const renderItem = (itemToRender: IItem | null) => {
158
- const element = [];
159
-
160
- if (itemToRender?.texturePath) {
161
- element.push(
162
- <ErrorBoundary key={itemToRender._id}>
163
- <SpriteFromAtlas
164
- key={itemToRender._id}
165
- atlasIMG={atlasIMG}
166
- atlasJSON={atlasJSON}
167
- spriteKey={getItemTextureKeyPath(
168
- {
169
- key: itemToRender.texturePath,
170
- texturePath: itemToRender.texturePath,
171
- stackQty: itemToRender.stackQty || 1,
172
- },
173
- atlasJSON
174
- )}
175
- imgScale={3}
176
- imgClassname="sprite-from-atlas-img--item"
177
- />
178
- </ErrorBoundary>
179
- );
180
- }
181
- const stackInfo = getStackInfo(
182
- itemToRender?._id ?? '',
183
- itemToRender?.stackQty ?? 0
184
- );
185
- if (stackInfo) {
186
- element.push(stackInfo);
187
- }
188
-
189
- return element;
190
- };
191
-
192
- const renderEquipment = (itemToRender: IItem | null) => {
193
- if (
194
- itemToRender?.texturePath &&
195
- itemToRender.allowedEquipSlotType?.includes(slotSpriteMask!)
196
- ) {
197
- const element = [];
198
-
199
- element.push(
200
- <ErrorBoundary key={itemToRender._id}>
201
- <SpriteFromAtlas
202
- key={itemToRender._id}
203
- atlasIMG={atlasIMG}
204
- atlasJSON={atlasJSON}
205
- spriteKey={getItemTextureKeyPath(
206
- {
207
- key: itemToRender.texturePath,
208
- texturePath: itemToRender.texturePath,
209
- stackQty: itemToRender.stackQty || 1,
210
- },
211
- atlasJSON
212
- )}
213
- imgScale={3}
214
- imgClassname="sprite-from-atlas-img--item"
215
- />
216
- </ErrorBoundary>
217
- );
218
- const stackInfo = getStackInfo(
219
- itemToRender?._id ?? '',
220
- itemToRender?.stackQty ?? 0
221
- );
222
- if (stackInfo) {
223
- element.push(stackInfo);
224
- }
225
- return element;
226
- } else {
227
- return (
228
- <ErrorBoundary key={uuidv4()}>
229
- <SpriteFromAtlas
230
- key={uuidv4()}
231
- atlasIMG={atlasIMG}
232
- atlasJSON={atlasJSON}
233
- spriteKey={EquipmentSlotSpriteByType[slotSpriteMask!]}
234
- imgScale={3}
235
- grayScale={true}
236
- opacity={0.4}
237
- imgClassname="sprite-from-atlas-img--item"
238
- />
239
- </ErrorBoundary>
240
- );
241
- }
242
- };
243
-
244
- const onRenderSlot = (itemToRender: IItem | null) => {
245
- switch (containerType) {
246
- case ItemContainerType.Equipment:
247
- return renderEquipment(itemToRender);
248
- case ItemContainerType.Inventory:
249
- return renderItem(itemToRender);
250
- default:
251
- return renderItem(itemToRender);
252
- }
253
- };
254
-
255
- const resetItem = () => {
256
- setTooltipVisible(false);
257
- setWasDragged(false);
258
- };
259
-
260
- const onSuccesfulDrag = (quantity?: number) => {
261
- resetItem();
262
-
263
- if (quantity === -1) {
264
- setDragPosition({ x: 0, y: 0 });
265
- setIsFocused(false);
266
- } else if (item) {
267
- onDragEnd(quantity);
268
- }
269
- };
270
-
271
- return (
272
- <Container
273
- item={item}
274
- className="rpgui-icon empty-slot"
275
- onMouseUp={() => {
276
- const data = item ? item : null;
277
- if (onPlaceDrop) onPlaceDrop(data, slotIndex, containerType);
278
- }}
279
- onTouchEnd={e => {
280
- const { clientX, clientY } = e.changedTouches[0];
281
- const simulatedEvent = new MouseEvent('mouseup', {
282
- clientX,
283
- clientY,
284
- bubbles: true,
285
- });
286
-
287
- document
288
- .elementFromPoint(clientX, clientY)
289
- ?.dispatchEvent(simulatedEvent);
290
- }}
291
- isSelectingShortcut={
292
- isSelectingShortcut &&
293
- (item?.type === ItemType.Consumable || item?.type === ItemType.Tool)
294
- }
295
- >
296
- <Draggable
297
- axis={isSelectingShortcut ? 'none' : 'both'}
298
- defaultClassName={item ? 'draggable' : 'empty-slot'}
299
- scale={dragScale}
300
- onStop={(e, data) => {
301
- if (wasDragged && item && !isSelectingShortcut) {
302
- //@ts-ignore
303
- const classes: string[] = Array.from(e.target?.classList);
304
-
305
- const isOutsideDrop =
306
- classes.some(elm => {
307
- return elm.includes('rpgui-content');
308
- }) || classes.length === 0;
309
-
310
- if (isOutsideDrop) {
311
- setDropPosition({
312
- x: data.x,
313
- y: data.y,
314
- });
315
- }
316
-
317
- setWasDragged(false);
318
-
319
- const target = dragContainer.current;
320
- if (!target || !wasDragged) return;
321
-
322
- const style = window.getComputedStyle(target);
323
- const matrix = new DOMMatrixReadOnly(style.transform);
324
- const x = matrix.m41;
325
- const y = matrix.m42;
326
-
327
- setDragPosition({ x, y });
328
-
329
- setTimeout(() => {
330
- if (checkIfItemCanBeMoved()) {
331
- if (checkIfItemShouldDragEnd && !checkIfItemShouldDragEnd())
332
- return;
333
-
334
- if (
335
- item.stackQty &&
336
- item.stackQty !== 1 &&
337
- openQuantitySelector
338
- )
339
- openQuantitySelector(item.stackQty, onSuccesfulDrag);
340
- else onSuccesfulDrag(item.stackQty);
341
- } else {
342
- resetItem();
343
- setIsFocused(false);
344
- setDragPosition({ x: 0, y: 0 });
345
- }
346
- }, 100);
347
- } else if (item) {
348
- let isTouch = false;
349
- if (
350
- !isContextMenuDisabled &&
351
- e.type === 'touchend' &&
352
- !isSelectingShortcut
353
- ) {
354
- isTouch = true;
355
- setIsTooltipMobileVisible(true);
356
- }
357
-
358
- if (!isContextMenuDisabled && !isSelectingShortcut && !isTouch)
359
- setIsContextMenuVisible(!isContextMenuVisible);
360
-
361
- onPointerDown(item.type, containerType, item);
362
- }
363
- }}
364
- onStart={() => {
365
- if (!item || isSelectingShortcut) {
366
- return;
367
- }
368
-
369
- if (onDragStart) {
370
- onDragStart(item, slotIndex, containerType);
371
- }
372
- }}
373
- onDrag={(_e, data) => {
374
- if (
375
- Math.abs(data.x - dragPosition.x) > 5 ||
376
- Math.abs(data.y - dragPosition.y) > 5
377
- ) {
378
- setWasDragged(true);
379
- setIsFocused(true);
380
- }
381
- }}
382
- position={dragPosition}
383
- cancel=".empty-slot"
384
- >
385
- <ItemContainer
386
- ref={dragContainer}
387
- isFocused={isFocused}
388
- onMouseOver={event => {
389
- onMouseOver(event, slotIndex, item, event.clientX, event.clientY);
390
- }}
391
- onMouseOut={() => {
392
- if (onMouseOut) onMouseOut();
393
- }}
394
- onMouseEnter={() => {
395
- setTooltipVisible(true);
396
- }}
397
- onMouseLeave={() => {
398
- setTooltipVisible(false);
399
- }}
400
- >
401
- {onRenderSlot(item)}
402
- </ItemContainer>
403
- </Draggable>
404
-
405
- {isTooltipVisible && item && !isFocused && (
406
- <ItemTooltip
407
- item={item}
408
- atlasIMG={atlasIMG}
409
- atlasJSON={atlasJSON}
410
- equipmentSet={equipmentSet}
411
- />
412
- )}
413
-
414
- {isTooltipMobileVisible && item && (
415
- <MobileItemTooltip
416
- item={item}
417
- atlasIMG={atlasIMG}
418
- atlasJSON={atlasJSON}
419
- equipmentSet={equipmentSet}
420
- closeTooltip={() => {
421
- setIsTooltipMobileVisible(false);
422
- }}
423
- scale={dragScale}
424
- options={contextActions}
425
- onSelected={(optionId: string) => {
426
- setIsContextMenuVisible(false);
427
- if (item) {
428
- onSelected(optionId, item);
429
- }
430
- }}
431
- />
432
- )}
433
-
434
- {!isContextMenuDisabled && isContextMenuVisible && contextActions && (
435
- <RelativeListMenu
436
- options={contextActions}
437
- onSelected={(optionId: string) => {
438
- setIsContextMenuVisible(false);
439
- if (item) {
440
- onSelected(optionId, item);
441
- }
442
- }}
443
- onOutsideClick={() => {
444
- setIsContextMenuVisible(false);
445
- }}
446
- />
447
- )}
448
- </Container>
449
- );
450
- }
451
- );
452
-
453
- export const rarityColor = (item: IItem | null) => {
454
- switch (item?.rarity) {
455
- case ItemRarities.Uncommon:
456
- return 'rgba(13, 193, 13, 0.6)';
457
- case ItemRarities.Rare:
458
- return 'rgba(8, 104, 187, 0.6)';
459
- case ItemRarities.Epic:
460
- return 'rgba(191, 0, 255, 0.6)';
461
- case ItemRarities.Legendary:
462
- return 'rgba(255, 191, 0,0.6)';
463
- default:
464
- return null;
465
- }
466
- };
467
-
468
- interface ContainerTypes {
469
- item: IItem | null;
470
- isSelectingShortcut?: boolean;
471
- }
472
-
473
- const Container = styled.div<ContainerTypes>`
474
- margin: 0.1rem;
475
- .sprite-from-atlas-img--item {
476
- position: relative;
477
- top: 1.5rem;
478
- left: 1.5rem;
479
- border-color: ${({ item }) => rarityColor(item)};
480
- box-shadow: ${({ item }) => `0 0 5px 2px ${rarityColor(item)}`} inset, ${({
481
- item,
482
- }) => `0 0 4px 3px ${rarityColor(item)}`};
483
- //background-color: ${({ item }) => rarityColor(item)};
484
- }
485
- position: relative;
486
-
487
- &::before {
488
- content: '';
489
- position: absolute;
490
- top: 0;
491
- left: 0;
492
- width: 100%;
493
- height: 100%;
494
- z-index: 1;
495
- border-radius: 12px;
496
- pointer-events: none;
497
- animation: ${({ isSelectingShortcut }) =>
498
- isSelectingShortcut ? 'bg-color-change 1s infinite' : 'none'};
499
-
500
- @keyframes bg-color-change {
501
- 0% {
502
- background-color: rgba(255 255 255 / 0.5);
503
- }
504
- 50% {
505
- background-color: transparent;
506
- }
507
- 100% {
508
- background-color: rgba(255 255 255 / 0.5);
509
- }
510
- }
511
- }
512
- `;
513
-
514
- const ItemContainer = styled.div<{ isFocused?: boolean }>`
515
- width: 100%;
516
- height: 100%;
517
- position: relative;
518
-
519
- ${props => props.isFocused && 'z-index: 100; pointer-events: none;'}
520
- `;
521
-
522
- const ItemQtyContainer = styled.div`
523
- position: relative;
524
- width: 85%;
525
- height: 16px;
526
- top: 25px;
527
- left: 2px;
528
- pointer-events: none;
529
-
530
- display: flex;
531
- justify-content: flex-end;
532
- `;
533
-
534
- const ItemQty = styled.span`
535
- &.regular {
536
- font-size: ${uiFonts.size.small};
537
- }
538
- &.small {
539
- font-size: ${uiFonts.size.xsmall};
540
- }
541
- &.xsmall {
542
- font-size: ${uiFonts.size.xxsmall};
543
- }
544
- `;
1
+ import {
2
+ getItemTextureKeyPath,
3
+ IEquipmentSet,
4
+ IItem,
5
+ IItemContainer,
6
+ ItemContainerType,
7
+ ItemRarities,
8
+ ItemSlotType,
9
+ ItemType,
10
+ } from '@rpg-engine/shared';
11
+
12
+ import { observer } from 'mobx-react-lite';
13
+ import React, { useEffect, useRef, useState } from 'react';
14
+ import Draggable from 'react-draggable';
15
+ import styled from 'styled-components';
16
+ import { v4 as uuidv4 } from 'uuid';
17
+ import { uiFonts } from '../../../constants/uiFonts';
18
+ import { IPosition } from '../../../types/eventTypes';
19
+ import { RelativeListMenu } from '../../RelativeListMenu';
20
+ import { Ellipsis } from '../../shared/Ellipsis';
21
+ import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
22
+ import { ItemTooltip } from '../Cards/ItemTooltip';
23
+ import { MobileItemTooltip } from '../Cards/MobileItemTooltip';
24
+ import { ErrorBoundary } from './ErrorBoundary';
25
+ import { generateContextMenu, IContextMenuItem } from './itemContainerHelper';
26
+
27
+ export const EquipmentSlotSpriteByType: any = {
28
+ Neck: 'accessories/corruption-necklace.png',
29
+ LeftHand: 'swords/broad-sword.png',
30
+ Ring: 'rings/iron-ring.png',
31
+ Head: 'helmets/viking-helmet.png',
32
+ Torso: 'armors/iron-armor.png',
33
+ Legs: 'legs/studded-legs.png',
34
+ Feet: 'boots/iron-boots.png',
35
+ Inventory: 'containers/bag.png',
36
+ RightHand: 'shields/plate-shield.png',
37
+ Accessory: 'ranged-weapons/arrow.png',
38
+ };
39
+
40
+ interface IProps {
41
+ slotIndex: number;
42
+ item: IItem | null;
43
+ itemContainer?: IItemContainer | null;
44
+ itemContainerType: ItemContainerType | null;
45
+ slotSpriteMask?: ItemSlotType | null;
46
+ onSelected: (selectedOption: string, item: IItem) => void;
47
+ onMouseOver: (
48
+ event: any,
49
+ slotIndex: number,
50
+ item: IItem | null,
51
+ x: number,
52
+ y: number
53
+ ) => void;
54
+ onMouseOut?: () => void;
55
+ onPointerDown: (
56
+ ItemType: ItemType,
57
+ itemContainerType: ItemContainerType | null,
58
+ item: IItem
59
+ ) => void;
60
+ onDragStart: (
61
+ item: IItem,
62
+ slotIndex: number,
63
+ itemContainerType: ItemContainerType | null
64
+ ) => void;
65
+ onDragEnd: (quantity?: number) => void;
66
+ onOutsideDrop?: (item: IItem, position: IPosition) => void;
67
+ dragScale?: number;
68
+ checkIfItemCanBeMoved: () => boolean;
69
+ checkIfItemShouldDragEnd?: () => boolean;
70
+ openQuantitySelector?: (maxQuantity: number, callback: () => void) => void;
71
+ onPlaceDrop: (
72
+ item: IItem | null,
73
+ slotIndex: number,
74
+ itemContainerType: ItemContainerType | null
75
+ ) => void;
76
+ atlasJSON: any;
77
+ atlasIMG: any;
78
+ isContextMenuDisabled?: boolean;
79
+ isSelectingShortcut?: boolean;
80
+ equipmentSet?: IEquipmentSet | null;
81
+ }
82
+
83
+ export const ItemSlot: React.FC<IProps> = observer(
84
+ ({
85
+ slotIndex,
86
+ item,
87
+ itemContainerType: containerType,
88
+ slotSpriteMask,
89
+ onMouseOver,
90
+ onMouseOut,
91
+ onPointerDown,
92
+ onSelected,
93
+ atlasJSON,
94
+ atlasIMG,
95
+ isContextMenuDisabled = false,
96
+ onDragEnd,
97
+ onDragStart,
98
+ onPlaceDrop,
99
+ onOutsideDrop: onDrop,
100
+ checkIfItemCanBeMoved,
101
+ openQuantitySelector,
102
+ checkIfItemShouldDragEnd,
103
+ dragScale,
104
+ isSelectingShortcut,
105
+ equipmentSet,
106
+ }) => {
107
+ const [isTooltipVisible, setTooltipVisible] = useState(false);
108
+ const [isTooltipMobileVisible, setIsTooltipMobileVisible] = useState(false);
109
+
110
+ const [isContextMenuVisible, setIsContextMenuVisible] = useState(false);
111
+
112
+ const [isFocused, setIsFocused] = useState(false);
113
+ const [wasDragged, setWasDragged] = useState(false);
114
+ const [dragPosition, setDragPosition] = useState<IPosition>({ x: 0, y: 0 });
115
+ const [dropPosition, setDropPosition] = useState<IPosition | null>(null);
116
+ const dragContainer = useRef<HTMLDivElement>(null);
117
+
118
+ const [contextActions, setContextActions] = useState<IContextMenuItem[]>(
119
+ []
120
+ );
121
+
122
+ useEffect(() => {
123
+ setDragPosition({ x: 0, y: 0 });
124
+ setIsFocused(false);
125
+
126
+ if (item) {
127
+ setContextActions(generateContextMenu(item, containerType));
128
+ }
129
+ }, [item]);
130
+
131
+ useEffect(() => {
132
+ if (onDrop && item && dropPosition) {
133
+ onDrop(item, dropPosition);
134
+ }
135
+ }, [dropPosition]);
136
+
137
+ const getStackInfo = (itemId: string, stackQty: number) => {
138
+ const isFractionalStackQty = stackQty % 1 !== 0;
139
+ const isLargerThan999 = stackQty > 999;
140
+
141
+ let qtyClassName = 'regular';
142
+ if (isLargerThan999) qtyClassName = 'small';
143
+ if (isFractionalStackQty) qtyClassName = 'xsmall';
144
+
145
+ if (stackQty > 1) {
146
+ return (
147
+ <ItemQtyContainer key={`qty-${itemId}`}>
148
+ <Ellipsis maxLines={1} maxWidth="48px">
149
+ <ItemQty className={qtyClassName}>
150
+ {Math.round(stackQty * 100) / 100}{' '}
151
+ </ItemQty>
152
+ </Ellipsis>
153
+ </ItemQtyContainer>
154
+ );
155
+ }
156
+ return undefined;
157
+ };
158
+
159
+ const renderItem = (itemToRender: IItem | null) => {
160
+ const element = [];
161
+
162
+ if (itemToRender?.texturePath) {
163
+ element.push(
164
+ <ErrorBoundary key={itemToRender._id}>
165
+ <SpriteFromAtlas
166
+ key={itemToRender._id}
167
+ atlasIMG={atlasIMG}
168
+ atlasJSON={atlasJSON}
169
+ spriteKey={getItemTextureKeyPath(
170
+ {
171
+ key: itemToRender.texturePath,
172
+ texturePath: itemToRender.texturePath,
173
+ stackQty: itemToRender.stackQty || 1,
174
+ },
175
+ atlasJSON
176
+ )}
177
+ imgScale={3}
178
+ imgClassname="sprite-from-atlas-img--item"
179
+ />
180
+ </ErrorBoundary>
181
+ );
182
+ }
183
+ const stackInfo = getStackInfo(
184
+ itemToRender?._id ?? '',
185
+ itemToRender?.stackQty ?? 0
186
+ );
187
+ if (stackInfo) {
188
+ element.push(stackInfo);
189
+ }
190
+
191
+ return element;
192
+ };
193
+
194
+ const renderEquipment = (itemToRender: IItem | null) => {
195
+ if (
196
+ itemToRender?.texturePath &&
197
+ itemToRender.allowedEquipSlotType?.includes(slotSpriteMask!)
198
+ ) {
199
+ const element = [];
200
+
201
+ element.push(
202
+ <ErrorBoundary key={itemToRender._id}>
203
+ <SpriteFromAtlas
204
+ key={itemToRender._id}
205
+ atlasIMG={atlasIMG}
206
+ atlasJSON={atlasJSON}
207
+ spriteKey={getItemTextureKeyPath(
208
+ {
209
+ key: itemToRender.texturePath,
210
+ texturePath: itemToRender.texturePath,
211
+ stackQty: itemToRender.stackQty || 1,
212
+ },
213
+ atlasJSON
214
+ )}
215
+ imgScale={3}
216
+ imgClassname="sprite-from-atlas-img--item"
217
+ />
218
+ </ErrorBoundary>
219
+ );
220
+ const stackInfo = getStackInfo(
221
+ itemToRender?._id ?? '',
222
+ itemToRender?.stackQty ?? 0
223
+ );
224
+ if (stackInfo) {
225
+ element.push(stackInfo);
226
+ }
227
+ return element;
228
+ } else {
229
+ return (
230
+ <ErrorBoundary key={uuidv4()}>
231
+ <SpriteFromAtlas
232
+ key={uuidv4()}
233
+ atlasIMG={atlasIMG}
234
+ atlasJSON={atlasJSON}
235
+ spriteKey={EquipmentSlotSpriteByType[slotSpriteMask!]}
236
+ imgScale={3}
237
+ grayScale={true}
238
+ opacity={0.4}
239
+ imgClassname="sprite-from-atlas-img--item"
240
+ />
241
+ </ErrorBoundary>
242
+ );
243
+ }
244
+ };
245
+
246
+ const onRenderSlot = (itemToRender: IItem | null) => {
247
+ switch (containerType) {
248
+ case ItemContainerType.Equipment:
249
+ return renderEquipment(itemToRender);
250
+ case ItemContainerType.Inventory:
251
+ return renderItem(itemToRender);
252
+ default:
253
+ return renderItem(itemToRender);
254
+ }
255
+ };
256
+
257
+ const resetItem = () => {
258
+ setTooltipVisible(false);
259
+ setWasDragged(false);
260
+ };
261
+
262
+ const onSuccesfulDrag = (quantity?: number) => {
263
+ resetItem();
264
+
265
+ if (quantity === -1) {
266
+ setDragPosition({ x: 0, y: 0 });
267
+ setIsFocused(false);
268
+ } else if (item) {
269
+ onDragEnd(quantity);
270
+ }
271
+ };
272
+
273
+ return (
274
+ <Container
275
+ item={item}
276
+ className="rpgui-icon empty-slot"
277
+ onMouseUp={() => {
278
+ const data = item ? item : null;
279
+ if (onPlaceDrop) onPlaceDrop(data, slotIndex, containerType);
280
+ }}
281
+ onTouchEnd={e => {
282
+ const { clientX, clientY } = e.changedTouches[0];
283
+ const simulatedEvent = new MouseEvent('mouseup', {
284
+ clientX,
285
+ clientY,
286
+ bubbles: true,
287
+ });
288
+
289
+ document
290
+ .elementFromPoint(clientX, clientY)
291
+ ?.dispatchEvent(simulatedEvent);
292
+ }}
293
+ isSelectingShortcut={
294
+ isSelectingShortcut &&
295
+ (item?.type === ItemType.Consumable || item?.type === ItemType.Tool)
296
+ }
297
+ >
298
+ <Draggable
299
+ axis={isSelectingShortcut ? 'none' : 'both'}
300
+ defaultClassName={item ? 'draggable' : 'empty-slot'}
301
+ scale={dragScale}
302
+ onStop={(e, data) => {
303
+ if (wasDragged && item && !isSelectingShortcut) {
304
+ //@ts-ignore
305
+ const classes: string[] = Array.from(e.target?.classList);
306
+
307
+ const isOutsideDrop =
308
+ classes.some(elm => {
309
+ return elm.includes('rpgui-content');
310
+ }) || classes.length === 0;
311
+
312
+ if (isOutsideDrop) {
313
+ setDropPosition({
314
+ x: data.x,
315
+ y: data.y,
316
+ });
317
+ }
318
+
319
+ setWasDragged(false);
320
+
321
+ const target = dragContainer.current;
322
+ if (!target || !wasDragged) return;
323
+
324
+ const style = window.getComputedStyle(target);
325
+ const matrix = new DOMMatrixReadOnly(style.transform);
326
+ const x = matrix.m41;
327
+ const y = matrix.m42;
328
+
329
+ setDragPosition({ x, y });
330
+
331
+ setTimeout(() => {
332
+ if (checkIfItemCanBeMoved()) {
333
+ if (checkIfItemShouldDragEnd && !checkIfItemShouldDragEnd())
334
+ return;
335
+
336
+ if (
337
+ item.stackQty &&
338
+ item.stackQty !== 1 &&
339
+ openQuantitySelector
340
+ )
341
+ openQuantitySelector(item.stackQty, onSuccesfulDrag);
342
+ else onSuccesfulDrag(item.stackQty);
343
+ } else {
344
+ resetItem();
345
+ setIsFocused(false);
346
+ setDragPosition({ x: 0, y: 0 });
347
+ }
348
+ }, 100);
349
+ } else if (item) {
350
+ let isTouch = false;
351
+ if (
352
+ !isContextMenuDisabled &&
353
+ e.type === 'touchend' &&
354
+ !isSelectingShortcut
355
+ ) {
356
+ isTouch = true;
357
+ setIsTooltipMobileVisible(true);
358
+ }
359
+
360
+ if (!isContextMenuDisabled && !isSelectingShortcut && !isTouch)
361
+ setIsContextMenuVisible(!isContextMenuVisible);
362
+
363
+ onPointerDown(item.type, containerType, item);
364
+ }
365
+ }}
366
+ onStart={() => {
367
+ if (!item || isSelectingShortcut) {
368
+ return;
369
+ }
370
+
371
+ if (onDragStart) {
372
+ onDragStart(item, slotIndex, containerType);
373
+ }
374
+ }}
375
+ onDrag={(_e, data) => {
376
+ if (
377
+ Math.abs(data.x - dragPosition.x) > 5 ||
378
+ Math.abs(data.y - dragPosition.y) > 5
379
+ ) {
380
+ setWasDragged(true);
381
+ setIsFocused(true);
382
+ }
383
+ }}
384
+ position={dragPosition}
385
+ cancel=".empty-slot"
386
+ >
387
+ <ItemContainer
388
+ ref={dragContainer}
389
+ isFocused={isFocused}
390
+ onMouseOver={event => {
391
+ onMouseOver(event, slotIndex, item, event.clientX, event.clientY);
392
+ }}
393
+ onMouseOut={() => {
394
+ if (onMouseOut) onMouseOut();
395
+ }}
396
+ onMouseEnter={() => {
397
+ setTooltipVisible(true);
398
+ }}
399
+ onMouseLeave={() => {
400
+ setTooltipVisible(false);
401
+ }}
402
+ >
403
+ {onRenderSlot(item)}
404
+ </ItemContainer>
405
+ </Draggable>
406
+
407
+ {isTooltipVisible && item && !isFocused && (
408
+ <ItemTooltip
409
+ item={item}
410
+ atlasIMG={atlasIMG}
411
+ atlasJSON={atlasJSON}
412
+ equipmentSet={equipmentSet}
413
+ />
414
+ )}
415
+
416
+ {isTooltipMobileVisible && item && (
417
+ <MobileItemTooltip
418
+ item={item}
419
+ atlasIMG={atlasIMG}
420
+ atlasJSON={atlasJSON}
421
+ equipmentSet={equipmentSet}
422
+ closeTooltip={() => {
423
+ setIsTooltipMobileVisible(false);
424
+ }}
425
+ scale={dragScale}
426
+ options={contextActions}
427
+ onSelected={(optionId: string) => {
428
+ setIsContextMenuVisible(false);
429
+ if (item) {
430
+ onSelected(optionId, item);
431
+ }
432
+ }}
433
+ />
434
+ )}
435
+
436
+ {!isContextMenuDisabled && isContextMenuVisible && contextActions && (
437
+ <RelativeListMenu
438
+ options={contextActions}
439
+ onSelected={(optionId: string) => {
440
+ setIsContextMenuVisible(false);
441
+ if (item) {
442
+ onSelected(optionId, item);
443
+ }
444
+ }}
445
+ onOutsideClick={() => {
446
+ setIsContextMenuVisible(false);
447
+ }}
448
+ />
449
+ )}
450
+ </Container>
451
+ );
452
+ }
453
+ );
454
+
455
+ export const rarityColor = (item: IItem | null) => {
456
+ switch (item?.rarity) {
457
+ case ItemRarities.Uncommon:
458
+ return 'rgba(13, 193, 13, 0.6)';
459
+ case ItemRarities.Rare:
460
+ return 'rgba(8, 104, 187, 0.6)';
461
+ case ItemRarities.Epic:
462
+ return 'rgba(191, 0, 255, 0.6)';
463
+ case ItemRarities.Legendary:
464
+ return 'rgba(255, 191, 0,0.6)';
465
+ default:
466
+ return null;
467
+ }
468
+ };
469
+
470
+ interface ContainerTypes {
471
+ item: IItem | null;
472
+ isSelectingShortcut?: boolean;
473
+ }
474
+
475
+ const Container = styled.div<ContainerTypes>`
476
+ margin: 0.1rem;
477
+ .sprite-from-atlas-img--item {
478
+ position: relative;
479
+ top: 1.5rem;
480
+ left: 1.5rem;
481
+ border-color: ${({ item }) => rarityColor(item)};
482
+ box-shadow: ${({ item }) => `0 0 5px 2px ${rarityColor(item)}`} inset, ${({
483
+ item,
484
+ }) => `0 0 4px 3px ${rarityColor(item)}`};
485
+ //background-color: ${({ item }) => rarityColor(item)};
486
+ }
487
+ position: relative;
488
+
489
+ &::before {
490
+ content: '';
491
+ position: absolute;
492
+ top: 0;
493
+ left: 0;
494
+ width: 100%;
495
+ height: 100%;
496
+ z-index: 1;
497
+ border-radius: 12px;
498
+ pointer-events: none;
499
+ animation: ${({ isSelectingShortcut }) =>
500
+ isSelectingShortcut ? 'bg-color-change 1s infinite' : 'none'};
501
+
502
+ @keyframes bg-color-change {
503
+ 0% {
504
+ background-color: rgba(255 255 255 / 0.5);
505
+ }
506
+ 50% {
507
+ background-color: transparent;
508
+ }
509
+ 100% {
510
+ background-color: rgba(255 255 255 / 0.5);
511
+ }
512
+ }
513
+ }
514
+ `;
515
+
516
+ const ItemContainer = styled.div<{ isFocused?: boolean }>`
517
+ width: 100%;
518
+ height: 100%;
519
+ position: relative;
520
+
521
+ ${props => props.isFocused && 'z-index: 100; pointer-events: none;'}
522
+ `;
523
+
524
+ const ItemQtyContainer = styled.div`
525
+ position: relative;
526
+ width: 85%;
527
+ height: 16px;
528
+ top: 25px;
529
+ left: 2px;
530
+ pointer-events: none;
531
+
532
+ display: flex;
533
+ justify-content: flex-end;
534
+ `;
535
+
536
+ const ItemQty = styled.span`
537
+ &.regular {
538
+ font-size: ${uiFonts.size.small};
539
+ }
540
+ &.small {
541
+ font-size: ${uiFonts.size.xsmall};
542
+ }
543
+ &.xsmall {
544
+ font-size: ${uiFonts.size.xxsmall};
545
+ }
546
+ `;