@rpg-engine/long-bow 0.1.63 → 0.1.67
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/DraggableContainer.d.ts +3 -0
- package/dist/components/Item/Cards/ItemCard.d.ts +9 -0
- package/dist/components/Item/Inventory/ItemContainer.d.ts +13 -0
- package/dist/components/Item/Inventory/ItemSlot.d.ts +11 -0
- package/dist/components/Item/Inventory/itemContainerHelper.d.ts +7 -0
- package/dist/components/Item/SpriteFromAtlas.d.ts +11 -0
- package/dist/hooks/useOutsideAlerter.d.ts +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/long-bow.cjs.development.js +2092 -28
- 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 +2091 -30
- package/dist/long-bow.esm.js.map +1 -1
- package/dist/mocks/itemContainer.mocks.d.ts +3 -0
- package/dist/types/eventTypes.d.ts +4 -0
- package/package.json +8 -7
- package/src/components/Chat/Chat.tsx +2 -2
- package/src/components/DraggableContainer.tsx +31 -8
- package/src/components/Item/Cards/ItemCard.tsx +36 -0
- package/src/components/Item/Inventory/ItemContainer.tsx +213 -0
- package/src/components/Item/Inventory/ItemSlot.tsx +90 -0
- package/src/components/Item/Inventory/itemContainerHelper.ts +44 -0
- package/src/components/Item/SpriteFromAtlas.tsx +69 -0
- package/src/hooks/useOutsideAlerter.ts +25 -0
- package/src/index.tsx +3 -0
- package/src/mocks/atlas/items/items.json +1695 -0
- package/src/mocks/atlas/items/items.png +0 -0
- package/src/mocks/itemContainer.mocks.ts +249 -0
- package/src/types/eventTypes.ts +4 -0
- package/dist/alice.png +0 -0
- package/dist/imgExp.png +0 -0
- package/dist/space.gif +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IItem,
|
|
3
|
+
IItemContainer,
|
|
4
|
+
IPayloadProps,
|
|
5
|
+
ItemSocketEvents,
|
|
6
|
+
} from '@rpg-engine/shared';
|
|
7
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
8
|
+
import styled from 'styled-components';
|
|
9
|
+
import { useOutsideClick } from '../../../hooks/useOutsideAlerter';
|
|
10
|
+
import { IPosition } from '../../../types/eventTypes';
|
|
11
|
+
import { DraggableContainer } from '../../DraggableContainer';
|
|
12
|
+
import { ListMenu } from '../../ListMenu';
|
|
13
|
+
import { RPGUIContainerTypes } from '../../RPGUIContainer';
|
|
14
|
+
import { ItemCard } from '../Cards/ItemCard';
|
|
15
|
+
import { handleContextMenuList } from './itemContainerHelper';
|
|
16
|
+
import { ItemSlot } from './ItemSlot';
|
|
17
|
+
|
|
18
|
+
export interface IItemContainerProps {
|
|
19
|
+
itemContainer: IItemContainer;
|
|
20
|
+
onClose: () => void;
|
|
21
|
+
onMouseOver: (e: any, slotIndex: number, item: IItem | null) => void;
|
|
22
|
+
onActionSelected: (payload: any) => void;
|
|
23
|
+
initialPosition?: { x: number; y: number };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface IContextItemProps {
|
|
27
|
+
visible: boolean;
|
|
28
|
+
posX: number;
|
|
29
|
+
posY: number;
|
|
30
|
+
slotItem: IItem | null;
|
|
31
|
+
slotIndex?: number | null;
|
|
32
|
+
contextActions: any;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface IHoverDetailProps {
|
|
36
|
+
visible: boolean;
|
|
37
|
+
posX: number;
|
|
38
|
+
posY: number;
|
|
39
|
+
item: IItem | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const ItemContainer: React.FC<IItemContainerProps> = ({
|
|
43
|
+
itemContainer,
|
|
44
|
+
onClose,
|
|
45
|
+
onMouseOver,
|
|
46
|
+
onActionSelected,
|
|
47
|
+
initialPosition = { x: 0, y: 0 },
|
|
48
|
+
}) => {
|
|
49
|
+
// we use this draggable position to offset the menu position, after the container is dragged (otherwise, it bugs!)
|
|
50
|
+
const [draggablePosition, setDraggablePosition] = useState<IPosition>(
|
|
51
|
+
initialPosition
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const draggableRef = useRef(null);
|
|
55
|
+
|
|
56
|
+
useOutsideClick(draggableRef, 'item-container');
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
document.addEventListener('clickOutside', event => {
|
|
60
|
+
const e = event as CustomEvent;
|
|
61
|
+
|
|
62
|
+
if (e.detail.id === 'item-container') {
|
|
63
|
+
clearContextMenu();
|
|
64
|
+
clearItemHoverDetail();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
document.removeEventListener('clickOutside', _e => {});
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
const { x: initX, y: initY } = initialPosition;
|
|
74
|
+
|
|
75
|
+
const [contextData, setContextData] = useState<IContextItemProps>({
|
|
76
|
+
visible: false,
|
|
77
|
+
posX: initX,
|
|
78
|
+
posY: initY,
|
|
79
|
+
contextActions: [],
|
|
80
|
+
slotItem: null,
|
|
81
|
+
});
|
|
82
|
+
const [itemHoverDetail, setItemHoverDetail] = useState<IHoverDetailProps>({
|
|
83
|
+
visible: false,
|
|
84
|
+
posX: initX,
|
|
85
|
+
posY: initY,
|
|
86
|
+
item: null,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const clearContextMenu = () => {
|
|
90
|
+
setContextData({
|
|
91
|
+
visible: false,
|
|
92
|
+
posX: 0,
|
|
93
|
+
posY: 0,
|
|
94
|
+
contextActions: [],
|
|
95
|
+
slotItem: null,
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handleOnMouseHover = (
|
|
100
|
+
event: any,
|
|
101
|
+
slotIndex: number,
|
|
102
|
+
item: IItem | null,
|
|
103
|
+
x: number,
|
|
104
|
+
y: number
|
|
105
|
+
) => {
|
|
106
|
+
if (item) {
|
|
107
|
+
setItemHoverDetail({
|
|
108
|
+
...itemHoverDetail,
|
|
109
|
+
visible: true,
|
|
110
|
+
item: item,
|
|
111
|
+
posX: x - draggablePosition.x,
|
|
112
|
+
posY: y - draggablePosition.y,
|
|
113
|
+
});
|
|
114
|
+
onMouseOver(event, slotIndex, item);
|
|
115
|
+
} else {
|
|
116
|
+
clearItemHoverDetail();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const clearItemHoverDetail = () => {
|
|
121
|
+
setItemHoverDetail({
|
|
122
|
+
...itemHoverDetail,
|
|
123
|
+
visible: false,
|
|
124
|
+
item: null,
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const handleOnItemClick = (item: IItem, posX: number, posY: number) => {
|
|
129
|
+
const contextList = handleContextMenuList(item.type);
|
|
130
|
+
|
|
131
|
+
setContextData({
|
|
132
|
+
...contextData,
|
|
133
|
+
visible: true,
|
|
134
|
+
posX,
|
|
135
|
+
posY,
|
|
136
|
+
slotItem: item,
|
|
137
|
+
contextActions: contextList,
|
|
138
|
+
});
|
|
139
|
+
clearItemHoverDetail();
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const onSelected = (selectedActionId: ItemSocketEvents | string): void => {
|
|
143
|
+
let payloadData: IPayloadProps = {
|
|
144
|
+
actionType: selectedActionId,
|
|
145
|
+
item: contextData.slotItem,
|
|
146
|
+
};
|
|
147
|
+
onActionSelected(payloadData);
|
|
148
|
+
clearContextMenu();
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const onRenderSlots = () => {
|
|
152
|
+
const slots = [];
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < itemContainer.slotQty; i++) {
|
|
155
|
+
slots.push(
|
|
156
|
+
<ItemSlot
|
|
157
|
+
key={i}
|
|
158
|
+
slotIndex={i}
|
|
159
|
+
item={itemContainer.slots?.[i] || null}
|
|
160
|
+
onMouseOver={handleOnMouseHover}
|
|
161
|
+
onClick={handleOnItemClick}
|
|
162
|
+
onCancelContextMenu={() => {
|
|
163
|
+
clearContextMenu();
|
|
164
|
+
}}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return slots;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div ref={draggableRef}>
|
|
173
|
+
<DraggableContainer
|
|
174
|
+
title={itemContainer.name || 'Container'}
|
|
175
|
+
type={RPGUIContainerTypes.Framed}
|
|
176
|
+
onCloseButton={() => onClose()}
|
|
177
|
+
width="330px"
|
|
178
|
+
cancelDrag=".item-container-body"
|
|
179
|
+
onPositionChange={({ x, y }) => {
|
|
180
|
+
setDraggablePosition({ x, y });
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
<ItemsContainer className="item-container-body">
|
|
184
|
+
{onRenderSlots()}
|
|
185
|
+
</ItemsContainer>
|
|
186
|
+
|
|
187
|
+
{contextData.visible ? (
|
|
188
|
+
<ListMenu
|
|
189
|
+
x={contextData.posX - draggablePosition.x}
|
|
190
|
+
y={contextData.posY - draggablePosition.y}
|
|
191
|
+
options={contextData.contextActions}
|
|
192
|
+
onSelected={onSelected}
|
|
193
|
+
/>
|
|
194
|
+
) : null}
|
|
195
|
+
|
|
196
|
+
{itemHoverDetail.visible ? (
|
|
197
|
+
<ItemCard
|
|
198
|
+
item={itemHoverDetail.item}
|
|
199
|
+
x={itemHoverDetail.posX}
|
|
200
|
+
y={itemHoverDetail.posY}
|
|
201
|
+
/>
|
|
202
|
+
) : null}
|
|
203
|
+
</DraggableContainer>
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const ItemsContainer = styled.div`
|
|
209
|
+
max-width: 280px;
|
|
210
|
+
display: flex;
|
|
211
|
+
justify-content: center;
|
|
212
|
+
flex-wrap: wrap;
|
|
213
|
+
`;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { IItem } from '@rpg-engine/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import atlasJSON from '../../../mocks/atlas/items/items.json';
|
|
5
|
+
import atlasIMG from '../../../mocks/atlas/items/items.png';
|
|
6
|
+
import { SpriteFromAtlas } from '../SpriteFromAtlas';
|
|
7
|
+
interface IProps {
|
|
8
|
+
slotIndex: number;
|
|
9
|
+
item: IItem | null;
|
|
10
|
+
onMouseOver: (
|
|
11
|
+
event: any,
|
|
12
|
+
slotIndex: number,
|
|
13
|
+
item: IItem | null,
|
|
14
|
+
x: number,
|
|
15
|
+
y: number
|
|
16
|
+
) => void;
|
|
17
|
+
onClick: (item: IItem, posX: number, posY: number) => void;
|
|
18
|
+
onCancelContextMenu: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const ItemSlot: React.FC<IProps> = ({
|
|
22
|
+
slotIndex,
|
|
23
|
+
item,
|
|
24
|
+
onMouseOver,
|
|
25
|
+
onClick,
|
|
26
|
+
onCancelContextMenu,
|
|
27
|
+
}) => {
|
|
28
|
+
const getLeftPositionValue = (quantity: number) => {
|
|
29
|
+
if (quantity > 0 && quantity < 10) return '2.5rem';
|
|
30
|
+
else if (quantity > 9 && quantity < 100) return '2.0rem';
|
|
31
|
+
else if (quantity > 99) return '1rem';
|
|
32
|
+
return '2.5rem';
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Container
|
|
37
|
+
className="rpgui-icon empty-slot"
|
|
38
|
+
onMouseOver={event =>
|
|
39
|
+
onMouseOver(event, slotIndex, item, event.clientX, event.clientY)
|
|
40
|
+
}
|
|
41
|
+
onClick={e => {
|
|
42
|
+
if (item) {
|
|
43
|
+
console.log(e);
|
|
44
|
+
onClick(item, e.clientX, e.clientY);
|
|
45
|
+
} else {
|
|
46
|
+
onCancelContextMenu();
|
|
47
|
+
}
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{item && item.texturePath ? (
|
|
51
|
+
<SpriteFromAtlas
|
|
52
|
+
atlasIMG={atlasIMG}
|
|
53
|
+
atlasJSON={atlasJSON}
|
|
54
|
+
spriteKey={item.texturePath}
|
|
55
|
+
scale={3}
|
|
56
|
+
/>
|
|
57
|
+
) : null}
|
|
58
|
+
{item && item.isStackable && item?.stackQty ? (
|
|
59
|
+
<ItemQty left={getLeftPositionValue(item.stackQty)}>
|
|
60
|
+
{' '}
|
|
61
|
+
{item.stackQty}{' '}
|
|
62
|
+
</ItemQty>
|
|
63
|
+
) : null}
|
|
64
|
+
</Container>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const Container = styled.div`
|
|
69
|
+
margin: 0.1rem;
|
|
70
|
+
// border: 1px red solid;
|
|
71
|
+
|
|
72
|
+
.sprite-from-atlas-img {
|
|
73
|
+
position: relative;
|
|
74
|
+
top: 1.5rem;
|
|
75
|
+
left: 1.5rem;
|
|
76
|
+
|
|
77
|
+
&:hover {
|
|
78
|
+
filter: sepia(100%) saturate(300%) brightness(70%) hue-rotate(180deg);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
interface IItemQtyProps {
|
|
84
|
+
left: string;
|
|
85
|
+
}
|
|
86
|
+
const ItemQty = styled.span<IItemQtyProps>`
|
|
87
|
+
position: relative;
|
|
88
|
+
top: 1.5rem;
|
|
89
|
+
left: ${props => props.left};
|
|
90
|
+
`;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ActionsByItemType, ItemType } from '@rpg-engine/shared';
|
|
2
|
+
|
|
3
|
+
interface IContextMenuItem {
|
|
4
|
+
id: string;
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const handleContextMenuList = (itemType: ItemType) => {
|
|
9
|
+
const generateContextList = (actionsByTypeList: any) => {
|
|
10
|
+
const contextMenu: IContextMenuItem[] = actionsByTypeList.map(
|
|
11
|
+
(action: string) => {
|
|
12
|
+
return { id: action, text: action };
|
|
13
|
+
}
|
|
14
|
+
);
|
|
15
|
+
return contextMenu;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let contextActionMenu: IContextMenuItem[] = [];
|
|
19
|
+
switch (itemType) {
|
|
20
|
+
case ItemType.Weapon:
|
|
21
|
+
case ItemType.Armor:
|
|
22
|
+
case ItemType.Accessory:
|
|
23
|
+
case ItemType.Jewelry:
|
|
24
|
+
case ItemType.Tool:
|
|
25
|
+
contextActionMenu = generateContextList(ActionsByItemType.Equipment);
|
|
26
|
+
break;
|
|
27
|
+
case ItemType.Consumable:
|
|
28
|
+
contextActionMenu = generateContextList(ActionsByItemType.Consumable);
|
|
29
|
+
break;
|
|
30
|
+
case ItemType.CraftMaterial:
|
|
31
|
+
contextActionMenu = generateContextList(ActionsByItemType.CraftMaterial);
|
|
32
|
+
break;
|
|
33
|
+
case ItemType.Other:
|
|
34
|
+
case ItemType.Information:
|
|
35
|
+
case ItemType.Quest:
|
|
36
|
+
case ItemType.Container:
|
|
37
|
+
contextActionMenu = generateContextList(ActionsByItemType.Other);
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
contextActionMenu = generateContextList(ActionsByItemType.Other);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
return contextActionMenu;
|
|
44
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { GRID_HEIGHT, GRID_WIDTH } from '@rpg-engine/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
|
|
5
|
+
interface IProps {
|
|
6
|
+
atlasJSON: any;
|
|
7
|
+
atlasIMG: any;
|
|
8
|
+
spriteKey: string;
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
scale?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const SpriteFromAtlas: React.FC<IProps> = ({
|
|
15
|
+
atlasJSON,
|
|
16
|
+
atlasIMG,
|
|
17
|
+
spriteKey,
|
|
18
|
+
width = GRID_WIDTH,
|
|
19
|
+
height = GRID_HEIGHT,
|
|
20
|
+
scale = 2,
|
|
21
|
+
}) => {
|
|
22
|
+
//! If an item is not showing, remember that you MUST run yarn atlas:copy everytime you add a new item to the atlas (it will sync our public folder atlas with src/atlas).
|
|
23
|
+
//!Due to React's limitations, we cannot import it from the public folder directly!
|
|
24
|
+
|
|
25
|
+
const spriteData = atlasJSON.frames[spriteKey];
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Container width={width} height={height}>
|
|
29
|
+
<ImgSprite
|
|
30
|
+
className="sprite-from-atlas-img"
|
|
31
|
+
atlasIMG={atlasIMG}
|
|
32
|
+
frame={spriteData.frame}
|
|
33
|
+
scale={scale}
|
|
34
|
+
/>
|
|
35
|
+
</Container>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
interface IImgSpriteProps {
|
|
40
|
+
atlasIMG: any;
|
|
41
|
+
frame: {
|
|
42
|
+
x: number;
|
|
43
|
+
y: number;
|
|
44
|
+
w: number;
|
|
45
|
+
h: number;
|
|
46
|
+
};
|
|
47
|
+
scale: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface IContainerProps {
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const Container = styled.div`
|
|
56
|
+
width: ${(props: IContainerProps) => props.width}px;
|
|
57
|
+
height: ${(props: IContainerProps) => props.height}px;
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const ImgSprite = styled.div<IImgSpriteProps>`
|
|
61
|
+
width: ${props => props.frame.w}px;
|
|
62
|
+
height: ${props => props.frame.h}px;
|
|
63
|
+
background-image: url(${props => props.atlasIMG});
|
|
64
|
+
background-position: -${props => props.frame.x}px -${props => props.frame.y}px;
|
|
65
|
+
transform: scale(${props => props.scale});
|
|
66
|
+
position: relative;
|
|
67
|
+
top: 8px;
|
|
68
|
+
left: 8px;
|
|
69
|
+
`;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useOutsideClick(ref: any, id: string) {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
/**
|
|
6
|
+
* Alert if clicked on outside of element
|
|
7
|
+
*/
|
|
8
|
+
function handleClickOutside(event: any) {
|
|
9
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
10
|
+
const event = new CustomEvent('clickOutside', {
|
|
11
|
+
detail: {
|
|
12
|
+
id,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
document.dispatchEvent(event);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// Bind the event listener
|
|
19
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
20
|
+
return () => {
|
|
21
|
+
// Unbind the event listener on clean up
|
|
22
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
23
|
+
};
|
|
24
|
+
}, [ref]);
|
|
25
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -4,6 +4,9 @@ export * from './components/CheckButton';
|
|
|
4
4
|
export * from './components/DraggableContainer';
|
|
5
5
|
export * from './components/Dropdown';
|
|
6
6
|
export * from './components/Input';
|
|
7
|
+
export * from './components/Item/Inventory/ItemContainer';
|
|
8
|
+
export * from './components/Item/Inventory/ItemSlot';
|
|
9
|
+
export * from './components/Item/SpriteFromAtlas';
|
|
7
10
|
export * from './components/ListMenu';
|
|
8
11
|
export * from './components/NPCDialog/NPCDialog';
|
|
9
12
|
export * from './components/NPCDialog/QuestionDialog/QuestionDialog';
|