@rpg-engine/long-bow 0.3.77 → 0.3.78

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