@rpg-engine/long-bow 0.2.85 → 0.2.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.
- package/dist/components/Abstractions/SlotsContainer.d.ts +1 -0
- package/dist/components/DraggableContainer.d.ts +1 -0
- package/dist/components/Equipment/EquipmentSet.d.ts +4 -0
- package/dist/components/Input.d.ts +1 -0
- package/dist/components/Item/Inventory/ItemContainer.d.ts +8 -0
- package/dist/components/Item/Inventory/ItemQuantitySelector.d.ts +7 -0
- package/dist/components/Item/Inventory/ItemSlot.d.ts +5 -0
- package/dist/components/RangeSlider.d.ts +1 -0
- package/dist/long-bow.cjs.development.js +361 -75
- package/dist/long-bow.cjs.development.js.map +1 -1
- package/dist/long-bow.cjs.production.min.js +1 -1
- package/dist/long-bow.cjs.production.min.js.map +1 -1
- package/dist/long-bow.esm.js +362 -76
- package/dist/long-bow.esm.js.map +1 -1
- package/dist/stories/ItemQuantitySelector.stories.d.ts +5 -0
- package/package.json +1 -1
- package/src/components/Abstractions/SlotsContainer.tsx +3 -0
- package/src/components/DraggableContainer.tsx +3 -0
- package/src/components/Equipment/EquipmentSet.tsx +30 -0
- package/src/components/Input.tsx +6 -2
- package/src/components/Item/Inventory/ItemContainer.tsx +92 -6
- package/src/components/Item/Inventory/ItemQuantitySelector.tsx +137 -0
- package/src/components/Item/Inventory/ItemSlot.tsx +144 -25
- package/src/components/RangeSlider.tsx +37 -14
- package/src/mocks/itemContainer.mocks.ts +1 -1
- package/src/stories/EquipmentSet.stories.tsx +4 -0
- package/src/stories/ItemContainer.stories.tsx +82 -15
- package/src/stories/ItemQuantitySelector.stories.tsx +26 -0
- package/src/stories/RangeSlider.stories.tsx +10 -9
- package/src/.DS_Store +0 -0
- package/src/components/NPCDialog/.DS_Store +0 -0
- package/src/components/NPCDialog/img/.DS_Store +0 -0
- package/src/mocks/.DS_Store +0 -0
- package/src/mocks/atlas/.DS_Store +0 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Meta } from '@storybook/react';
|
|
2
|
+
import { IItemQuantitySelectorProps } from '../components/Item/Inventory/ItemQuantitySelector';
|
|
3
|
+
declare const meta: Meta;
|
|
4
|
+
export default meta;
|
|
5
|
+
export declare const Default: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, IItemQuantitySelectorProps>;
|
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@ interface IProps {
|
|
|
9
9
|
onClose?: () => void;
|
|
10
10
|
onPositionChange?: (position: IPosition) => void;
|
|
11
11
|
onOutsideClick?: () => void;
|
|
12
|
+
initialPosition?: IPosition;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export const SlotsContainer: React.FC<IProps> = ({
|
|
@@ -17,6 +18,7 @@ export const SlotsContainer: React.FC<IProps> = ({
|
|
|
17
18
|
onClose,
|
|
18
19
|
onPositionChange,
|
|
19
20
|
onOutsideClick,
|
|
21
|
+
initialPosition,
|
|
20
22
|
}) => {
|
|
21
23
|
return (
|
|
22
24
|
<DraggableContainer
|
|
@@ -35,6 +37,7 @@ export const SlotsContainer: React.FC<IProps> = ({
|
|
|
35
37
|
}
|
|
36
38
|
}}
|
|
37
39
|
onOutsideClick={onOutsideClick}
|
|
40
|
+
initialPosition={initialPosition}
|
|
38
41
|
>
|
|
39
42
|
{children}
|
|
40
43
|
</DraggableContainer>
|
|
@@ -19,6 +19,7 @@ export interface IDraggableContainerProps {
|
|
|
19
19
|
cancelDrag?: string;
|
|
20
20
|
onPositionChange?: (position: IPosition) => void;
|
|
21
21
|
onOutsideClick?: () => void;
|
|
22
|
+
initialPosition?: IPosition;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export const DraggableContainer: React.FC<IDraggableContainerProps> = ({
|
|
@@ -34,6 +35,7 @@ export const DraggableContainer: React.FC<IDraggableContainerProps> = ({
|
|
|
34
35
|
cancelDrag,
|
|
35
36
|
onPositionChange,
|
|
36
37
|
onOutsideClick,
|
|
38
|
+
initialPosition = { x: 0, y: 0 },
|
|
37
39
|
}) => {
|
|
38
40
|
const draggableRef = useRef(null);
|
|
39
41
|
|
|
@@ -66,6 +68,7 @@ export const DraggableContainer: React.FC<IDraggableContainerProps> = ({
|
|
|
66
68
|
});
|
|
67
69
|
}
|
|
68
70
|
}}
|
|
71
|
+
defaultPosition={initialPosition}
|
|
69
72
|
>
|
|
70
73
|
<Container
|
|
71
74
|
ref={draggableRef}
|
|
@@ -20,6 +20,18 @@ export interface IEquipmentSetProps {
|
|
|
20
20
|
item: IItem,
|
|
21
21
|
itemContainerType: ItemContainerType | null
|
|
22
22
|
) => void;
|
|
23
|
+
onItemDragStart?: (
|
|
24
|
+
item: IItem,
|
|
25
|
+
slotIndex: number,
|
|
26
|
+
itemContainerType: ItemContainerType | null
|
|
27
|
+
) => void;
|
|
28
|
+
onItemDragEnd?: (quantity?: number) => void;
|
|
29
|
+
onItemPlaceDrop?: (
|
|
30
|
+
item: IItem | null,
|
|
31
|
+
slotIndex: number,
|
|
32
|
+
itemContainerType: ItemContainerType | null
|
|
33
|
+
) => void;
|
|
34
|
+
checkIfItemCanBeMoved: () => boolean;
|
|
23
35
|
onMouseOver?: (e: any, slotIndex: number, item: IItem | null) => void;
|
|
24
36
|
onSelected?: (optionId: string) => void;
|
|
25
37
|
initialPosition?: { x: number; y: number };
|
|
@@ -36,6 +48,10 @@ export const EquipmentSet: React.FC<IEquipmentSetProps> = ({
|
|
|
36
48
|
onItemClick,
|
|
37
49
|
atlasIMG,
|
|
38
50
|
atlasJSON,
|
|
51
|
+
onItemDragEnd,
|
|
52
|
+
onItemDragStart,
|
|
53
|
+
onItemPlaceDrop,
|
|
54
|
+
checkIfItemCanBeMoved,
|
|
39
55
|
}) => {
|
|
40
56
|
const {
|
|
41
57
|
neck,
|
|
@@ -102,6 +118,18 @@ export const EquipmentSet: React.FC<IEquipmentSetProps> = ({
|
|
|
102
118
|
onSelected={(optionId: string) => {
|
|
103
119
|
if (onSelected) onSelected(optionId);
|
|
104
120
|
}}
|
|
121
|
+
onDragStart={(item, slotIndex, itemContainerType) => {
|
|
122
|
+
if (onItemDragStart)
|
|
123
|
+
onItemDragStart(item, slotIndex, itemContainerType);
|
|
124
|
+
}}
|
|
125
|
+
onDragEnd={quantity => {
|
|
126
|
+
if (onItemDragEnd) onItemDragEnd(quantity);
|
|
127
|
+
}}
|
|
128
|
+
checkIfItemCanBeMoved={checkIfItemCanBeMoved}
|
|
129
|
+
onPlaceDrop={(item, slotIndex, itemContainerType) => {
|
|
130
|
+
if (onItemPlaceDrop)
|
|
131
|
+
onItemPlaceDrop(item, slotIndex, itemContainerType);
|
|
132
|
+
}}
|
|
105
133
|
atlasIMG={atlasIMG}
|
|
106
134
|
atlasJSON={atlasJSON}
|
|
107
135
|
/>
|
|
@@ -134,6 +162,7 @@ const EquipmentSetContainer = styled.div`
|
|
|
134
162
|
justify-content: center;
|
|
135
163
|
flex-wrap: wrap;
|
|
136
164
|
flex-direction: row;
|
|
165
|
+
touch-action: none;
|
|
137
166
|
`;
|
|
138
167
|
|
|
139
168
|
const EquipmentColumn = styled.div`
|
|
@@ -141,4 +170,5 @@ const EquipmentColumn = styled.div`
|
|
|
141
170
|
justify-content: center;
|
|
142
171
|
flex-wrap: wrap;
|
|
143
172
|
flex-direction: column;
|
|
173
|
+
touch-action: none;
|
|
144
174
|
`;
|
package/src/components/Input.tsx
CHANGED
|
@@ -4,8 +4,12 @@ export interface IInputProps
|
|
|
4
4
|
extends React.DetailedHTMLProps<
|
|
5
5
|
React.InputHTMLAttributes<HTMLInputElement>,
|
|
6
6
|
HTMLInputElement
|
|
7
|
-
> {
|
|
7
|
+
> {
|
|
8
|
+
innerRef?: React.Ref<HTMLInputElement>;
|
|
9
|
+
}
|
|
8
10
|
|
|
9
11
|
export const Input: React.FC<IInputProps> = ({ ...props }) => {
|
|
10
|
-
|
|
12
|
+
const { innerRef, ...rest } = props;
|
|
13
|
+
|
|
14
|
+
return <input {...rest} ref={props.innerRef} />;
|
|
11
15
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { IItem, IItemContainer, ItemContainerType } from '@rpg-engine/shared';
|
|
2
|
-
import React from 'react';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
4
|
import { SlotsContainer } from '../../Abstractions/SlotsContainer';
|
|
5
|
+
import { ItemQuantitySelector } from './ItemQuantitySelector';
|
|
5
6
|
|
|
6
7
|
import { ItemSlot } from './ItemSlot';
|
|
7
8
|
|
|
@@ -13,12 +14,25 @@ export interface IItemContainerProps {
|
|
|
13
14
|
ItemType: IItem['type'],
|
|
14
15
|
itemContainerType: ItemContainerType | null
|
|
15
16
|
) => void;
|
|
17
|
+
onItemDragStart?: (
|
|
18
|
+
item: IItem,
|
|
19
|
+
slotIndex: number,
|
|
20
|
+
itemContainerType: ItemContainerType | null
|
|
21
|
+
) => void;
|
|
22
|
+
onItemDragEnd?: (quantity?: number) => void;
|
|
23
|
+
onItemPlaceDrop?: (
|
|
24
|
+
item: IItem | null,
|
|
25
|
+
slotIndex: number,
|
|
26
|
+
itemContainerType: ItemContainerType | null
|
|
27
|
+
) => void;
|
|
28
|
+
checkIfItemCanBeMoved: () => boolean;
|
|
16
29
|
onMouseOver?: (e: any, slotIndex: number, item: IItem | null) => void;
|
|
17
30
|
onSelected?: (optionId: string, item: IItem) => void;
|
|
18
31
|
type: ItemContainerType;
|
|
19
32
|
atlasJSON: any;
|
|
20
33
|
atlasIMG: any;
|
|
21
34
|
disableContextMenu?: boolean;
|
|
35
|
+
initialPosition?: { x: number; y: number };
|
|
22
36
|
}
|
|
23
37
|
|
|
24
38
|
export const ItemContainer: React.FC<IItemContainerProps> = ({
|
|
@@ -31,7 +45,18 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
|
|
|
31
45
|
atlasJSON,
|
|
32
46
|
atlasIMG,
|
|
33
47
|
disableContextMenu = false,
|
|
48
|
+
onItemDragEnd,
|
|
49
|
+
onItemDragStart,
|
|
50
|
+
onItemPlaceDrop,
|
|
51
|
+
checkIfItemCanBeMoved,
|
|
52
|
+
initialPosition,
|
|
34
53
|
}) => {
|
|
54
|
+
const [quantitySelect, setQuantitySelect] = useState({
|
|
55
|
+
isOpen: false,
|
|
56
|
+
maxQuantity: 1,
|
|
57
|
+
callback: (_quantity: number) => {},
|
|
58
|
+
});
|
|
59
|
+
|
|
35
60
|
const onRenderSlots = () => {
|
|
36
61
|
const slots = [];
|
|
37
62
|
|
|
@@ -52,6 +77,25 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
|
|
|
52
77
|
onSelected={(optionId: string, item: IItem) => {
|
|
53
78
|
if (onSelected) onSelected(optionId, item);
|
|
54
79
|
}}
|
|
80
|
+
onDragStart={(item, slotIndex, itemContainerType) => {
|
|
81
|
+
if (onItemDragStart)
|
|
82
|
+
onItemDragStart(item, slotIndex, itemContainerType);
|
|
83
|
+
}}
|
|
84
|
+
onDragEnd={quantity => {
|
|
85
|
+
if (onItemDragEnd) onItemDragEnd(quantity);
|
|
86
|
+
}}
|
|
87
|
+
checkIfItemCanBeMoved={checkIfItemCanBeMoved}
|
|
88
|
+
openQuantitySelector={(maxQuantity, callback) => {
|
|
89
|
+
setQuantitySelect({
|
|
90
|
+
isOpen: true,
|
|
91
|
+
maxQuantity,
|
|
92
|
+
callback,
|
|
93
|
+
});
|
|
94
|
+
}}
|
|
95
|
+
onPlaceDrop={(item, slotIndex, itemContainerType) => {
|
|
96
|
+
if (onItemPlaceDrop)
|
|
97
|
+
onItemPlaceDrop(item, slotIndex, itemContainerType);
|
|
98
|
+
}}
|
|
55
99
|
atlasIMG={atlasIMG}
|
|
56
100
|
atlasJSON={atlasJSON}
|
|
57
101
|
/>
|
|
@@ -61,11 +105,40 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
|
|
|
61
105
|
};
|
|
62
106
|
|
|
63
107
|
return (
|
|
64
|
-
|
|
65
|
-
<
|
|
66
|
-
{
|
|
67
|
-
|
|
68
|
-
|
|
108
|
+
<>
|
|
109
|
+
<SlotsContainer
|
|
110
|
+
title={itemContainer.name || 'Container'}
|
|
111
|
+
onClose={onClose}
|
|
112
|
+
initialPosition={initialPosition}
|
|
113
|
+
>
|
|
114
|
+
<ItemsContainer className="item-container-body">
|
|
115
|
+
{onRenderSlots()}
|
|
116
|
+
</ItemsContainer>
|
|
117
|
+
</SlotsContainer>
|
|
118
|
+
{quantitySelect.isOpen && (
|
|
119
|
+
<QuantitySelectorContainer>
|
|
120
|
+
<ItemQuantitySelector
|
|
121
|
+
quantity={quantitySelect.maxQuantity}
|
|
122
|
+
onConfirm={quantity => {
|
|
123
|
+
quantitySelect.callback(quantity);
|
|
124
|
+
setQuantitySelect({
|
|
125
|
+
isOpen: false,
|
|
126
|
+
maxQuantity: 1,
|
|
127
|
+
callback: () => {},
|
|
128
|
+
});
|
|
129
|
+
}}
|
|
130
|
+
onClose={() => {
|
|
131
|
+
quantitySelect.callback(-1);
|
|
132
|
+
setQuantitySelect({
|
|
133
|
+
isOpen: false,
|
|
134
|
+
maxQuantity: 1,
|
|
135
|
+
callback: () => {},
|
|
136
|
+
});
|
|
137
|
+
}}
|
|
138
|
+
/>
|
|
139
|
+
</QuantitySelectorContainer>
|
|
140
|
+
)}
|
|
141
|
+
</>
|
|
69
142
|
);
|
|
70
143
|
};
|
|
71
144
|
|
|
@@ -75,3 +148,16 @@ const ItemsContainer = styled.div`
|
|
|
75
148
|
justify-content: center;
|
|
76
149
|
flex-wrap: wrap;
|
|
77
150
|
`;
|
|
151
|
+
|
|
152
|
+
const QuantitySelectorContainer = styled.div`
|
|
153
|
+
position: absolute;
|
|
154
|
+
top: 0;
|
|
155
|
+
left: 0;
|
|
156
|
+
width: 100vw;
|
|
157
|
+
height: 100vh;
|
|
158
|
+
z-index: 100;
|
|
159
|
+
display: flex;
|
|
160
|
+
justify-content: center;
|
|
161
|
+
align-items: center;
|
|
162
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
163
|
+
`;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { Button, ButtonTypes } from '../../Button';
|
|
4
|
+
import { Input } from '../../Input';
|
|
5
|
+
import { RangeSlider, RangeSliderType } from '../../RangeSlider';
|
|
6
|
+
import { RPGUIContainer, RPGUIContainerTypes } from '../../RPGUIContainer';
|
|
7
|
+
|
|
8
|
+
export interface IItemQuantitySelectorProps {
|
|
9
|
+
quantity: number;
|
|
10
|
+
onConfirm: (quantity: number) => void;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ItemQuantitySelector: React.FC<IItemQuantitySelectorProps> = ({
|
|
15
|
+
quantity,
|
|
16
|
+
onConfirm,
|
|
17
|
+
onClose,
|
|
18
|
+
}) => {
|
|
19
|
+
const [value, setValue] = useState(quantity);
|
|
20
|
+
|
|
21
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (inputRef.current) {
|
|
25
|
+
inputRef.current.focus();
|
|
26
|
+
inputRef.current.select();
|
|
27
|
+
|
|
28
|
+
const closeSelector = (e: KeyboardEvent) => {
|
|
29
|
+
if (e.key === 'Escape') {
|
|
30
|
+
onClose();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
document.addEventListener('keydown', closeSelector);
|
|
35
|
+
|
|
36
|
+
return () => {
|
|
37
|
+
document.removeEventListener('keydown', closeSelector);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return () => {};
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<StyledContainer type={RPGUIContainerTypes.Framed} width="25rem">
|
|
46
|
+
<CloseButton
|
|
47
|
+
className="container-close"
|
|
48
|
+
onClick={onClose}
|
|
49
|
+
onTouchStart={onClose}
|
|
50
|
+
>
|
|
51
|
+
X
|
|
52
|
+
</CloseButton>
|
|
53
|
+
<h2>Select quantity to move</h2>
|
|
54
|
+
<StyledForm
|
|
55
|
+
style={{ width: '100%' }}
|
|
56
|
+
onSubmit={e => {
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
|
|
59
|
+
const numberValue = Number(value);
|
|
60
|
+
|
|
61
|
+
if (Number.isNaN(numberValue)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
onConfirm(Math.max(1, Math.min(quantity, numberValue)));
|
|
66
|
+
}}
|
|
67
|
+
noValidate
|
|
68
|
+
>
|
|
69
|
+
<StyledInput
|
|
70
|
+
innerRef={inputRef}
|
|
71
|
+
placeholder="Enter quantity"
|
|
72
|
+
type="number"
|
|
73
|
+
min={1}
|
|
74
|
+
max={quantity}
|
|
75
|
+
value={value}
|
|
76
|
+
onChange={e => {
|
|
77
|
+
setValue((e.target.value as unknown) as number);
|
|
78
|
+
}}
|
|
79
|
+
onBlur={e => {
|
|
80
|
+
const newValue = Math.max(
|
|
81
|
+
1,
|
|
82
|
+
Math.min(quantity, Number(e.target.value))
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
setValue(newValue);
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
<RangeSlider
|
|
89
|
+
type={RangeSliderType.Slider}
|
|
90
|
+
valueMin={1}
|
|
91
|
+
valueMax={quantity}
|
|
92
|
+
width="100%"
|
|
93
|
+
onChange={setValue}
|
|
94
|
+
value={value}
|
|
95
|
+
/>
|
|
96
|
+
<Button buttonType={ButtonTypes.RPGUIButton} type="submit">
|
|
97
|
+
Confirm
|
|
98
|
+
</Button>
|
|
99
|
+
</StyledForm>
|
|
100
|
+
</StyledContainer>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const StyledContainer = styled(RPGUIContainer)`
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-direction: column;
|
|
107
|
+
align-items: center;
|
|
108
|
+
`;
|
|
109
|
+
|
|
110
|
+
const StyledForm = styled.form`
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-direction: column;
|
|
113
|
+
align-items: center;
|
|
114
|
+
width: 100%;
|
|
115
|
+
`;
|
|
116
|
+
const StyledInput = styled(Input)`
|
|
117
|
+
text-align: center;
|
|
118
|
+
|
|
119
|
+
&::-webkit-outer-spin-button,
|
|
120
|
+
&::-webkit-inner-spin-button {
|
|
121
|
+
-webkit-appearance: none;
|
|
122
|
+
margin: 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
&[type='number'] {
|
|
126
|
+
-moz-appearance: textfield;
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
const CloseButton = styled.div`
|
|
131
|
+
position: absolute;
|
|
132
|
+
top: 3px;
|
|
133
|
+
right: 0px;
|
|
134
|
+
color: white;
|
|
135
|
+
z-index: 22;
|
|
136
|
+
font-size: 0.8rem;
|
|
137
|
+
`;
|
|
@@ -4,11 +4,12 @@ import {
|
|
|
4
4
|
IItemContainer,
|
|
5
5
|
ItemContainerType,
|
|
6
6
|
ItemSlotType,
|
|
7
|
-
ItemType
|
|
7
|
+
ItemType,
|
|
8
8
|
} from '@rpg-engine/shared';
|
|
9
9
|
|
|
10
10
|
import { observer } from 'mobx-react-lite';
|
|
11
|
-
import React, { useEffect, useState } from 'react';
|
|
11
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
12
|
+
import Draggable from 'react-draggable';
|
|
12
13
|
import styled from 'styled-components';
|
|
13
14
|
import { v4 as uuidv4 } from 'uuid';
|
|
14
15
|
import { uiFonts } from '../../../constants/uiFonts';
|
|
@@ -52,6 +53,19 @@ interface IProps {
|
|
|
52
53
|
itemContainerType: ItemContainerType | null,
|
|
53
54
|
item: IItem
|
|
54
55
|
) => void;
|
|
56
|
+
onDragStart: (
|
|
57
|
+
item: IItem,
|
|
58
|
+
slotIndex: number,
|
|
59
|
+
itemContainerType: ItemContainerType | null
|
|
60
|
+
) => void;
|
|
61
|
+
onDragEnd: (quantity?: number) => void;
|
|
62
|
+
checkIfItemCanBeMoved: () => boolean;
|
|
63
|
+
openQuantitySelector?: (maxQuantity: number, callback: () => void) => void;
|
|
64
|
+
onPlaceDrop: (
|
|
65
|
+
item: IItem | null,
|
|
66
|
+
slotIndex: number,
|
|
67
|
+
itemContainerType: ItemContainerType | null
|
|
68
|
+
) => void;
|
|
55
69
|
atlasJSON: any;
|
|
56
70
|
atlasIMG: any;
|
|
57
71
|
isContextMenuDisabled?: boolean;
|
|
@@ -70,16 +84,28 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
70
84
|
atlasJSON,
|
|
71
85
|
atlasIMG,
|
|
72
86
|
isContextMenuDisabled = false,
|
|
87
|
+
onDragEnd,
|
|
88
|
+
onDragStart,
|
|
89
|
+
onPlaceDrop,
|
|
90
|
+
checkIfItemCanBeMoved,
|
|
91
|
+
openQuantitySelector,
|
|
73
92
|
}) => {
|
|
74
93
|
const [isTooltipVisible, setTooltipVisible] = useState(false);
|
|
75
94
|
|
|
76
95
|
const [isContextMenuVisible, setIsContextMenuVisible] = useState(false);
|
|
77
96
|
|
|
97
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
98
|
+
const [wasDragged, setWasDragged] = useState(false);
|
|
99
|
+
const [dragPosition, setDragPosition] = useState({ x: 0, y: 0 });
|
|
100
|
+
const dragContainer = useRef<HTMLDivElement>(null);
|
|
101
|
+
|
|
78
102
|
const [contextActions, setContextActions] = useState<IContextMenuItem[]>(
|
|
79
103
|
[]
|
|
80
104
|
);
|
|
81
105
|
|
|
82
106
|
useEffect(() => {
|
|
107
|
+
setDragPosition({ x: 0, y: 0 });
|
|
108
|
+
|
|
83
109
|
if (item) {
|
|
84
110
|
setContextActions(generateContextMenu(item, containerType));
|
|
85
111
|
}
|
|
@@ -112,7 +138,7 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
112
138
|
|
|
113
139
|
const renderItem = (itemToRender: IItem | null) => {
|
|
114
140
|
const element = [];
|
|
115
|
-
|
|
141
|
+
|
|
116
142
|
if (itemToRender?.texturePath) {
|
|
117
143
|
element.push(
|
|
118
144
|
<ErrorBoundary key={itemToRender._id}>
|
|
@@ -150,7 +176,7 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
150
176
|
itemToRender.allowedEquipSlotType?.includes(slotSpriteMask!)
|
|
151
177
|
) {
|
|
152
178
|
const element = [];
|
|
153
|
-
|
|
179
|
+
|
|
154
180
|
element.push(
|
|
155
181
|
<ErrorBoundary key={itemToRender._id}>
|
|
156
182
|
<SpriteFromAtlas
|
|
@@ -205,29 +231,117 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
205
231
|
}
|
|
206
232
|
};
|
|
207
233
|
|
|
234
|
+
const resetItem = () => {
|
|
235
|
+
setTooltipVisible(false);
|
|
236
|
+
setIsFocused(false);
|
|
237
|
+
setWasDragged(false);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const onSuccesfulDrag = (quantity?: number) => {
|
|
241
|
+
resetItem();
|
|
242
|
+
|
|
243
|
+
if (quantity === -1) setDragPosition({ x: 0, y: 0 });
|
|
244
|
+
else if (item) {
|
|
245
|
+
onDragEnd(quantity);
|
|
246
|
+
resetItem();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
208
250
|
return (
|
|
209
251
|
<Container
|
|
210
252
|
className="rpgui-icon empty-slot"
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
onMouseOut={() => {
|
|
215
|
-
if (onMouseOut) onMouseOut();
|
|
253
|
+
onMouseUp={() => {
|
|
254
|
+
const data = item ? item : null;
|
|
255
|
+
if (onPlaceDrop) onPlaceDrop(data, slotIndex, containerType);
|
|
216
256
|
}}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
257
|
+
onTouchEnd={e => {
|
|
258
|
+
const { clientX, clientY } = e.changedTouches[0];
|
|
259
|
+
const simulatedEvent = new MouseEvent('mouseup', {
|
|
260
|
+
clientX,
|
|
261
|
+
clientY,
|
|
262
|
+
bubbles: true,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
document
|
|
266
|
+
.elementFromPoint(clientX, clientY)
|
|
267
|
+
?.dispatchEvent(simulatedEvent);
|
|
229
268
|
}}
|
|
230
269
|
>
|
|
270
|
+
{item && (
|
|
271
|
+
<Draggable
|
|
272
|
+
onStop={() => {
|
|
273
|
+
if (wasDragged) {
|
|
274
|
+
setWasDragged(false);
|
|
275
|
+
|
|
276
|
+
const target = dragContainer.current;
|
|
277
|
+
if (!target || !wasDragged) return;
|
|
278
|
+
|
|
279
|
+
const style = window.getComputedStyle(target);
|
|
280
|
+
const matrix = new DOMMatrixReadOnly(style.transform);
|
|
281
|
+
const x = matrix.m41;
|
|
282
|
+
const y = matrix.m42;
|
|
283
|
+
|
|
284
|
+
setDragPosition({ x, y });
|
|
285
|
+
|
|
286
|
+
setTimeout(() => {
|
|
287
|
+
if (checkIfItemCanBeMoved()) {
|
|
288
|
+
if (
|
|
289
|
+
item.stackQty &&
|
|
290
|
+
item.stackQty !== 1 &&
|
|
291
|
+
openQuantitySelector
|
|
292
|
+
)
|
|
293
|
+
openQuantitySelector(item.stackQty, onSuccesfulDrag);
|
|
294
|
+
else onSuccesfulDrag(item.stackQty);
|
|
295
|
+
} else {
|
|
296
|
+
resetItem();
|
|
297
|
+
setDragPosition({ x: 0, y: 0 });
|
|
298
|
+
}
|
|
299
|
+
}, 100);
|
|
300
|
+
} else if (item) {
|
|
301
|
+
if (!isContextMenuDisabled)
|
|
302
|
+
setIsContextMenuVisible(!isContextMenuVisible);
|
|
303
|
+
|
|
304
|
+
onClick(item.type, containerType, item);
|
|
305
|
+
}
|
|
306
|
+
}}
|
|
307
|
+
onStart={() => {
|
|
308
|
+
if (onDragStart) onDragStart(item, slotIndex, containerType);
|
|
309
|
+
}}
|
|
310
|
+
onDrag={() => {
|
|
311
|
+
setWasDragged(true);
|
|
312
|
+
setIsFocused(true);
|
|
313
|
+
}}
|
|
314
|
+
position={dragPosition}
|
|
315
|
+
>
|
|
316
|
+
<ItemContainer
|
|
317
|
+
ref={dragContainer}
|
|
318
|
+
isFocused={isFocused}
|
|
319
|
+
onMouseOver={event => {
|
|
320
|
+
onMouseOver(
|
|
321
|
+
event,
|
|
322
|
+
slotIndex,
|
|
323
|
+
item,
|
|
324
|
+
event.clientX,
|
|
325
|
+
event.clientY
|
|
326
|
+
);
|
|
327
|
+
}}
|
|
328
|
+
onMouseOut={() => {
|
|
329
|
+
if (onMouseOut) onMouseOut();
|
|
330
|
+
}}
|
|
331
|
+
onMouseEnter={() => {
|
|
332
|
+
setTooltipVisible(true);
|
|
333
|
+
}}
|
|
334
|
+
onMouseLeave={() => {
|
|
335
|
+
setTooltipVisible(false);
|
|
336
|
+
}}
|
|
337
|
+
>
|
|
338
|
+
{onRenderSlot(item)}
|
|
339
|
+
</ItemContainer>
|
|
340
|
+
</Draggable>
|
|
341
|
+
)}
|
|
342
|
+
|
|
343
|
+
{isTooltipVisible && item && <ItemTooltip label={item.name} />}
|
|
344
|
+
|
|
231
345
|
{!isContextMenuDisabled && isContextMenuVisible && contextActions && (
|
|
232
346
|
<RelativeListMenu
|
|
233
347
|
options={contextActions}
|
|
@@ -242,10 +356,6 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
242
356
|
}}
|
|
243
357
|
/>
|
|
244
358
|
)}
|
|
245
|
-
|
|
246
|
-
{isTooltipVisible && item && <ItemTooltip label={item.name} />}
|
|
247
|
-
|
|
248
|
-
{onRenderSlot(item)}
|
|
249
359
|
</Container>
|
|
250
360
|
);
|
|
251
361
|
}
|
|
@@ -261,12 +371,21 @@ const Container = styled.div`
|
|
|
261
371
|
position: relative;
|
|
262
372
|
`;
|
|
263
373
|
|
|
374
|
+
const ItemContainer = styled.div<{ isFocused?: boolean }>`
|
|
375
|
+
width: 100%;
|
|
376
|
+
height: 100%;
|
|
377
|
+
position: relative;
|
|
378
|
+
|
|
379
|
+
${props => props.isFocused && 'z-index: 100; pointer-events: none;'}
|
|
380
|
+
`;
|
|
381
|
+
|
|
264
382
|
const ItemQtyContainer = styled.div`
|
|
265
383
|
position: relative;
|
|
266
384
|
width: 85%;
|
|
267
385
|
height: 16px;
|
|
268
386
|
top: 25px;
|
|
269
387
|
left: 2px;
|
|
388
|
+
pointer-events: none;
|
|
270
389
|
|
|
271
390
|
display: flex;
|
|
272
391
|
justify-content: flex-end;
|