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