@rpg-engine/long-bow 0.3.86 → 0.3.87

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