@rpg-engine/long-bow 0.8.9 → 0.8.11

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,5 @@
1
+ import { Meta } from '@storybook/react';
2
+ import { IGemSelectorProps } from '../../../components/itemSelector/GemSelector';
3
+ declare const meta: Meta<IGemSelectorProps>;
4
+ export default meta;
5
+ export declare const Default: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, IGemSelectorProps>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.9",
3
+ "version": "0.8.11",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -84,7 +84,7 @@
84
84
  "dependencies": {
85
85
  "@capacitor/core": "^6.1.0",
86
86
  "@rollup/plugin-image": "^2.1.1",
87
- "@rpg-engine/shared": "^0.9.92",
87
+ "@rpg-engine/shared": "^0.9.96",
88
88
  "dayjs": "^1.11.2",
89
89
  "font-awesome": "^4.7.0",
90
90
  "fs-extra": "^10.1.0",
@@ -62,15 +62,6 @@ export interface IItemContainerProps {
62
62
  opacity?: number;
63
63
  }
64
64
 
65
- type onDragStart =
66
- | ((
67
- item: IItem,
68
- slotIndex: number,
69
- itemContainerType: ItemContainerType | null
70
- ) => void)
71
- | undefined;
72
- type onDragEnd = ((quantity?: number) => void) | undefined;
73
-
74
65
  const MIN_SLOTS_FOR_SCROLL = 20;
75
66
 
76
67
  export const ItemContainer: React.FC<IItemContainerProps> = React.memo(
@@ -154,7 +145,7 @@ export const ItemContainer: React.FC<IItemContainerProps> = React.memo(
154
145
  document.removeEventListener('pointermove', handleMouseMove);
155
146
  }, [handleMouseMove, stopScrolling]);
156
147
 
157
- const onDragStartHandler: onDragStart = useCallback(
148
+ const onDragStartHandler = useCallback(
158
149
  (
159
150
  item: IItem,
160
151
  slotIndex: number,
@@ -169,7 +160,7 @@ export const ItemContainer: React.FC<IItemContainerProps> = React.memo(
169
160
  [onItemDragStart, onDragStartScrollingEvents]
170
161
  );
171
162
 
172
- const onDragEndHandler: onDragEnd = useCallback(
163
+ const onDragEndHandler = useCallback(
173
164
  (quantity: number | undefined) => {
174
165
  if (onItemDragEnd) {
175
166
  onItemDragEnd(quantity);
@@ -179,16 +170,85 @@ export const ItemContainer: React.FC<IItemContainerProps> = React.memo(
179
170
  [onItemDragEnd, onDragEndScrollingEvents]
180
171
  );
181
172
 
182
- // Memoize handleSetShortcut to prevent unnecessary re-renders
183
- const memoizedHandleSetShortcut = useCallback(
184
- (item: IItem, index: number) => {
185
- handleSetShortcut(item, index);
173
+ const handleItemClick = useCallback(
174
+ (
175
+ itemType: IItem['type'],
176
+ containerType: ItemContainerType | null,
177
+ item: IItem
178
+ ) => {
179
+ if (settingShortcutIndex !== -1) {
180
+ setSettingShortcutIndex(-1);
181
+ handleSetShortcut(item, settingShortcutIndex);
182
+ } else if (onItemClick) {
183
+ onItemClick(item, itemType, containerType);
184
+ }
185
+ },
186
+ [settingShortcutIndex, handleSetShortcut, onItemClick]
187
+ );
188
+
189
+ const handlePlaceDrop = useCallback(
190
+ (
191
+ item: IItem | null,
192
+ slotIndex: number,
193
+ itemContainerType: ItemContainerType | null
194
+ ) => {
195
+ if (onItemPlaceDrop) {
196
+ onItemPlaceDrop(item, slotIndex, itemContainerType);
197
+ }
198
+ onDragEndScrollingEvents();
199
+ },
200
+ [onItemPlaceDrop, onDragEndScrollingEvents]
201
+ );
202
+
203
+ const handleOutsideDrop = useCallback(
204
+ (item: IItem, position: IPosition) => {
205
+ if (onOutsideDrop) {
206
+ onOutsideDrop(item, position);
207
+ }
208
+ onDragEndScrollingEvents();
186
209
  },
187
- [handleSetShortcut]
210
+ [onOutsideDrop, onDragEndScrollingEvents]
188
211
  );
189
212
 
190
- // Memoize onRenderSlots to prevent re-creating the slots array on every render
191
- const onRenderSlots = useMemo(() => {
213
+ const handleQuantitySelect = useCallback(
214
+ (maxQuantity: number, callback: (quantity: number) => void) => {
215
+ setQuantitySelect({
216
+ isOpen: true,
217
+ maxQuantity,
218
+ callback,
219
+ });
220
+ },
221
+ []
222
+ );
223
+
224
+ // Memoize slot rendering configuration
225
+ const slotConfig = useMemo(
226
+ () => ({
227
+ type,
228
+ atlasIMG,
229
+ atlasJSON,
230
+ scale,
231
+ equipmentSet,
232
+ isDepotSystem,
233
+ isSelectingShortcut: settingShortcutIndex !== -1,
234
+ checkIfItemCanBeMoved,
235
+ checkIfItemShouldDragEnd,
236
+ }),
237
+ [
238
+ type,
239
+ atlasIMG,
240
+ atlasJSON,
241
+ scale,
242
+ equipmentSet,
243
+ isDepotSystem,
244
+ settingShortcutIndex,
245
+ checkIfItemCanBeMoved,
246
+ checkIfItemShouldDragEnd,
247
+ ]
248
+ );
249
+
250
+ // Memoize slots rendering
251
+ const renderSlots = useMemo(() => {
192
252
  const slots = [];
193
253
 
194
254
  for (let i = 0; i < itemContainer.slotQty; i++) {
@@ -199,62 +259,27 @@ export const ItemContainer: React.FC<IItemContainerProps> = React.memo(
199
259
  key={i}
200
260
  slotIndex={i}
201
261
  item={currentItem}
202
- itemContainerType={type}
203
- onMouseOver={
204
- onMouseOver
205
- ? (event, slotIndex, item) =>
206
- onMouseOver(event, slotIndex, item)
207
- : undefined
208
- }
209
- onMouseOut={undefined} // Assuming you don't need onMouseOut here
210
- onPointerDown={(itemType, containerType, item) => {
211
- if (settingShortcutIndex !== -1) {
212
- setSettingShortcutIndex(-1);
213
- memoizedHandleSetShortcut(item, settingShortcutIndex);
214
- } else if (onItemClick) {
215
- onItemClick(item, itemType, containerType);
216
- }
217
- }}
262
+ itemContainerType={slotConfig.type}
263
+ onMouseOver={onMouseOver}
264
+ onPointerDown={handleItemClick}
218
265
  onDragStart={onDragStartHandler}
219
266
  onDragEnd={onDragEndHandler}
220
- dragScale={scale}
221
- checkIfItemCanBeMoved={checkIfItemCanBeMoved}
222
- checkIfItemShouldDragEnd={checkIfItemShouldDragEnd}
223
- openQuantitySelector={(maxQuantity, callback) => {
224
- setQuantitySelect({
225
- isOpen: true,
226
- maxQuantity,
227
- callback,
228
- });
229
- }}
230
- onPlaceDrop={(item, slotIndex, itemContainerType) => {
231
- if (onItemPlaceDrop) {
232
- onItemPlaceDrop(item, slotIndex, itemContainerType);
233
- }
234
-
235
- console.log('PLACE DROP');
236
-
237
- onDragEndScrollingEvents();
238
- }}
239
- onOutsideDrop={(item, position) => {
240
- if (onOutsideDrop) {
241
- onOutsideDrop(item, position);
242
- }
243
-
244
- console.log('OUTSIDE DROP');
245
-
246
- onDragEndScrollingEvents();
247
- }}
248
- atlasIMG={atlasIMG}
249
- atlasJSON={atlasJSON}
250
- isSelectingShortcut={settingShortcutIndex !== -1}
251
- equipmentSet={equipmentSet}
267
+ dragScale={slotConfig.scale}
268
+ checkIfItemCanBeMoved={slotConfig.checkIfItemCanBeMoved}
269
+ checkIfItemShouldDragEnd={slotConfig.checkIfItemShouldDragEnd}
270
+ openQuantitySelector={handleQuantitySelect}
271
+ onPlaceDrop={handlePlaceDrop}
272
+ onOutsideDrop={handleOutsideDrop}
273
+ atlasIMG={slotConfig.atlasIMG}
274
+ atlasJSON={slotConfig.atlasJSON}
275
+ isSelectingShortcut={slotConfig.isSelectingShortcut}
276
+ equipmentSet={slotConfig.equipmentSet}
252
277
  setItemShortcut={
253
278
  type === ItemContainerType.Inventory
254
- ? memoizedHandleSetShortcut
279
+ ? handleSetShortcut
255
280
  : undefined
256
281
  }
257
- isDepotSystem={isDepotSystem}
282
+ isDepotSystem={slotConfig.isDepotSystem}
258
283
  />
259
284
  );
260
285
  }
@@ -262,23 +287,16 @@ export const ItemContainer: React.FC<IItemContainerProps> = React.memo(
262
287
  }, [
263
288
  itemContainer.slotQty,
264
289
  itemContainer.slots,
265
- type,
290
+ slotConfig,
266
291
  onMouseOver,
267
- settingShortcutIndex,
268
- memoizedHandleSetShortcut,
269
- onItemClick,
292
+ handleItemClick,
270
293
  onDragStartHandler,
271
294
  onDragEndHandler,
272
- scale,
273
- checkIfItemCanBeMoved,
274
- checkIfItemShouldDragEnd,
275
- onItemPlaceDrop,
276
- onOutsideDrop,
277
- atlasIMG,
278
- atlasJSON,
279
- equipmentSet,
280
- isDepotSystem,
281
- onDragEndScrollingEvents,
295
+ handleQuantitySelect,
296
+ handlePlaceDrop,
297
+ handleOutsideDrop,
298
+ handleSetShortcut,
299
+ type,
282
300
  ]);
283
301
 
284
302
  return (
@@ -317,7 +335,7 @@ export const ItemContainer: React.FC<IItemContainerProps> = React.memo(
317
335
  isScrollable={itemContainer.slotQty > MIN_SLOTS_FOR_SCROLL}
318
336
  isFullScreen={isFullScreen}
319
337
  >
320
- {onRenderSlots}
338
+ {renderSlots}
321
339
  </ItemsContainer>
322
340
  </SlotsContainer>
323
341
  {quantitySelect.isOpen && (
@@ -6,7 +6,7 @@ interface IProps {
6
6
  item: IItem;
7
7
  }
8
8
 
9
- const gemColors: { [key: string]: string } = {
9
+ export const gemColors: { [key: string]: string } = {
10
10
  'emerald-gem': '#50C878', // Emerald green
11
11
  'topaz-radiance': '#FFC87C', // Topaz yellow
12
12
  'sapphire-gem': '#0F52BA', // Sapphire blue
@@ -203,5 +203,9 @@ export const generateContextMenu = (
203
203
  }
204
204
  }
205
205
 
206
+ if (item.attachedGems && item.attachedGems.length > 0) {
207
+ contextActionMenu.push({ id: ItemSocketEvents.DetachGem, text: "Detach Gems" });
208
+ }
209
+
206
210
  return contextActionMenu;
207
211
  };
@@ -0,0 +1,203 @@
1
+ import { IGemSchema, IItem } from '@rpg-engine/shared';
2
+ import React, { useState } from 'react';
3
+ import styled from 'styled-components';
4
+ import { uiColors } from '../../constants/uiColors';
5
+ import { Button, ButtonTypes } from '../Button';
6
+ import { CheckItem } from '../CheckItem';
7
+ import { useResponsiveSize } from '../CraftBook/hooks/useResponsiveSize';
8
+ import { DraggableContainer } from '../DraggableContainer';
9
+ import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
10
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
11
+
12
+ export interface IGemSelectorProps {
13
+ atlasJSON: any;
14
+ atlasIMG: any;
15
+ item: IItem;
16
+ scale?: number;
17
+ onClose: () => void;
18
+ onSelect: (selectedGems: IGemSchema[]) => void;
19
+ }
20
+
21
+ export const GemSelector: React.FC<IGemSelectorProps> = ({
22
+ atlasJSON,
23
+ atlasIMG,
24
+ item,
25
+ scale,
26
+ onClose,
27
+ onSelect,
28
+ }) => {
29
+ const [selectedGems, setSelectedGems] = useState<IGemSchema[]>([]);
30
+ const size = useResponsiveSize(scale);
31
+
32
+ const handleGemToggle = (gem: IGemSchema) => {
33
+ setSelectedGems(prev =>
34
+ prev.some(selected => selected.key === gem.key)
35
+ ? prev.filter(selected => selected.key !== gem.key)
36
+ : [...prev, gem]
37
+ );
38
+ };
39
+
40
+ const handleConfirm = () => {
41
+ onSelect(selectedGems);
42
+ onClose();
43
+ };
44
+
45
+ if (!size) return null;
46
+
47
+ return (
48
+ <DraggableContainer
49
+ type={RPGUIContainerTypes.Framed}
50
+ width="450px"
51
+ height={size.height}
52
+ scale={scale}
53
+ cancelDrag=".gem-selector-container"
54
+ onCloseButton={onClose}
55
+ >
56
+ <ContentWrapper>
57
+ <Header>
58
+ <Title>GEM SELECTION</Title>
59
+ <Subtitle>Select gems to detach</Subtitle>
60
+ </Header>
61
+
62
+ <GemGrid>
63
+ {item.attachedGems?.map((gem, index) => {
64
+ return (
65
+ <GemItem key={`${gem.key}-${index}`}>
66
+ <CheckItemWrapper>
67
+ <CheckItem
68
+ defaultValue={selectedGems.some(
69
+ selected => selected.key === gem.key
70
+ )}
71
+ onChange={() => {
72
+ handleGemToggle(gem);
73
+ }}
74
+ />
75
+ </CheckItemWrapper>
76
+ <SpriteWrapper>
77
+ <SpriteFromAtlas
78
+ atlasIMG={atlasIMG}
79
+ atlasJSON={atlasJSON}
80
+ spriteKey={`gems/${gem.key}.png`}
81
+ imgScale={2}
82
+ imgClassname="gem-sprite"
83
+ centered={true}
84
+ />
85
+ </SpriteWrapper>
86
+ <GemName>
87
+ {gem.name.replace(/ of /gi, '\n').toUpperCase()}
88
+ </GemName>
89
+ </GemItem>
90
+ );
91
+ })}
92
+ </GemGrid>
93
+
94
+ <ButtonWrapper>
95
+ <Button
96
+ buttonType={ButtonTypes.RPGUIButton}
97
+ onPointerDown={() => {
98
+ onClose();
99
+ }}
100
+ >
101
+ Cancel
102
+ </Button>
103
+ <Button
104
+ buttonType={ButtonTypes.RPGUIButton}
105
+ onPointerDown={() => {
106
+ handleConfirm();
107
+ }}
108
+ disabled={selectedGems.length === 0}
109
+ >
110
+ Confirm
111
+ </Button>
112
+ </ButtonWrapper>
113
+ </ContentWrapper>
114
+ </DraggableContainer>
115
+ );
116
+ };
117
+
118
+ const Title = styled.h1`
119
+ font-size: 0.8rem;
120
+ color: ${uiColors.yellow} !important;
121
+ `;
122
+
123
+ const Subtitle = styled.h2`
124
+ font-size: 0.6rem;
125
+ color: ${uiColors.white};
126
+ margin: 0;
127
+ `;
128
+
129
+ const Header = styled.div`
130
+ text-align: center;
131
+ padding: 5px;
132
+ border-bottom: 2px solid #444;
133
+ `;
134
+
135
+ const GemGrid = styled.div`
136
+ display: grid;
137
+ grid-template-columns: repeat(3, 1fr);
138
+ gap: 15px;
139
+ margin-bottom: 20px;
140
+ margin-top: 10px;
141
+ min-height: 150px;
142
+ overflow-y: auto;
143
+ `;
144
+
145
+ const ContentWrapper = styled.div`
146
+ display: flex;
147
+ flex-direction: column;
148
+ align-items: center;
149
+ margin-left: 20px;
150
+ justify-content: space-between;
151
+ height: 100%;
152
+ `;
153
+
154
+ const GemItem = styled.div`
155
+ display: flex;
156
+ flex-direction: column;
157
+ align-items: center;
158
+ justify-content: space-between;
159
+ background-color: ${uiColors.darkGray} !important;
160
+ height: 100px;
161
+ width: 90px;
162
+ padding: 5px;
163
+ `;
164
+
165
+ const GemName = styled.div`
166
+ font-size: 0.5rem;
167
+ color: ${uiColors.yellow} !important;
168
+ text-align: center;
169
+ text-transform: uppercase;
170
+ line-height: 1.2;
171
+ width: 100%;
172
+ min-height: 30px;
173
+ display: -webkit-box;
174
+ -webkit-line-clamp: 2;
175
+ -webkit-box-orient: vertical;
176
+ word-break: break-word;
177
+ `;
178
+
179
+ const ButtonWrapper = styled.div`
180
+ display: flex;
181
+ justify-content: center;
182
+ align-self: center;
183
+ `;
184
+
185
+ const CheckItemWrapper = styled.div`
186
+ display: flex;
187
+ justify-content: center;
188
+ width: 100%;
189
+ height: 20px;
190
+
191
+ input.rpgui-checkbox + label {
192
+ margin: 0 !important;
193
+ padding-left: 23px !important;
194
+ }
195
+ `;
196
+
197
+ const SpriteWrapper = styled.div`
198
+ display: flex;
199
+ align-items: center;
200
+ justify-content: center;
201
+ width: 100%;
202
+ height: 50px;
203
+ `;
package/src/index.tsx CHANGED
@@ -25,6 +25,7 @@ export * from './components/Item/Inventory/ItemContainer';
25
25
  export * from './components/Item/Inventory/ItemPropertySimpleHandler';
26
26
  export * from './components/Item/Inventory/ItemQuantitySelectorModal';
27
27
  export * from './components/Item/Inventory/ItemSlot';
28
+ export * from './components/itemSelector/GemSelector';
28
29
  export * from './components/itemSelector/ItemSelector';
29
30
  export * from './components/Leaderboard/Leaderboard';
30
31
  export * from './components/ListMenu';
@@ -0,0 +1,66 @@
1
+ import { IItem, ItemType } from '@rpg-engine/shared';
2
+ import { Meta, Story } from '@storybook/react';
3
+ import React from 'react';
4
+ import { RPGUIRoot } from '../../..';
5
+ import {
6
+ GemSelector,
7
+ IGemSelectorProps,
8
+ } from '../../../components/itemSelector/GemSelector';
9
+
10
+ const meta: Meta<IGemSelectorProps> = {
11
+ title: 'UI/Dropdowns & Selectors/Gem Selector',
12
+ component: GemSelector,
13
+ parameters: {
14
+ controls: { expanded: true },
15
+ },
16
+ };
17
+
18
+ export default meta;
19
+
20
+ const Template: Story<IGemSelectorProps> = args => (
21
+ <RPGUIRoot>
22
+ <GemSelector {...args} />
23
+ </RPGUIRoot>
24
+ );
25
+
26
+ const mockItem = ({
27
+ _id: '123',
28
+ name: 'Example Item',
29
+ type: ItemType.Armor,
30
+ texturePath: 'weapons/sword.png',
31
+ attachedGems: [
32
+ {
33
+ key: 'emerald-gem',
34
+ name: 'Emerald of Power',
35
+ texturePath: 'gems/emerald-gem.png',
36
+ },
37
+ {
38
+ key: 'ruby-gem',
39
+ name: 'Ruby of Fire',
40
+ texturePath: 'gems/ruby-gem.png',
41
+ },
42
+ {
43
+ key: 'sapphire-gem',
44
+ name: 'Sapphire of Wisdom',
45
+ texturePath: 'gems/sapphire-gem.png',
46
+ },
47
+ {
48
+ key: 'earthstone-gem',
49
+ name: 'EarthStone Guardian',
50
+ texturePath: 'gems/earthstone-gem.png',
51
+ },
52
+ ],
53
+ } as unknown) as IItem;
54
+
55
+ export const Default = Template.bind({});
56
+ Default.args = {
57
+ atlasJSON: require('../../../mocks/atlas/items/items.json'),
58
+ atlasIMG: require('../../../mocks/atlas/items/items.png'),
59
+ item: mockItem,
60
+ onClose: () => console.log('Closing Gem Selector'),
61
+ onSelect: gems =>
62
+ console.log(
63
+ 'Selected gems:',
64
+ gems.map(gem => gem.name)
65
+ ), // Agora imprime os nomes das gemas
66
+ };