@rpg-engine/long-bow 0.3.53 → 0.3.55

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