@rpg-engine/long-bow 0.1.66 → 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.
@@ -1,17 +1,18 @@
1
1
  import {
2
- ActionsByItemType,
3
2
  IItem,
4
3
  IItemContainer,
5
4
  IPayloadProps,
6
5
  ItemSocketEvents,
7
- ItemType,
8
6
  } from '@rpg-engine/shared';
9
- import React, { useEffect, useState } from 'react';
7
+ import React, { useEffect, useRef, useState } from 'react';
10
8
  import styled from 'styled-components';
9
+ import { useOutsideClick } from '../../../hooks/useOutsideAlerter';
10
+ import { IPosition } from '../../../types/eventTypes';
11
11
  import { DraggableContainer } from '../../DraggableContainer';
12
12
  import { ListMenu } from '../../ListMenu';
13
13
  import { RPGUIContainerTypes } from '../../RPGUIContainer';
14
14
  import { ItemCard } from '../Cards/ItemCard';
15
+ import { handleContextMenuList } from './itemContainerHelper';
15
16
  import { ItemSlot } from './ItemSlot';
16
17
 
17
18
  export interface IItemContainerProps {
@@ -19,9 +20,10 @@ export interface IItemContainerProps {
19
20
  onClose: () => void;
20
21
  onMouseOver: (e: any, slotIndex: number, item: IItem | null) => void;
21
22
  onActionSelected: (payload: any) => void;
23
+ initialPosition?: { x: number; y: number };
22
24
  }
23
25
 
24
- interface contextItemPropx {
26
+ interface IContextItemProps {
25
27
  visible: boolean;
26
28
  posX: number;
27
29
  posY: number;
@@ -30,127 +32,111 @@ interface contextItemPropx {
30
32
  contextActions: any;
31
33
  }
32
34
 
35
+ interface IHoverDetailProps {
36
+ visible: boolean;
37
+ posX: number;
38
+ posY: number;
39
+ item: IItem | null;
40
+ }
41
+
33
42
  export const ItemContainer: React.FC<IItemContainerProps> = ({
34
43
  itemContainer,
35
44
  onClose,
36
45
  onMouseOver,
37
46
  onActionSelected,
47
+ initialPosition = { x: 0, y: 0 },
38
48
  }) => {
39
- const [contextData, setContextData] = useState<contextItemPropx>({
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>({
40
76
  visible: false,
41
- posX: 0,
42
- posY: 0,
77
+ posX: initX,
78
+ posY: initY,
43
79
  contextActions: [],
44
80
  slotItem: null,
45
81
  });
46
- const [itemHoverDetail] = useState({
82
+ const [itemHoverDetail, setItemHoverDetail] = useState<IHoverDetailProps>({
47
83
  visible: false,
48
- posX: 0,
49
- posY: 0,
84
+ posX: initX,
85
+ posY: initY,
50
86
  item: null,
51
87
  });
52
- let selectedSlotContext: number | null = null;
53
88
 
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;
89
+ const clearContextMenu = () => {
90
+ setContextData({
91
+ visible: false,
92
+ posX: 0,
93
+ posY: 0,
94
+ contextActions: [],
95
+ slotItem: null,
96
+ });
90
97
  };
91
98
 
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,
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,
132
113
  });
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]);
114
+ onMouseOver(event, slotIndex, item);
115
+ } else {
116
+ clearItemHoverDetail();
117
+ }
118
+ };
142
119
 
143
- const onActionContextMenu = (event: any, slotIndex: number) => {
144
- selectedSlotContext = slotIndex;
145
- console.log(event);
146
- // setContextData({...contextData, slotItem: slotIndex})
120
+ const clearItemHoverDetail = () => {
121
+ setItemHoverDetail({
122
+ ...itemHoverDetail,
123
+ visible: false,
124
+ item: null,
125
+ });
147
126
  };
148
127
 
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);
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();
154
140
  };
155
141
 
156
142
  const onSelected = (selectedActionId: ItemSocketEvents | string): void => {
@@ -158,8 +144,8 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
158
144
  actionType: selectedActionId,
159
145
  item: contextData.slotItem,
160
146
  };
161
- // TODO: create a function to validate the selection set the paylod data.
162
147
  onActionSelected(payloadData);
148
+ clearContextMenu();
163
149
  };
164
150
 
165
151
  const onRenderSlots = () => {
@@ -172,7 +158,10 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
172
158
  slotIndex={i}
173
159
  item={itemContainer.slots?.[i] || null}
174
160
  onMouseOver={handleOnMouseHover}
175
- onActionContextMenu={onActionContextMenu}
161
+ onClick={handleOnItemClick}
162
+ onCancelContextMenu={() => {
163
+ clearContextMenu();
164
+ }}
176
165
  />
177
166
  );
178
167
  }
@@ -180,31 +169,39 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
180
169
  };
181
170
 
182
171
  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>
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>
208
205
  );
209
206
  };
210
207
 
@@ -7,17 +7,25 @@ import { SpriteFromAtlas } from '../SpriteFromAtlas';
7
7
  interface IProps {
8
8
  slotIndex: number;
9
9
  item: IItem | null;
10
- onMouseOver: (event: any, slotIndex: number, item: IItem | null) => void;
11
- onActionContextMenu: (event: any, slotIndex: number) => void;
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;
12
19
  }
13
20
 
14
21
  export const ItemSlot: React.FC<IProps> = ({
15
22
  slotIndex,
16
23
  item,
17
24
  onMouseOver,
18
- onActionContextMenu,
25
+ onClick,
26
+ onCancelContextMenu,
19
27
  }) => {
20
- const getLeftPostionValue = (quantity: number) => {
28
+ const getLeftPositionValue = (quantity: number) => {
21
29
  if (quantity > 0 && quantity < 10) return '2.5rem';
22
30
  else if (quantity > 9 && quantity < 100) return '2.0rem';
23
31
  else if (quantity > 99) return '1rem';
@@ -26,11 +34,18 @@ export const ItemSlot: React.FC<IProps> = ({
26
34
 
27
35
  return (
28
36
  <Container
29
- onContextMenu={event => {
30
- onActionContextMenu(event, slotIndex);
31
- }}
32
37
  className="rpgui-icon empty-slot"
33
- onMouseOver={event => onMouseOver(event, slotIndex, item)}
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
+ }}
34
49
  >
35
50
  {item && item.texturePath ? (
36
51
  <SpriteFromAtlas
@@ -41,7 +56,7 @@ export const ItemSlot: React.FC<IProps> = ({
41
56
  />
42
57
  ) : null}
43
58
  {item && item.isStackable && item?.stackQty ? (
44
- <ItemQty left={getLeftPostionValue(item.stackQty)}>
59
+ <ItemQty left={getLeftPositionValue(item.stackQty)}>
45
60
  {' '}
46
61
  {item.stackQty}{' '}
47
62
  </ItemQty>
@@ -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,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
+ }
@@ -0,0 +1,4 @@
1
+ export interface IPosition {
2
+ x: number;
3
+ y: number;
4
+ }