@rpg-engine/long-bow 0.5.21 → 0.5.22

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.5.21",
3
+ "version": "0.5.22",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -13,6 +13,7 @@ interface IProps {
13
13
  onOutsideClick?: () => void;
14
14
  initialPosition?: IPosition;
15
15
  scale?: number;
16
+ width?: string;
16
17
  }
17
18
 
18
19
  export const SlotsContainer: React.FC<IProps> = ({
@@ -25,6 +26,7 @@ export const SlotsContainer: React.FC<IProps> = ({
25
26
  onOutsideClick,
26
27
  initialPosition,
27
28
  scale,
29
+ width,
28
30
  }) => {
29
31
  return (
30
32
  <DraggableContainer
@@ -35,7 +37,7 @@ export const SlotsContainer: React.FC<IProps> = ({
35
37
  onClose();
36
38
  }
37
39
  }}
38
- width="450px"
40
+ width={width ?? '415px'}
39
41
  cancelDrag=".item-container-body, #shortcuts_list"
40
42
  onPositionChange={({ x, y }) => {
41
43
  if (onPositionChange) {
@@ -10,7 +10,9 @@ import React from 'react';
10
10
  import styled from 'styled-components';
11
11
  import { IPosition } from '../../types/eventTypes';
12
12
  import { DraggableContainer } from '../DraggableContainer';
13
+ import DraggedItem from '../Item/Inventory/DraggedItem';
13
14
  import { ItemSlot } from '../Item/Inventory/ItemSlot';
15
+ import { DraggingProvider } from '../Item/Inventory/context/DraggingContext';
14
16
  import { RPGUIContainerTypes } from '../RPGUIContainer';
15
17
 
16
18
  export interface IEquipmentSetProps {
@@ -159,25 +161,28 @@ export const EquipmentSet: React.FC<IEquipmentSetProps> = ({
159
161
  };
160
162
 
161
163
  return (
162
- <DraggableContainer
163
- title={'Equipments'}
164
- type={RPGUIContainerTypes.Framed}
165
- onCloseButton={() => {
166
- if (onClose) onClose();
167
- }}
168
- width="330px"
169
- cancelDrag=".equipment-container-body"
170
- scale={scale}
171
- initialPosition={initialPosition}
172
- onPositionChangeEnd={onPositionChangeEnd}
173
- onPositionChangeStart={onPositionChangeStart}
174
- >
175
- <EquipmentSetContainer className="equipment-container-body">
176
- <EquipmentColumn>{onRenderEquipmentSlotRange(0, 3)}</EquipmentColumn>
177
- <EquipmentColumn>{onRenderEquipmentSlotRange(3, 7)}</EquipmentColumn>
178
- <EquipmentColumn>{onRenderEquipmentSlotRange(7, 10)}</EquipmentColumn>
179
- </EquipmentSetContainer>
180
- </DraggableContainer>
164
+ <DraggingProvider>
165
+ <DraggedItem atlasIMG={atlasIMG} atlasJSON={atlasJSON} />
166
+ <DraggableContainer
167
+ title={'Equipments'}
168
+ type={RPGUIContainerTypes.Framed}
169
+ onCloseButton={() => {
170
+ if (onClose) onClose();
171
+ }}
172
+ width="330px"
173
+ cancelDrag=".equipment-container-body"
174
+ scale={scale}
175
+ initialPosition={initialPosition}
176
+ onPositionChangeEnd={onPositionChangeEnd}
177
+ onPositionChangeStart={onPositionChangeStart}
178
+ >
179
+ <EquipmentSetContainer className="equipment-container-body">
180
+ <EquipmentColumn>{onRenderEquipmentSlotRange(0, 3)}</EquipmentColumn>
181
+ <EquipmentColumn>{onRenderEquipmentSlotRange(3, 7)}</EquipmentColumn>
182
+ <EquipmentColumn>{onRenderEquipmentSlotRange(7, 10)}</EquipmentColumn>
183
+ </EquipmentSetContainer>
184
+ </DraggableContainer>
185
+ </DraggingProvider>
181
186
  );
182
187
  };
183
188
 
@@ -0,0 +1,107 @@
1
+ import { getItemTextureKeyPath } from '@rpg-engine/shared';
2
+ import React from 'react';
3
+ import styled from 'styled-components';
4
+ import { useCursorPosition } from '../../../hooks/useMousePosition';
5
+ import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
6
+ import { onRenderStackInfo } from './ItemSlotQty/ItemSlotQty';
7
+ import { useDragging } from './context/DraggingContext';
8
+
9
+ const CONTAINER_SIZE = 32;
10
+ const OFFSET = CONTAINER_SIZE / 2;
11
+
12
+ interface IProps {
13
+ atlasJSON: any;
14
+ atlasIMG: any;
15
+ }
16
+
17
+ export const DraggedItem = ({
18
+ atlasJSON,
19
+ atlasIMG,
20
+ }: IProps): JSX.Element | null => {
21
+ const { item } = useDragging();
22
+ const { x, y } = useCursorPosition();
23
+
24
+ if (!item) {
25
+ return null;
26
+ }
27
+
28
+ const centeredX = x - OFFSET;
29
+ const centeredY = y - OFFSET;
30
+
31
+ const stackInfo = onRenderStackInfo(item?._id ?? '', item?.stackQty ?? 0);
32
+
33
+ return (
34
+ <Container>
35
+ <SpriteContainer x={centeredX} y={centeredY}>
36
+ <SpriteFromAtlas
37
+ key={item._id}
38
+ atlasIMG={atlasIMG}
39
+ atlasJSON={atlasJSON}
40
+ spriteKey={getItemTextureKeyPath(
41
+ {
42
+ key: item.texturePath,
43
+ texturePath: item.texturePath,
44
+ stackQty: item.stackQty || 1,
45
+ isStackable: item.isStackable,
46
+ },
47
+ atlasJSON
48
+ )}
49
+ imgScale={3}
50
+ imgClassname="sprite-from-atlas-img--item"
51
+ />
52
+ {stackInfo}
53
+ </SpriteContainer>
54
+ </Container>
55
+ );
56
+ };
57
+
58
+ interface IContainer {
59
+ x: number;
60
+ y: number;
61
+ }
62
+
63
+ const pulse = `
64
+ @keyframes pulse {
65
+ 0%, 100% {
66
+ transform: scale(1) rotate(-3deg);
67
+ }
68
+ 50% {
69
+ transform: scale(0.95) rotate(-3deg);
70
+ }
71
+ }
72
+ `;
73
+
74
+ const Container = styled.div`
75
+ position: relative;
76
+ `;
77
+
78
+ const SpriteContainer = styled.div.attrs<IContainer>(props => ({
79
+ style: {
80
+ left: `${props.x}px`,
81
+ top: `${props.y}px`,
82
+ },
83
+ }))<IContainer>`
84
+ ${pulse}
85
+
86
+ position: absolute;
87
+ z-index: 100;
88
+ pointer-events: none;
89
+
90
+ width: ${CONTAINER_SIZE}px;
91
+ height: ${CONTAINER_SIZE}px;
92
+
93
+ transform: rotate(-3deg);
94
+
95
+ filter: grayscale(100%);
96
+ opacity: 0.35;
97
+
98
+ animation: pulse 2s infinite;
99
+
100
+ .item-slot-qty {
101
+ position: absolute;
102
+ bottom: 0;
103
+ margin-left: 0.8rem;
104
+ }
105
+ `;
106
+
107
+ export default DraggedItem;
@@ -14,7 +14,9 @@ import { ItemQuantitySelector } from './ItemQuantitySelector';
14
14
  import { IPosition } from '../../../types/eventTypes';
15
15
  import ModalPortal from '../../Abstractions/ModalPortal';
16
16
  import { ShortcutsSetter } from '../../Shortcuts/ShortcutsSetter';
17
+ import { DraggedItem } from './DraggedItem';
17
18
  import { ItemSlot } from './ItemSlot';
19
+ import { DraggingProvider } from './context/DraggingContext';
18
20
 
19
21
  export interface IItemContainerProps {
20
22
  itemContainer: IItemContainer;
@@ -157,7 +159,8 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
157
159
  };
158
160
 
159
161
  return (
160
- <>
162
+ <DraggingProvider>
163
+ <DraggedItem atlasIMG={atlasIMG} atlasJSON={atlasJSON} />
161
164
  <SlotsContainer
162
165
  title={itemContainer.name || 'Container'}
163
166
  onClose={onClose}
@@ -207,7 +210,7 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
207
210
  </QuantitySelectorContainer>
208
211
  </ModalPortal>
209
212
  )}
210
- </>
213
+ </DraggingProvider>
211
214
  );
212
215
  };
213
216
 
@@ -218,7 +221,7 @@ const ItemsContainer = styled.div`
218
221
  max-height: 270px;
219
222
  overflow-y: auto;
220
223
  overflow-x: hidden;
221
- width: 450px;
224
+ width: 415px;
222
225
  `;
223
226
 
224
227
  const QuantitySelectorContainer = styled.div`
@@ -1,5 +1,4 @@
1
1
  import {
2
- getItemTextureKeyPath,
3
2
  IEquipmentSet,
4
3
  IItem,
5
4
  IItemContainer,
@@ -13,16 +12,11 @@ import { observer } from 'mobx-react-lite';
13
12
  import React, { useEffect, useRef, useState } from 'react';
14
13
  import Draggable from 'react-draggable';
15
14
  import styled from 'styled-components';
16
- import { v4 as uuidv4 } from 'uuid';
17
- import { uiFonts } from '../../../constants/uiFonts';
18
15
  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';
16
+ import { ItemSlotRenderer } from './ItemSlotRenderer';
17
+ import { ItemSlotToolTips } from './ItemSlotTooltips';
18
+ import { useDragging } from './context/DraggingContext';
19
+ import { IContextMenuItem, generateContextMenu } from './itemContainerHelper';
26
20
 
27
21
  export const EquipmentSlotSpriteByType: any = {
28
22
  Neck: 'accessories/corruption-necklace.png',
@@ -123,6 +117,8 @@ export const ItemSlot: React.FC<IProps> = observer(
123
117
  const [dropPosition, setDropPosition] = useState<IPosition | null>(null);
124
118
  const dragContainer = useRef<HTMLDivElement>(null);
125
119
 
120
+ const { setDraggingItem } = useDragging();
121
+
126
122
  const [contextActions, setContextActions] = useState<IContextMenuItem[]>(
127
123
  []
128
124
  );
@@ -144,134 +140,12 @@ export const ItemSlot: React.FC<IProps> = observer(
144
140
  }
145
141
  }, [dropPosition]);
146
142
 
147
- const getStackInfo = (itemId: string, stackQty: number) => {
148
- const isFractionalStackQty = stackQty % 1 !== 0;
149
- const isLargerThan999 = stackQty > 999;
150
-
151
- let qtyClassName = 'regular';
152
- if (isLargerThan999) qtyClassName = 'small';
153
- if (isFractionalStackQty) qtyClassName = 'xsmall';
154
-
155
- if (stackQty > 1) {
156
- return (
157
- <ItemQtyContainer key={`qty-${itemId}`}>
158
- <Ellipsis maxLines={1} maxWidth="48px">
159
- <ItemQty className={qtyClassName}>
160
- {Math.round(stackQty * 100) / 100}{' '}
161
- </ItemQty>
162
- </Ellipsis>
163
- </ItemQtyContainer>
164
- );
165
- }
166
- return undefined;
167
- };
168
-
169
- const renderItem = (itemToRender: IItem | null) => {
170
- const element = [];
171
-
172
- if (itemToRender?.texturePath) {
173
- element.push(
174
- <ErrorBoundary key={itemToRender._id}>
175
- <SpriteFromAtlas
176
- key={itemToRender._id}
177
- atlasIMG={atlasIMG}
178
- atlasJSON={atlasJSON}
179
- spriteKey={getItemTextureKeyPath(
180
- {
181
- key: itemToRender.texturePath,
182
- texturePath: itemToRender.texturePath,
183
- stackQty: itemToRender.stackQty || 1,
184
- isStackable: itemToRender.isStackable,
185
- },
186
- atlasJSON
187
- )}
188
- imgScale={3}
189
- imgClassname="sprite-from-atlas-img--item"
190
- />
191
- </ErrorBoundary>
192
- );
193
- }
194
- const stackInfo = getStackInfo(
195
- itemToRender?._id ?? '',
196
- itemToRender?.stackQty ?? 0
197
- );
198
- if (stackInfo) {
199
- element.push(stackInfo);
200
- }
201
-
202
- return element;
203
- };
204
-
205
- const renderEquipment = (itemToRender: IItem | null) => {
206
- if (
207
- itemToRender?.texturePath &&
208
- itemToRender.allowedEquipSlotType?.includes(slotSpriteMask!)
209
- ) {
210
- const element = [];
211
-
212
- element.push(
213
- <ErrorBoundary key={itemToRender._id}>
214
- <SpriteFromAtlas
215
- key={itemToRender._id}
216
- atlasIMG={atlasIMG}
217
- atlasJSON={atlasJSON}
218
- spriteKey={getItemTextureKeyPath(
219
- {
220
- key: itemToRender.texturePath,
221
- texturePath: itemToRender.texturePath,
222
- stackQty: itemToRender.stackQty || 1,
223
- isStackable: itemToRender.isStackable,
224
- },
225
- atlasJSON
226
- )}
227
- imgScale={3}
228
- imgClassname="sprite-from-atlas-img--item"
229
- />
230
- </ErrorBoundary>
231
- );
232
- const stackInfo = getStackInfo(
233
- itemToRender?._id ?? '',
234
- itemToRender?.stackQty ?? 0
235
- );
236
- if (stackInfo) {
237
- element.push(stackInfo);
238
- }
239
- return element;
240
- } else {
241
- return (
242
- <ErrorBoundary key={uuidv4()}>
243
- <SpriteFromAtlas
244
- key={uuidv4()}
245
- atlasIMG={atlasIMG}
246
- atlasJSON={atlasJSON}
247
- spriteKey={EquipmentSlotSpriteByType[slotSpriteMask!]}
248
- imgScale={3}
249
- grayScale={true}
250
- opacity={0.4}
251
- imgClassname="sprite-from-atlas-img--item"
252
- />
253
- </ErrorBoundary>
254
- );
255
- }
256
- };
257
-
258
- const onRenderSlot = (itemToRender: IItem | null) => {
259
- switch (containerType) {
260
- case ItemContainerType.Equipment:
261
- return renderEquipment(itemToRender);
262
- case ItemContainerType.Inventory:
263
- return renderItem(itemToRender);
264
- default:
265
- return renderItem(itemToRender);
266
- }
267
- };
268
-
269
143
  const resetItem = () => {
270
144
  setTooltipVisible(false);
271
145
  setWasDragged(false);
272
146
  };
273
147
 
274
- const onSuccesfulDrag = (quantity?: number) => {
148
+ const onSuccessfulDrag = (quantity?: number) => {
275
149
  resetItem();
276
150
 
277
151
  if (quantity === -1) {
@@ -321,6 +195,8 @@ export const ItemSlot: React.FC<IProps> = observer(
321
195
  scale={dragScale}
322
196
  disabled={onDragStart === undefined || onDragEnd === undefined}
323
197
  onStop={(e, data) => {
198
+ setDraggingItem(null);
199
+
324
200
  const target = e.target as HTMLElement;
325
201
  if (
326
202
  target?.id.includes('shortcutSetter') &&
@@ -371,14 +247,14 @@ export const ItemSlot: React.FC<IProps> = observer(
371
247
  item.stackQty !== 1 &&
372
248
  openQuantitySelector
373
249
  )
374
- openQuantitySelector(item.stackQty, onSuccesfulDrag);
375
- else onSuccesfulDrag(item.stackQty);
250
+ openQuantitySelector(item.stackQty, onSuccessfulDrag);
251
+ else onSuccessfulDrag(item.stackQty);
376
252
  } else {
377
253
  resetItem();
378
254
  setIsFocused(false);
379
255
  setDragPosition({ x: 0, y: 0 });
380
256
  }
381
- }, 100);
257
+ }, 50);
382
258
  } else if (item) {
383
259
  let isTouch = false;
384
260
  if (
@@ -410,6 +286,8 @@ export const ItemSlot: React.FC<IProps> = observer(
410
286
  return;
411
287
  }
412
288
 
289
+ setDraggingItem(item);
290
+
413
291
  if (onDragStart && containerType) {
414
292
  onDragStart(item, slotIndex, containerType);
415
293
  }
@@ -448,54 +326,37 @@ export const ItemSlot: React.FC<IProps> = observer(
448
326
  setTooltipVisible(false);
449
327
  }}
450
328
  >
451
- {onRenderSlot(item)}
329
+ <ItemSlotRenderer
330
+ item={item}
331
+ slotSpriteMask={slotSpriteMask}
332
+ atlasIMG={atlasIMG}
333
+ atlasJSON={atlasJSON}
334
+ containerType={containerType}
335
+ />
452
336
  </ItemContainer>
453
337
  </Draggable>
454
338
 
455
- {isTooltipVisible && item && !isFocused && (
456
- <ItemTooltip
457
- item={item}
458
- atlasIMG={atlasIMG}
459
- atlasJSON={atlasJSON}
460
- equipmentSet={equipmentSet}
461
- />
462
- )}
463
-
464
- {isTooltipMobileVisible && item && (
465
- <MobileItemTooltip
466
- item={item}
467
- atlasIMG={atlasIMG}
468
- atlasJSON={atlasJSON}
469
- equipmentSet={equipmentSet}
470
- closeTooltip={() => {
471
- setIsTooltipMobileVisible(false);
472
- }}
473
- scale={dragScale}
474
- options={contextActions}
475
- onSelected={(optionId: string) => {
476
- setIsContextMenuVisible(false);
477
- if (item) {
478
- onSelected?.(optionId, item);
479
- }
480
- }}
481
- />
482
- )}
483
-
484
- {!isContextMenuDisabled && isContextMenuVisible && contextActions && (
485
- <RelativeListMenu
486
- options={contextActions}
487
- onSelected={(optionId: string) => {
488
- setIsContextMenuVisible(false);
489
- if (item) {
490
- onSelected?.(optionId, item);
491
- }
492
- }}
493
- onOutsideClick={() => {
494
- setIsContextMenuVisible(false);
495
- }}
496
- pos={contextMenuPosition}
497
- />
498
- )}
339
+ <ItemSlotToolTips
340
+ isTooltipVisible={isTooltipVisible}
341
+ isTooltipMobileVisible={isTooltipMobileVisible}
342
+ setIsTooltipMobileVisible={setIsTooltipMobileVisible}
343
+ isFocused={isFocused}
344
+ isContextMenuVisible={isContextMenuVisible}
345
+ isContextMenuDisabled={isContextMenuDisabled}
346
+ item={item}
347
+ contextActions={contextActions}
348
+ contextMenuPosition={contextMenuPosition}
349
+ dragScale={dragScale}
350
+ setIsContextMenuVisible={setIsContextMenuVisible}
351
+ onSelected={(optionId: string, item: IItem) => {
352
+ setIsContextMenuVisible(false);
353
+ if (onSelected) onSelected(optionId, item);
354
+ }}
355
+ atlasIMG={atlasIMG}
356
+ atlasJSON={atlasJSON}
357
+ equipmentSet={equipmentSet}
358
+ setIsTooltipVisible={setTooltipVisible}
359
+ />
499
360
  </Container>
500
361
  );
501
362
  }
@@ -518,6 +379,7 @@ export const rarityColor = (item: IItem | null) => {
518
379
 
519
380
  interface ContainerTypes {
520
381
  item: IItem | null;
382
+ containerType?: ItemContainerType | null;
521
383
  isSelectingShortcut?: boolean;
522
384
  }
523
385
 
@@ -525,9 +387,10 @@ const Container = styled.div<ContainerTypes>`
525
387
  margin: 0.1rem;
526
388
 
527
389
  .react-draggable-dragging {
528
- opacity: 0.5;
390
+ display: none;
529
391
  }
530
-
392
+
393
+
531
394
  .sprite-from-atlas-img--item {
532
395
  position: relative;
533
396
  top: 1.5rem;
@@ -568,33 +431,9 @@ const Container = styled.div<ContainerTypes>`
568
431
  `;
569
432
 
570
433
  const ItemContainer = styled.div<{ isFocused?: boolean }>`
571
- width: 100%;
572
- height: 100%;
434
+ width: 64px;
435
+ height: 64px;
436
+ // This fixes an issue where if you drag an item inside of a scrollable container (overflow), its cut out
573
437
  position: relative;
574
-
575
- ${props => props.isFocused && 'z-index: 100; pointer-events: none;'}
576
- `;
577
-
578
- const ItemQtyContainer = styled.div`
579
- position: relative;
580
- width: 85%;
581
- height: 16px;
582
- top: 25px;
583
- left: 2px;
584
- pointer-events: none;
585
-
586
- display: flex;
587
- justify-content: flex-end;
588
- `;
589
-
590
- const ItemQty = styled.span`
591
- &.regular {
592
- font-size: ${uiFonts.size.small};
593
- }
594
- &.small {
595
- font-size: ${uiFonts.size.xsmall};
596
- }
597
- &.xsmall {
598
- font-size: ${uiFonts.size.xxsmall};
599
- }
438
+ ${props => props.isFocused && 'z-index: 100; pointer-events: none;'};
600
439
  `;
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { uiFonts } from '../../../../constants/uiFonts';
4
+ import { Ellipsis } from '../../../shared/Ellipsis';
5
+
6
+ interface IProps {
7
+ itemId: string;
8
+ stackQty: number;
9
+ qtyClassName: string;
10
+ }
11
+
12
+ export const onRenderStackInfo = (itemId: string, stackQty: number) => {
13
+ const isFractionalStackQty = stackQty % 1 !== 0;
14
+ const isLargerThan999 = stackQty > 999;
15
+
16
+ let qtyClassName = 'regular';
17
+ if (isLargerThan999) qtyClassName = 'small';
18
+ if (isFractionalStackQty) qtyClassName = 'xsmall';
19
+
20
+ if (stackQty > 1) {
21
+ return (
22
+ <ItemSlotQty
23
+ itemId={itemId}
24
+ stackQty={stackQty}
25
+ qtyClassName={qtyClassName}
26
+ />
27
+ );
28
+ }
29
+ return undefined;
30
+ };
31
+
32
+ export const ItemSlotQty = ({
33
+ itemId,
34
+ stackQty,
35
+ qtyClassName,
36
+ }: IProps): JSX.Element => {
37
+ return (
38
+ <ItemQtyContainer key={`qty-${itemId}`} className="item-slot-qty">
39
+ <Ellipsis maxLines={1} maxWidth="48px">
40
+ <ItemQty className={qtyClassName}>
41
+ {Math.round(stackQty * 100) / 100}{' '}
42
+ </ItemQty>
43
+ </Ellipsis>
44
+ </ItemQtyContainer>
45
+ );
46
+ };
47
+
48
+ const ItemQtyContainer = styled.div`
49
+ position: relative;
50
+ width: 85%;
51
+ height: 16px;
52
+ top: 25px;
53
+ left: 2px;
54
+ pointer-events: none;
55
+
56
+ display: flex;
57
+ justify-content: flex-end;
58
+ `;
59
+
60
+ const ItemQty = styled.span`
61
+ &.regular {
62
+ font-size: ${uiFonts.size.small};
63
+ }
64
+ &.small {
65
+ font-size: ${uiFonts.size.xsmall};
66
+ }
67
+ &.xsmall {
68
+ font-size: ${uiFonts.size.xxsmall};
69
+ }
70
+ `;