@rpg-engine/long-bow 0.8.31 → 0.8.33
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/InformationCenter/sections/bestiary/BestiaryAdvancedFilters.d.ts +13 -0
- package/dist/components/InformationCenter/sections/bestiary/InformationCenterBestiarySection.d.ts +2 -2
- package/dist/components/InformationCenter/sections/items/ItemsAdvancedFilters.d.ts +11 -0
- package/dist/components/shared/AdvancedFilters/AdvancedFilters.d.ts +23 -0
- package/dist/components/shared/PaginatedContent/PaginatedContent.d.ts +1 -0
- package/dist/components/shared/SearchBar/SearchBar.d.ts +1 -0
- package/dist/components/shared/SearchHeader/SearchHeader.d.ts +1 -0
- package/dist/hooks/useTooltipPosition.d.ts +15 -0
- package/dist/long-bow.cjs.development.js +532 -212
- 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 +533 -213
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/InformationCenter/sections/bestiary/BestiaryAdvancedFilters.tsx +95 -0
- package/src/components/InformationCenter/sections/bestiary/InformationCenterBestiarySection.tsx +120 -69
- package/src/components/InformationCenter/sections/items/InformationCenterItemsSection.tsx +77 -75
- package/src/components/InformationCenter/sections/items/ItemsAdvancedFilters.tsx +80 -0
- package/src/components/InformationCenter/shared/BaseInformationDetails.tsx +3 -7
- package/src/components/shared/AdvancedFilters/AdvancedFilters.tsx +279 -0
- package/src/components/shared/PaginatedContent/PaginatedContent.tsx +1 -0
- package/src/components/shared/SearchBar/SearchBar.tsx +15 -5
- package/src/components/shared/SearchHeader/SearchHeader.tsx +2 -0
- package/src/hooks/useTooltipPosition.ts +73 -0
package/package.json
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { EntityAttackType, NPCSubtype } from '@rpg-engine/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import {
|
|
4
|
+
AdvancedFilters,
|
|
5
|
+
IFilterSection,
|
|
6
|
+
} from '../../../shared/AdvancedFilters/AdvancedFilters';
|
|
7
|
+
import { formatItemType } from '../items/InformationCenterItemsSection';
|
|
8
|
+
|
|
9
|
+
interface IBestiaryAdvancedFiltersProps {
|
|
10
|
+
isOpen: boolean;
|
|
11
|
+
onToggle: () => void;
|
|
12
|
+
onLevelRangeChange: (range: [number | undefined, number | undefined]) => void;
|
|
13
|
+
onSubtypeChange: (value: string) => void;
|
|
14
|
+
onAttackTypeChange: (value: string) => void;
|
|
15
|
+
levelRange: [number | undefined, number | undefined];
|
|
16
|
+
selectedSubtype: string;
|
|
17
|
+
selectedAttackType: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const BestiaryAdvancedFilters = ({
|
|
21
|
+
isOpen,
|
|
22
|
+
onToggle,
|
|
23
|
+
onLevelRangeChange,
|
|
24
|
+
onSubtypeChange,
|
|
25
|
+
onAttackTypeChange,
|
|
26
|
+
levelRange,
|
|
27
|
+
selectedSubtype,
|
|
28
|
+
selectedAttackType,
|
|
29
|
+
}: IBestiaryAdvancedFiltersProps): JSX.Element => {
|
|
30
|
+
const subtypeOptions = [
|
|
31
|
+
{ id: 0, value: 'all', option: 'All Types' },
|
|
32
|
+
...Object.entries(NPCSubtype).map(([, value], index) => ({
|
|
33
|
+
id: index + 1,
|
|
34
|
+
value,
|
|
35
|
+
option: formatItemType(value),
|
|
36
|
+
})),
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const attackTypeOptions = [
|
|
40
|
+
{ id: 0, value: 'all', option: 'All Attack Types' },
|
|
41
|
+
...Object.entries(EntityAttackType).map(([, value], index) => ({
|
|
42
|
+
id: index + 1,
|
|
43
|
+
value,
|
|
44
|
+
option: formatItemType(value),
|
|
45
|
+
})),
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const hasActiveFilters =
|
|
49
|
+
levelRange[0] !== undefined ||
|
|
50
|
+
levelRange[1] !== undefined ||
|
|
51
|
+
selectedSubtype !== 'all' ||
|
|
52
|
+
selectedAttackType !== 'all';
|
|
53
|
+
|
|
54
|
+
const handleClearFilters = () => {
|
|
55
|
+
onLevelRangeChange([undefined, undefined]);
|
|
56
|
+
onSubtypeChange('all');
|
|
57
|
+
onAttackTypeChange('all');
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const sections: IFilterSection[] = [
|
|
61
|
+
{
|
|
62
|
+
type: 'range',
|
|
63
|
+
label: 'Level Range',
|
|
64
|
+
key: 'level',
|
|
65
|
+
value: levelRange,
|
|
66
|
+
onChange: onLevelRangeChange,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: 'dropdown',
|
|
70
|
+
label: 'Monster Type',
|
|
71
|
+
key: 'subtype',
|
|
72
|
+
options: subtypeOptions,
|
|
73
|
+
value: selectedSubtype,
|
|
74
|
+
onChange: onSubtypeChange,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'dropdown',
|
|
78
|
+
label: 'Attack Type',
|
|
79
|
+
key: 'attackType',
|
|
80
|
+
options: attackTypeOptions,
|
|
81
|
+
value: selectedAttackType,
|
|
82
|
+
onChange: onAttackTypeChange,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<AdvancedFilters
|
|
88
|
+
isOpen={isOpen}
|
|
89
|
+
onToggle={onToggle}
|
|
90
|
+
sections={sections}
|
|
91
|
+
onClearAll={handleClearFilters}
|
|
92
|
+
hasActiveFilters={hasActiveFilters}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
};
|
package/src/components/InformationCenter/sections/bestiary/InformationCenterBestiarySection.tsx
CHANGED
|
@@ -5,11 +5,14 @@ import {
|
|
|
5
5
|
} from '@rpg-engine/shared';
|
|
6
6
|
import React, { useMemo, useState } from 'react';
|
|
7
7
|
import styled from 'styled-components';
|
|
8
|
+
import { useTooltipPosition } from '../../../../hooks/useTooltipPosition';
|
|
8
9
|
import { IOptionsProps } from '../../../Dropdown';
|
|
9
10
|
import { PaginatedContent } from '../../../shared/PaginatedContent/PaginatedContent';
|
|
10
11
|
import { Portal } from '../../../shared/Portal/Portal';
|
|
11
12
|
import { InformationCenterCell } from '../../InformationCenterCell';
|
|
13
|
+
import { BaseInformationDetails } from '../../shared/BaseInformationDetails';
|
|
12
14
|
import { formatItemType } from '../items/InformationCenterItemsSection';
|
|
15
|
+
import { BestiaryAdvancedFilters } from './BestiaryAdvancedFilters';
|
|
13
16
|
import { InformationCenterNPCDetails } from './InformationCenterNPCDetails';
|
|
14
17
|
import { InformationCenterNPCTooltip } from './InformationCenterNPCTooltip';
|
|
15
18
|
|
|
@@ -25,7 +28,7 @@ interface IBestiarySectionProps {
|
|
|
25
28
|
tabId: string;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
export const InformationCenterBestiarySection
|
|
31
|
+
export const InformationCenterBestiarySection = ({
|
|
29
32
|
bestiaryItems,
|
|
30
33
|
itemsAtlasJSON,
|
|
31
34
|
itemsAtlasIMG,
|
|
@@ -35,74 +38,59 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
|
|
|
35
38
|
entitiesAtlasIMG,
|
|
36
39
|
initialSearchQuery,
|
|
37
40
|
tabId,
|
|
38
|
-
}) => {
|
|
41
|
+
}: IBestiarySectionProps): JSX.Element => {
|
|
39
42
|
const isMobile = isMobileOrTablet();
|
|
40
|
-
const [searchQuery, setSearchQuery] = useState(initialSearchQuery);
|
|
41
|
-
const [tooltipData, setTooltipData] = useState<{
|
|
42
|
-
npc: IInformationCenterNPC;
|
|
43
|
-
position: { x: number; y: number };
|
|
44
|
-
} | null>(null);
|
|
43
|
+
const [searchQuery, setSearchQuery] = useState<string>(initialSearchQuery);
|
|
45
44
|
const [
|
|
46
45
|
selectedMonster,
|
|
47
46
|
setSelectedMonster,
|
|
48
47
|
] = useState<IInformationCenterNPC | null>(null);
|
|
48
|
+
const [selectedBestiaryCategory, setSelectedBestiaryCategory] = useState<
|
|
49
|
+
string
|
|
50
|
+
>('all');
|
|
49
51
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
npc: monster,
|
|
57
|
-
position: { x: event.clientX, y: event.clientY },
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
};
|
|
52
|
+
const {
|
|
53
|
+
tooltipState,
|
|
54
|
+
handleMouseEnter,
|
|
55
|
+
handleMouseLeave,
|
|
56
|
+
TOOLTIP_WIDTH,
|
|
57
|
+
} = useTooltipPosition<IInformationCenterNPC>();
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
...tooltipData,
|
|
72
|
-
position: { x: event.clientX, y: event.clientY },
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
};
|
|
59
|
+
// Advanced filters state
|
|
60
|
+
const [isAdvancedFiltersOpen, setIsAdvancedFiltersOpen] = useState<boolean>(
|
|
61
|
+
false
|
|
62
|
+
);
|
|
63
|
+
const [levelRange, setLevelRange] = useState<
|
|
64
|
+
[number | undefined, number | undefined]
|
|
65
|
+
>([undefined, undefined]);
|
|
66
|
+
const [selectedSubtype, setSelectedSubtype] = useState<string>('all');
|
|
67
|
+
const [selectedAttackType, setSelectedAttackType] = useState<string>('all');
|
|
76
68
|
|
|
77
69
|
const handleTouchStart = (
|
|
78
70
|
monster: IInformationCenterNPC,
|
|
79
71
|
event: React.TouchEvent
|
|
80
|
-
) => {
|
|
72
|
+
): void => {
|
|
81
73
|
event.preventDefault();
|
|
82
74
|
setSelectedMonster(monster);
|
|
83
|
-
setTooltipData(null);
|
|
84
75
|
};
|
|
85
76
|
|
|
86
|
-
const handleMonsterClick = (monster: IInformationCenterNPC) => {
|
|
77
|
+
const handleMonsterClick = (monster: IInformationCenterNPC): void => {
|
|
87
78
|
setSelectedMonster(monster);
|
|
88
|
-
setTooltipData(null);
|
|
89
79
|
};
|
|
90
80
|
|
|
91
|
-
const [selectedBestiaryCategory, setSelectedBestiaryCategory] = useState<
|
|
92
|
-
string
|
|
93
|
-
>('all');
|
|
94
|
-
|
|
95
81
|
const bestiaryCategoryOptions: IOptionsProps[] = [
|
|
96
82
|
{ id: 0, value: 'all', option: 'All Monsters' },
|
|
97
83
|
{ id: 1, value: 'bosses', option: 'Bosses' },
|
|
98
|
-
...Object.entries(NPCAlignment)
|
|
99
|
-
|
|
100
|
-
value,
|
|
101
|
-
|
|
102
|
-
|
|
84
|
+
...Object.entries(NPCAlignment)
|
|
85
|
+
.filter(([, value]) => value !== NPCAlignment.Friendly)
|
|
86
|
+
.map(([, value], index) => ({
|
|
87
|
+
id: index + 2,
|
|
88
|
+
value,
|
|
89
|
+
option: formatItemType(value),
|
|
90
|
+
})),
|
|
103
91
|
];
|
|
104
92
|
|
|
105
|
-
const renderItem = (item: IInformationCenterNPC) => (
|
|
93
|
+
const renderItem = (item: IInformationCenterNPC): JSX.Element => (
|
|
106
94
|
<InformationCenterCell
|
|
107
95
|
key={item.id}
|
|
108
96
|
name={item.name}
|
|
@@ -112,19 +100,19 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
|
|
|
112
100
|
onClick={() => handleMonsterClick(item)}
|
|
113
101
|
onMouseEnter={e => handleMouseEnter(item, e)}
|
|
114
102
|
onMouseLeave={handleMouseLeave}
|
|
115
|
-
onMouseMove={handleMouseMove}
|
|
116
103
|
onTouchStart={e => handleTouchStart(item, e)}
|
|
117
104
|
/>
|
|
118
105
|
);
|
|
119
106
|
|
|
120
|
-
const filteredItems = useMemo(() => {
|
|
107
|
+
const filteredItems = useMemo<IInformationCenterNPC[]>(() => {
|
|
121
108
|
return bestiaryItems.filter(item => {
|
|
109
|
+
// Basic search filter
|
|
122
110
|
const matchesSearch = item.name
|
|
123
111
|
.toLowerCase()
|
|
124
112
|
.includes(searchQuery.toLowerCase());
|
|
125
113
|
|
|
114
|
+
// Category filter
|
|
126
115
|
let matchesCategory = true;
|
|
127
|
-
|
|
128
116
|
if (selectedBestiaryCategory === 'bosses') {
|
|
129
117
|
matchesCategory = item.isBoss === true;
|
|
130
118
|
} else if (selectedBestiaryCategory === NPCAlignment.Hostile) {
|
|
@@ -134,17 +122,60 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
|
|
|
134
122
|
matchesCategory = item.alignment === selectedBestiaryCategory;
|
|
135
123
|
}
|
|
136
124
|
|
|
137
|
-
|
|
125
|
+
// Advanced filters
|
|
126
|
+
const matchesLevel =
|
|
127
|
+
(!levelRange[0] || item.skills.level >= levelRange[0]) &&
|
|
128
|
+
(!levelRange[1] || item.skills.level <= levelRange[1]);
|
|
129
|
+
|
|
130
|
+
const matchesSubtype =
|
|
131
|
+
selectedSubtype === 'all' || item.subType === selectedSubtype;
|
|
132
|
+
|
|
133
|
+
const matchesAttackType =
|
|
134
|
+
selectedAttackType === 'all' || item.attackType === selectedAttackType;
|
|
135
|
+
|
|
136
|
+
// Filter out friendly NPCs
|
|
137
|
+
const isNotFriendly = item.alignment !== NPCAlignment.Friendly;
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
matchesSearch &&
|
|
141
|
+
matchesCategory &&
|
|
142
|
+
matchesLevel &&
|
|
143
|
+
matchesSubtype &&
|
|
144
|
+
matchesAttackType &&
|
|
145
|
+
isNotFriendly
|
|
146
|
+
);
|
|
138
147
|
});
|
|
139
|
-
}, [
|
|
148
|
+
}, [
|
|
149
|
+
bestiaryItems,
|
|
150
|
+
searchQuery,
|
|
151
|
+
selectedBestiaryCategory,
|
|
152
|
+
levelRange,
|
|
153
|
+
selectedSubtype,
|
|
154
|
+
selectedAttackType,
|
|
155
|
+
]);
|
|
140
156
|
|
|
141
|
-
const handleSearchChange = (newQuery: string) => {
|
|
157
|
+
const handleSearchChange = (newQuery: string): void => {
|
|
142
158
|
setSearchQuery(newQuery);
|
|
143
159
|
if (newQuery && selectedBestiaryCategory !== 'all') {
|
|
144
160
|
setSelectedBestiaryCategory('all');
|
|
145
161
|
}
|
|
146
162
|
};
|
|
147
163
|
|
|
164
|
+
const SearchBarRightElement = (
|
|
165
|
+
<SearchBarActions>
|
|
166
|
+
<BestiaryAdvancedFilters
|
|
167
|
+
isOpen={isAdvancedFiltersOpen}
|
|
168
|
+
onToggle={() => setIsAdvancedFiltersOpen(!isAdvancedFiltersOpen)}
|
|
169
|
+
onLevelRangeChange={setLevelRange}
|
|
170
|
+
onSubtypeChange={setSelectedSubtype}
|
|
171
|
+
onAttackTypeChange={setSelectedAttackType}
|
|
172
|
+
levelRange={levelRange}
|
|
173
|
+
selectedSubtype={selectedSubtype}
|
|
174
|
+
selectedAttackType={selectedAttackType}
|
|
175
|
+
/>
|
|
176
|
+
</SearchBarActions>
|
|
177
|
+
);
|
|
178
|
+
|
|
148
179
|
return (
|
|
149
180
|
<>
|
|
150
181
|
<PaginatedContent<IInformationCenterNPC>
|
|
@@ -162,21 +193,28 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
|
|
|
162
193
|
value: searchQuery,
|
|
163
194
|
onChange: handleSearchChange,
|
|
164
195
|
placeholder: 'Search monsters...',
|
|
196
|
+
rightElement: SearchBarRightElement,
|
|
165
197
|
}}
|
|
166
|
-
dependencies={[
|
|
198
|
+
dependencies={[
|
|
199
|
+
selectedBestiaryCategory,
|
|
200
|
+
levelRange,
|
|
201
|
+
selectedSubtype,
|
|
202
|
+
selectedAttackType,
|
|
203
|
+
]}
|
|
167
204
|
itemHeight="180px"
|
|
168
205
|
/>
|
|
169
|
-
{!isMobile &&
|
|
206
|
+
{!isMobile && tooltipState && tooltipState.item && (
|
|
170
207
|
<Portal>
|
|
171
208
|
<TooltipWrapper
|
|
209
|
+
width={TOOLTIP_WIDTH}
|
|
172
210
|
style={{
|
|
173
211
|
position: 'fixed',
|
|
174
|
-
left:
|
|
175
|
-
top:
|
|
212
|
+
left: `${tooltipState.position.x}px`,
|
|
213
|
+
top: `${tooltipState.position.y}px`,
|
|
176
214
|
}}
|
|
177
215
|
>
|
|
178
216
|
<InformationCenterNPCTooltip
|
|
179
|
-
npc={
|
|
217
|
+
npc={tooltipState.item}
|
|
180
218
|
itemsAtlasJSON={itemsAtlasJSON}
|
|
181
219
|
itemsAtlasIMG={itemsAtlasIMG}
|
|
182
220
|
/>
|
|
@@ -185,25 +223,38 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
|
|
|
185
223
|
)}
|
|
186
224
|
{selectedMonster && (
|
|
187
225
|
<Portal>
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
iconAtlasJSON={iconsAtlasJSON}
|
|
194
|
-
entitiesAtlasJSON={entitiesAtlasJSON}
|
|
195
|
-
entitiesAtlasIMG={entitiesAtlasIMG}
|
|
226
|
+
<BaseInformationDetails
|
|
227
|
+
name={selectedMonster.name}
|
|
228
|
+
spriteKey={selectedMonster.key}
|
|
229
|
+
atlasJSON={entitiesAtlasJSON}
|
|
230
|
+
atlasIMG={entitiesAtlasIMG}
|
|
196
231
|
onBack={() => setSelectedMonster(null)}
|
|
197
|
-
|
|
232
|
+
>
|
|
233
|
+
<InformationCenterNPCDetails
|
|
234
|
+
npc={selectedMonster}
|
|
235
|
+
itemsAtlasJSON={itemsAtlasJSON}
|
|
236
|
+
itemsAtlasIMG={itemsAtlasIMG}
|
|
237
|
+
iconAtlasIMG={iconsAtlasIMG}
|
|
238
|
+
iconAtlasJSON={iconsAtlasJSON}
|
|
239
|
+
entitiesAtlasJSON={entitiesAtlasJSON}
|
|
240
|
+
entitiesAtlasIMG={entitiesAtlasIMG}
|
|
241
|
+
onBack={() => setSelectedMonster(null)}
|
|
242
|
+
/>
|
|
243
|
+
</BaseInformationDetails>
|
|
198
244
|
</Portal>
|
|
199
245
|
)}
|
|
200
246
|
</>
|
|
201
247
|
);
|
|
202
248
|
};
|
|
203
249
|
|
|
204
|
-
const TooltipWrapper = styled.div
|
|
205
|
-
position: fixed;
|
|
250
|
+
const TooltipWrapper = styled.div<{ width: number }>`
|
|
206
251
|
z-index: 1000;
|
|
207
252
|
pointer-events: none;
|
|
208
|
-
width:
|
|
253
|
+
width: ${props => `${props.width}px`};
|
|
254
|
+
`;
|
|
255
|
+
|
|
256
|
+
const SearchBarActions = styled.div`
|
|
257
|
+
display: flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
gap: 0.5rem;
|
|
209
260
|
`;
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
IInformationCenterItem,
|
|
3
3
|
IInformationCenterNPC,
|
|
4
|
-
ItemType,
|
|
5
4
|
isMobileOrTablet,
|
|
6
5
|
} from '@rpg-engine/shared';
|
|
7
|
-
import React, { useState } from 'react';
|
|
6
|
+
import React, { useMemo, useState } from 'react';
|
|
8
7
|
import styled from 'styled-components';
|
|
9
|
-
import {
|
|
8
|
+
import { useTooltipPosition } from '../../../../hooks/useTooltipPosition';
|
|
10
9
|
import { PaginatedContent } from '../../../shared/PaginatedContent/PaginatedContent';
|
|
11
10
|
import { Portal } from '../../../shared/Portal/Portal';
|
|
12
11
|
import { InformationCenterCell } from '../../InformationCenterCell';
|
|
12
|
+
import { BaseInformationDetails } from '../../shared/BaseInformationDetails';
|
|
13
13
|
import { InformationCenterItemDetails } from './InformationCenterItemDetails';
|
|
14
14
|
import { InformationCenterItemTooltip } from './InformationCenterItemTooltip';
|
|
15
|
-
|
|
16
|
-
const TOOLTIP_OFFSET = 200;
|
|
15
|
+
import { ItemsAdvancedFilters } from './ItemsAdvancedFilters';
|
|
17
16
|
|
|
18
17
|
interface IItemsSectionProps {
|
|
19
18
|
items: IInformationCenterItem[];
|
|
@@ -54,29 +53,33 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
|
|
|
54
53
|
const [selectedItemCategory, setSelectedItemCategory] = useState<string>(
|
|
55
54
|
'all'
|
|
56
55
|
);
|
|
57
|
-
const [
|
|
58
|
-
|
|
59
|
-
);
|
|
60
|
-
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
|
|
56
|
+
const [selectedTier, setSelectedTier] = useState<string>('all');
|
|
57
|
+
const [isAdvancedFiltersOpen, setIsAdvancedFiltersOpen] = useState(false);
|
|
61
58
|
const [
|
|
62
59
|
selectedItem,
|
|
63
60
|
setSelectedItem,
|
|
64
61
|
] = useState<IInformationCenterItem | null>(null);
|
|
65
62
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
]
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
63
|
+
const {
|
|
64
|
+
tooltipState,
|
|
65
|
+
handleMouseEnter,
|
|
66
|
+
handleMouseLeave,
|
|
67
|
+
TOOLTIP_WIDTH,
|
|
68
|
+
} = useTooltipPosition<IInformationCenterItem>();
|
|
69
|
+
|
|
70
|
+
const filteredItems = useMemo<IInformationCenterItem[]>(() => {
|
|
71
|
+
return items.filter(item => {
|
|
72
|
+
const matchesSearch = item.name
|
|
73
|
+
.toLowerCase()
|
|
74
|
+
.includes(searchQuery.toLowerCase());
|
|
75
|
+
const matchesCategory =
|
|
76
|
+
selectedItemCategory === 'all' || item.type === selectedItemCategory;
|
|
77
|
+
const matchesTier =
|
|
78
|
+
selectedTier === 'all' || String(item.tier) === selectedTier;
|
|
79
|
+
|
|
80
|
+
return matchesSearch && matchesCategory && matchesTier;
|
|
81
|
+
});
|
|
82
|
+
}, [items, searchQuery, selectedItemCategory, selectedTier]);
|
|
80
83
|
|
|
81
84
|
const getDroppedByNPCs = (
|
|
82
85
|
itemId: string,
|
|
@@ -89,34 +92,6 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
|
|
|
89
92
|
);
|
|
90
93
|
};
|
|
91
94
|
|
|
92
|
-
const handleMouseEnter = (
|
|
93
|
-
e: React.MouseEvent,
|
|
94
|
-
item: IInformationCenterItem
|
|
95
|
-
) => {
|
|
96
|
-
if (!isMobile) {
|
|
97
|
-
setTooltipPosition({
|
|
98
|
-
x: e.clientX + TOOLTIP_OFFSET,
|
|
99
|
-
y: e.clientY,
|
|
100
|
-
});
|
|
101
|
-
setHoveredItem(item);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const handleMouseMove = (e: React.MouseEvent) => {
|
|
106
|
-
if (!isMobile && hoveredItem) {
|
|
107
|
-
setTooltipPosition({
|
|
108
|
-
x: e.clientX + TOOLTIP_OFFSET,
|
|
109
|
-
y: e.clientY,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const handleMouseLeave = () => {
|
|
115
|
-
if (!isMobile) {
|
|
116
|
-
setHoveredItem(null);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
95
|
const handleTouchStart = (
|
|
121
96
|
e: React.TouchEvent,
|
|
122
97
|
item: IInformationCenterItem
|
|
@@ -127,7 +102,6 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
|
|
|
127
102
|
|
|
128
103
|
const handleItemClick = (item: IInformationCenterItem) => {
|
|
129
104
|
setSelectedItem(item);
|
|
130
|
-
setHoveredItem(null);
|
|
131
105
|
};
|
|
132
106
|
|
|
133
107
|
const handleSearchChange = (newQuery: string) => {
|
|
@@ -144,60 +118,88 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
|
|
|
144
118
|
spriteKey={item.texturePath}
|
|
145
119
|
atlasJSON={itemsAtlasJSON}
|
|
146
120
|
atlasIMG={itemsAtlasIMG}
|
|
147
|
-
onMouseEnter={e => handleMouseEnter(
|
|
148
|
-
onMouseMove={handleMouseMove}
|
|
121
|
+
onMouseEnter={e => handleMouseEnter(item, e)}
|
|
149
122
|
onMouseLeave={handleMouseLeave}
|
|
150
123
|
onTouchStart={e => handleTouchStart(e, item)}
|
|
151
124
|
onClick={() => handleItemClick(item)}
|
|
152
125
|
/>
|
|
153
126
|
);
|
|
154
127
|
|
|
128
|
+
const SearchBarRightElement = (
|
|
129
|
+
<SearchBarActions>
|
|
130
|
+
<ItemsAdvancedFilters
|
|
131
|
+
isOpen={isAdvancedFiltersOpen}
|
|
132
|
+
onToggle={() => setIsAdvancedFiltersOpen(!isAdvancedFiltersOpen)}
|
|
133
|
+
onTierChange={setSelectedTier}
|
|
134
|
+
onTypeChange={setSelectedItemCategory}
|
|
135
|
+
selectedTier={selectedTier}
|
|
136
|
+
selectedType={selectedItemCategory}
|
|
137
|
+
/>
|
|
138
|
+
</SearchBarActions>
|
|
139
|
+
);
|
|
140
|
+
|
|
155
141
|
return (
|
|
156
142
|
<>
|
|
157
143
|
<PaginatedContent<IInformationCenterItem>
|
|
158
144
|
items={filteredItems}
|
|
159
145
|
renderItem={renderItem}
|
|
160
146
|
emptyMessage="No items found"
|
|
161
|
-
filterOptions={{
|
|
162
|
-
options: itemCategoryOptions,
|
|
163
|
-
selectedOption: selectedItemCategory,
|
|
164
|
-
onOptionChange: setSelectedItemCategory,
|
|
165
|
-
}}
|
|
166
147
|
searchOptions={{
|
|
167
148
|
value: searchQuery,
|
|
168
149
|
onChange: handleSearchChange,
|
|
169
150
|
placeholder: 'Search items...',
|
|
151
|
+
rightElement: SearchBarRightElement,
|
|
170
152
|
}}
|
|
171
|
-
dependencies={[selectedItemCategory]}
|
|
153
|
+
dependencies={[selectedItemCategory, selectedTier]}
|
|
172
154
|
tabId={tabId}
|
|
173
155
|
layout="grid"
|
|
174
156
|
itemHeight="180px"
|
|
175
157
|
/>
|
|
176
|
-
{!isMobile &&
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
158
|
+
{!isMobile && tooltipState && tooltipState.item && (
|
|
159
|
+
<Portal>
|
|
160
|
+
<TooltipWrapper
|
|
161
|
+
width={TOOLTIP_WIDTH}
|
|
162
|
+
style={{
|
|
163
|
+
position: 'fixed',
|
|
164
|
+
left: `${tooltipState.position.x}px`,
|
|
165
|
+
top: `${tooltipState.position.y}px`,
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
<InformationCenterItemTooltip item={tooltipState.item} />
|
|
169
|
+
</TooltipWrapper>
|
|
170
|
+
</Portal>
|
|
182
171
|
)}
|
|
183
172
|
{selectedItem && (
|
|
184
173
|
<Portal>
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
174
|
+
<BaseInformationDetails
|
|
175
|
+
name={selectedItem.name}
|
|
176
|
+
spriteKey={selectedItem.texturePath}
|
|
177
|
+
atlasJSON={itemsAtlasJSON}
|
|
178
|
+
atlasIMG={itemsAtlasIMG}
|
|
190
179
|
onBack={() => setSelectedItem(null)}
|
|
191
|
-
|
|
180
|
+
>
|
|
181
|
+
<InformationCenterItemDetails
|
|
182
|
+
item={selectedItem}
|
|
183
|
+
itemsAtlasJSON={itemsAtlasJSON}
|
|
184
|
+
itemsAtlasIMG={itemsAtlasIMG}
|
|
185
|
+
droppedBy={getDroppedByNPCs(selectedItem.key, bestiaryItems)}
|
|
186
|
+
onBack={() => setSelectedItem(null)}
|
|
187
|
+
/>
|
|
188
|
+
</BaseInformationDetails>
|
|
192
189
|
</Portal>
|
|
193
190
|
)}
|
|
194
191
|
</>
|
|
195
192
|
);
|
|
196
193
|
};
|
|
197
194
|
|
|
198
|
-
const TooltipWrapper = styled.div
|
|
199
|
-
position: fixed;
|
|
195
|
+
const TooltipWrapper = styled.div<{ width: number }>`
|
|
200
196
|
z-index: 1000;
|
|
201
197
|
pointer-events: none;
|
|
202
|
-
|
|
198
|
+
width: ${props => `${props.width}px`};
|
|
199
|
+
`;
|
|
200
|
+
|
|
201
|
+
const SearchBarActions = styled.div`
|
|
202
|
+
display: flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
gap: 0.5rem;
|
|
203
205
|
`;
|