@rpg-engine/long-bow 0.3.71 → 0.3.73

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