@rpg-engine/long-bow 0.2.86 → 0.2.88
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 +382 -77
- 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 +383 -78
- 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 +37 -0
- package/src/components/Input.tsx +6 -2
- package/src/components/Item/Inventory/ItemContainer.tsx +92 -6
- package/src/components/Item/Inventory/ItemQuantitySelector.tsx +142 -0
- package/src/components/Item/Inventory/ItemSlot.tsx +147 -24
- package/src/components/ListMenu.tsx +1 -2
- 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,25 @@ 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 (!item) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (onItemDragStart)
|
|
127
|
+
onItemDragStart(item, slotIndex, itemContainerType);
|
|
128
|
+
}}
|
|
129
|
+
onDragEnd={quantity => {
|
|
130
|
+
if (onItemDragEnd) onItemDragEnd(quantity);
|
|
131
|
+
}}
|
|
132
|
+
checkIfItemCanBeMoved={checkIfItemCanBeMoved}
|
|
133
|
+
onPlaceDrop={(item, slotIndex, itemContainerType) => {
|
|
134
|
+
if (!item) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (onItemPlaceDrop)
|
|
138
|
+
onItemPlaceDrop(item, slotIndex, itemContainerType);
|
|
139
|
+
}}
|
|
105
140
|
atlasIMG={atlasIMG}
|
|
106
141
|
atlasJSON={atlasJSON}
|
|
107
142
|
/>
|
|
@@ -134,6 +169,7 @@ const EquipmentSetContainer = styled.div`
|
|
|
134
169
|
justify-content: center;
|
|
135
170
|
flex-wrap: wrap;
|
|
136
171
|
flex-direction: row;
|
|
172
|
+
touch-action: none;
|
|
137
173
|
`;
|
|
138
174
|
|
|
139
175
|
const EquipmentColumn = styled.div`
|
|
@@ -141,4 +177,5 @@ const EquipmentColumn = styled.div`
|
|
|
141
177
|
justify-content: center;
|
|
142
178
|
flex-wrap: wrap;
|
|
143
179
|
flex-direction: column;
|
|
180
|
+
touch-action: none;
|
|
144
181
|
`;
|
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,142 @@
|
|
|
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 { RPGUIContainer, RPGUIContainerTypes } from '../../RPGUIContainer';
|
|
6
|
+
import { RangeSlider, RangeSliderType } from '../../RangeSlider';
|
|
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
|
+
if (Number(e.target.value) >= quantity) {
|
|
78
|
+
setValue(quantity);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setValue((e.target.value as unknown) as number);
|
|
83
|
+
}}
|
|
84
|
+
onBlur={e => {
|
|
85
|
+
const newValue = Math.max(
|
|
86
|
+
1,
|
|
87
|
+
Math.min(quantity, Number(e.target.value))
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
setValue(newValue);
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
<RangeSlider
|
|
94
|
+
type={RangeSliderType.Slider}
|
|
95
|
+
valueMin={1}
|
|
96
|
+
valueMax={quantity}
|
|
97
|
+
width="100%"
|
|
98
|
+
onChange={setValue}
|
|
99
|
+
value={value}
|
|
100
|
+
/>
|
|
101
|
+
<Button buttonType={ButtonTypes.RPGUIButton} type="submit">
|
|
102
|
+
Confirm
|
|
103
|
+
</Button>
|
|
104
|
+
</StyledForm>
|
|
105
|
+
</StyledContainer>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const StyledContainer = styled(RPGUIContainer)`
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
align-items: center;
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
const StyledForm = styled.form`
|
|
116
|
+
display: flex;
|
|
117
|
+
flex-direction: column;
|
|
118
|
+
align-items: center;
|
|
119
|
+
width: 100%;
|
|
120
|
+
`;
|
|
121
|
+
const StyledInput = styled(Input)`
|
|
122
|
+
text-align: center;
|
|
123
|
+
|
|
124
|
+
&::-webkit-outer-spin-button,
|
|
125
|
+
&::-webkit-inner-spin-button {
|
|
126
|
+
-webkit-appearance: none;
|
|
127
|
+
margin: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
&[type='number'] {
|
|
131
|
+
-moz-appearance: textfield;
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
const CloseButton = styled.div`
|
|
136
|
+
position: absolute;
|
|
137
|
+
top: 3px;
|
|
138
|
+
right: 0px;
|
|
139
|
+
color: white;
|
|
140
|
+
z-index: 22;
|
|
141
|
+
font-size: 0.8rem;
|
|
142
|
+
`;
|
|
@@ -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,121 @@ 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
|
-
setIsContextMenuVisible(!isContextMenuVisible);
|
|
225
|
-
}
|
|
257
|
+
onTouchEnd={e => {
|
|
258
|
+
const { clientX, clientY } = e.changedTouches[0];
|
|
259
|
+
const simulatedEvent = new MouseEvent('mouseup', {
|
|
260
|
+
clientX,
|
|
261
|
+
clientY,
|
|
262
|
+
bubbles: true,
|
|
263
|
+
});
|
|
226
264
|
|
|
227
|
-
|
|
228
|
-
|
|
265
|
+
document
|
|
266
|
+
.elementFromPoint(clientX, clientY)
|
|
267
|
+
?.dispatchEvent(simulatedEvent);
|
|
229
268
|
}}
|
|
230
269
|
>
|
|
270
|
+
<Draggable
|
|
271
|
+
defaultClassName={item ? 'draggable' : 'empty-slot'}
|
|
272
|
+
onStop={() => {
|
|
273
|
+
if (!item) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (wasDragged) {
|
|
278
|
+
setWasDragged(false);
|
|
279
|
+
|
|
280
|
+
const target = dragContainer.current;
|
|
281
|
+
if (!target || !wasDragged) return;
|
|
282
|
+
|
|
283
|
+
const style = window.getComputedStyle(target);
|
|
284
|
+
const matrix = new DOMMatrixReadOnly(style.transform);
|
|
285
|
+
const x = matrix.m41;
|
|
286
|
+
const y = matrix.m42;
|
|
287
|
+
|
|
288
|
+
setDragPosition({ x, y });
|
|
289
|
+
|
|
290
|
+
setTimeout(() => {
|
|
291
|
+
if (checkIfItemCanBeMoved()) {
|
|
292
|
+
if (
|
|
293
|
+
item.stackQty &&
|
|
294
|
+
item.stackQty !== 1 &&
|
|
295
|
+
openQuantitySelector
|
|
296
|
+
)
|
|
297
|
+
openQuantitySelector(item.stackQty, onSuccesfulDrag);
|
|
298
|
+
else onSuccesfulDrag(item.stackQty);
|
|
299
|
+
} else {
|
|
300
|
+
resetItem();
|
|
301
|
+
setDragPosition({ x: 0, y: 0 });
|
|
302
|
+
}
|
|
303
|
+
}, 100);
|
|
304
|
+
} else if (item) {
|
|
305
|
+
if (!isContextMenuDisabled)
|
|
306
|
+
setIsContextMenuVisible(!isContextMenuVisible);
|
|
307
|
+
|
|
308
|
+
onClick(item.type, containerType, item);
|
|
309
|
+
}
|
|
310
|
+
}}
|
|
311
|
+
onStart={() => {
|
|
312
|
+
if (!item) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (onDragStart) {
|
|
317
|
+
onDragStart(item, slotIndex, containerType);
|
|
318
|
+
}
|
|
319
|
+
}}
|
|
320
|
+
onDrag={() => {
|
|
321
|
+
setWasDragged(true);
|
|
322
|
+
setIsFocused(true);
|
|
323
|
+
}}
|
|
324
|
+
position={dragPosition}
|
|
325
|
+
cancel=".empty-slot"
|
|
326
|
+
>
|
|
327
|
+
<ItemContainer
|
|
328
|
+
ref={dragContainer}
|
|
329
|
+
isFocused={isFocused}
|
|
330
|
+
onMouseOver={event => {
|
|
331
|
+
onMouseOver(event, slotIndex, item, event.clientX, event.clientY);
|
|
332
|
+
}}
|
|
333
|
+
onMouseOut={() => {
|
|
334
|
+
if (onMouseOut) onMouseOut();
|
|
335
|
+
}}
|
|
336
|
+
onMouseEnter={() => {
|
|
337
|
+
setTooltipVisible(true);
|
|
338
|
+
}}
|
|
339
|
+
onMouseLeave={() => {
|
|
340
|
+
setTooltipVisible(false);
|
|
341
|
+
}}
|
|
342
|
+
>
|
|
343
|
+
{onRenderSlot(item)}
|
|
344
|
+
</ItemContainer>
|
|
345
|
+
</Draggable>
|
|
346
|
+
|
|
347
|
+
{isTooltipVisible && item && <ItemTooltip label={item.name} />}
|
|
348
|
+
|
|
231
349
|
{!isContextMenuDisabled && isContextMenuVisible && contextActions && (
|
|
232
350
|
<RelativeListMenu
|
|
233
351
|
options={contextActions}
|
|
@@ -242,10 +360,6 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
242
360
|
}}
|
|
243
361
|
/>
|
|
244
362
|
)}
|
|
245
|
-
|
|
246
|
-
{isTooltipVisible && item && <ItemTooltip label={item.name} />}
|
|
247
|
-
|
|
248
|
-
{onRenderSlot(item)}
|
|
249
363
|
</Container>
|
|
250
364
|
);
|
|
251
365
|
}
|
|
@@ -261,12 +375,21 @@ const Container = styled.div`
|
|
|
261
375
|
position: relative;
|
|
262
376
|
`;
|
|
263
377
|
|
|
378
|
+
const ItemContainer = styled.div<{ isFocused?: boolean }>`
|
|
379
|
+
width: 100%;
|
|
380
|
+
height: 100%;
|
|
381
|
+
position: relative;
|
|
382
|
+
|
|
383
|
+
${props => props.isFocused && 'z-index: 100; pointer-events: none;'}
|
|
384
|
+
`;
|
|
385
|
+
|
|
264
386
|
const ItemQtyContainer = styled.div`
|
|
265
387
|
position: relative;
|
|
266
388
|
width: 85%;
|
|
267
389
|
height: 16px;
|
|
268
390
|
top: 25px;
|
|
269
391
|
left: 2px;
|
|
392
|
+
pointer-events: none;
|
|
270
393
|
|
|
271
394
|
display: flex;
|
|
272
395
|
justify-content: flex-end;
|