@rpg-engine/long-bow 0.1.62 → 0.1.66

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.
@@ -0,0 +1,216 @@
1
+ import {
2
+ ActionsByItemType,
3
+ IItem,
4
+ IItemContainer,
5
+ IPayloadProps,
6
+ ItemSocketEvents,
7
+ ItemType,
8
+ } from '@rpg-engine/shared';
9
+ import React, { useEffect, useState } from 'react';
10
+ import styled from 'styled-components';
11
+ import { DraggableContainer } from '../../DraggableContainer';
12
+ import { ListMenu } from '../../ListMenu';
13
+ import { RPGUIContainerTypes } from '../../RPGUIContainer';
14
+ import { ItemCard } from '../Cards/ItemCard';
15
+ import { ItemSlot } from './ItemSlot';
16
+
17
+ export interface IItemContainerProps {
18
+ itemContainer: IItemContainer;
19
+ onClose: () => void;
20
+ onMouseOver: (e: any, slotIndex: number, item: IItem | null) => void;
21
+ onActionSelected: (payload: any) => void;
22
+ }
23
+
24
+ interface contextItemPropx {
25
+ visible: boolean;
26
+ posX: number;
27
+ posY: number;
28
+ slotItem: IItem | null;
29
+ slotIndex?: number | null;
30
+ contextActions: any;
31
+ }
32
+
33
+ export const ItemContainer: React.FC<IItemContainerProps> = ({
34
+ itemContainer,
35
+ onClose,
36
+ onMouseOver,
37
+ onActionSelected,
38
+ }) => {
39
+ const [contextData, setContextData] = useState<contextItemPropx>({
40
+ visible: false,
41
+ posX: 0,
42
+ posY: 0,
43
+ contextActions: [],
44
+ slotItem: null,
45
+ });
46
+ const [itemHoverDetail] = useState({
47
+ visible: false,
48
+ posX: 0,
49
+ posY: 0,
50
+ item: null,
51
+ });
52
+ let selectedSlotContext: number | null = null;
53
+
54
+ const handleContextMenuList = (itemType: ItemType) => {
55
+ const generateContextList = (actionsByTypeList: any) => {
56
+ let contextMenu = actionsByTypeList.map((action: string) => {
57
+ return { id: action, text: action };
58
+ });
59
+ return contextMenu;
60
+ };
61
+
62
+ let contextActionMenu: any = [];
63
+ switch (itemType) {
64
+ case ItemType.Weapon:
65
+ case ItemType.Armor:
66
+ case ItemType.Accessory:
67
+ case ItemType.Jewelry:
68
+ case ItemType.Tool:
69
+ contextActionMenu = generateContextList(ActionsByItemType.Equipment);
70
+ break;
71
+ case ItemType.Consumable:
72
+ contextActionMenu = generateContextList(ActionsByItemType.Consumable);
73
+ break;
74
+ case ItemType.CraftMaterial:
75
+ contextActionMenu = generateContextList(
76
+ ActionsByItemType.CraftMaterial
77
+ );
78
+ break;
79
+ case ItemType.Other:
80
+ case ItemType.Information:
81
+ case ItemType.Quest:
82
+ case ItemType.Container:
83
+ contextActionMenu = generateContextList(ActionsByItemType.Other);
84
+ break;
85
+ default:
86
+ contextActionMenu = generateContextList(ActionsByItemType.Other);
87
+ break;
88
+ }
89
+ return contextActionMenu;
90
+ };
91
+
92
+ useEffect(() => {
93
+ const contextMenuEventHandler = (event: any) => {
94
+ event.preventDefault();
95
+ if (selectedSlotContext !== null) {
96
+ const itemData = itemContainer.slots?.[selectedSlotContext] ?? null;
97
+ if (itemData) {
98
+ // TODO: generate a menuActionList based on type and subType of the item.
99
+ let contextActionMenu: any = handleContextMenuList(itemData.type);
100
+ setContextData({
101
+ ...contextData,
102
+ visible: true,
103
+ posX: event.clientX,
104
+ posY: event.clientY,
105
+ slotItem: itemData,
106
+ contextActions: contextActionMenu,
107
+ });
108
+ } else {
109
+ // console.log("Empty Slot")
110
+ setContextData({
111
+ ...contextData,
112
+ visible: false,
113
+ posX: 0,
114
+ posY: 0,
115
+ slotIndex: null,
116
+ slotItem: null,
117
+ contextActions: [],
118
+ });
119
+ }
120
+ }
121
+ };
122
+
123
+ const offClickHandler = (event: any) => {
124
+ console.log(event);
125
+ setContextData({
126
+ ...contextData,
127
+ visible: false,
128
+ posX: 0,
129
+ posY: 0,
130
+ slotIndex: null,
131
+ slotItem: null,
132
+ });
133
+ };
134
+
135
+ document.addEventListener('contextmenu', contextMenuEventHandler);
136
+ document.addEventListener('click', offClickHandler);
137
+ return () => {
138
+ document.removeEventListener('contextmenu', contextMenuEventHandler);
139
+ document.removeEventListener('click', offClickHandler);
140
+ };
141
+ }, [contextData, selectedSlotContext]);
142
+
143
+ const onActionContextMenu = (event: any, slotIndex: number) => {
144
+ selectedSlotContext = slotIndex;
145
+ console.log(event);
146
+ // setContextData({...contextData, slotItem: slotIndex})
147
+ };
148
+
149
+ const handleOnMouseHover = (event: any, slotIndex: number, item: any) => {
150
+ // TODO: Show a tip view with basic info about the item.
151
+ // set a minimun hover period before show infos.
152
+ // setItemHoverDetail({...itemHoverDetail, visible: true, item: item, posX: event.clientX, posY: event.clientY})
153
+ onMouseOver(event, slotIndex, item);
154
+ };
155
+
156
+ const onSelected = (selectedActionId: ItemSocketEvents | string): void => {
157
+ let payloadData: IPayloadProps = {
158
+ actionType: selectedActionId,
159
+ item: contextData.slotItem,
160
+ };
161
+ // TODO: create a function to validate the selection set the paylod data.
162
+ onActionSelected(payloadData);
163
+ };
164
+
165
+ const onRenderSlots = () => {
166
+ const slots = [];
167
+
168
+ for (let i = 0; i < itemContainer.slotQty; i++) {
169
+ slots.push(
170
+ <ItemSlot
171
+ key={i}
172
+ slotIndex={i}
173
+ item={itemContainer.slots?.[i] || null}
174
+ onMouseOver={handleOnMouseHover}
175
+ onActionContextMenu={onActionContextMenu}
176
+ />
177
+ );
178
+ }
179
+ return slots;
180
+ };
181
+
182
+ return (
183
+ <DraggableContainer
184
+ title={itemContainer.name || 'Container'}
185
+ type={RPGUIContainerTypes.Framed}
186
+ onCloseButton={() => onClose()}
187
+ width="330px"
188
+ >
189
+ <ItemsContainer>{onRenderSlots()}</ItemsContainer>
190
+
191
+ {contextData.visible ? (
192
+ <ListMenu
193
+ x={contextData.posX}
194
+ y={contextData.posY}
195
+ options={contextData.contextActions}
196
+ onSelected={onSelected}
197
+ />
198
+ ) : null}
199
+
200
+ {itemHoverDetail.visible ? (
201
+ <ItemCard
202
+ item={itemHoverDetail.item}
203
+ x={itemHoverDetail.posX}
204
+ y={itemHoverDetail.posY}
205
+ />
206
+ ) : null}
207
+ </DraggableContainer>
208
+ );
209
+ };
210
+
211
+ const ItemsContainer = styled.div`
212
+ max-width: 280px;
213
+ display: flex;
214
+ justify-content: center;
215
+ flex-wrap: wrap;
216
+ `;
@@ -0,0 +1,75 @@
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: (event: any, slotIndex: number, item: IItem | null) => void;
11
+ onActionContextMenu: (event: any, slotIndex: number) => void;
12
+ }
13
+
14
+ export const ItemSlot: React.FC<IProps> = ({
15
+ slotIndex,
16
+ item,
17
+ onMouseOver,
18
+ onActionContextMenu,
19
+ }) => {
20
+ const getLeftPostionValue = (quantity: number) => {
21
+ if (quantity > 0 && quantity < 10) return '2.5rem';
22
+ else if (quantity > 9 && quantity < 100) return '2.0rem';
23
+ else if (quantity > 99) return '1rem';
24
+ return '2.5rem';
25
+ };
26
+
27
+ return (
28
+ <Container
29
+ onContextMenu={event => {
30
+ onActionContextMenu(event, slotIndex);
31
+ }}
32
+ className="rpgui-icon empty-slot"
33
+ onMouseOver={event => onMouseOver(event, slotIndex, item)}
34
+ >
35
+ {item && item.texturePath ? (
36
+ <SpriteFromAtlas
37
+ atlasIMG={atlasIMG}
38
+ atlasJSON={atlasJSON}
39
+ spriteKey={item.texturePath}
40
+ scale={3}
41
+ />
42
+ ) : null}
43
+ {item && item.isStackable && item?.stackQty ? (
44
+ <ItemQty left={getLeftPostionValue(item.stackQty)}>
45
+ {' '}
46
+ {item.stackQty}{' '}
47
+ </ItemQty>
48
+ ) : null}
49
+ </Container>
50
+ );
51
+ };
52
+
53
+ const Container = styled.div`
54
+ margin: 0.1rem;
55
+ // border: 1px red solid;
56
+
57
+ .sprite-from-atlas-img {
58
+ position: relative;
59
+ top: 1.5rem;
60
+ left: 1.5rem;
61
+
62
+ &:hover {
63
+ filter: sepia(100%) saturate(300%) brightness(70%) hue-rotate(180deg);
64
+ }
65
+ }
66
+ `;
67
+
68
+ interface IItemQtyProps {
69
+ left: string;
70
+ }
71
+ const ItemQty = styled.span<IItemQtyProps>`
72
+ position: relative;
73
+ top: 1.5rem;
74
+ left: ${props => props.left};
75
+ `;
@@ -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
+ `;
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';