ps99-api 2.3.3 → 2.5.0
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/.github/workflows/release-on-main.yml +1 -2
- package/.idea/node-ps99-api.iml +1 -0
- package/.idea/runConfigurations/test_changing.xml +1 -1
- package/README.md +1 -1
- package/debug_currency.json +57 -0
- package/debug_goals.json +271 -0
- package/dist/ps99-api.d.ts +2 -0
- package/dist/ps99-api.js +4 -1
- package/dist/ps99-api.js.map +1 -1
- package/dist/request-client/axios.js +6 -1
- package/dist/request-client/axios.js.map +1 -1
- package/dist/responses/collection/achievement.d.ts +2 -0
- package/dist/responses/collection/guild-battle.d.ts +2 -4
- package/dist/responses/collection/index.d.ts +1 -0
- package/dist/responses/collection/index.js +15 -0
- package/dist/responses/collection/index.js.map +1 -1
- package/dist/responses/collection/rank.d.ts +1 -0
- package/dist/responses/collection/rarity.d.ts +1 -0
- package/dist/responses/collection/seed.d.ts +1 -0
- package/dist/responses/exists.d.ts +2 -0
- package/example-web/react/package-lock.json +1504 -1470
- package/example-web/react2/package-lock.json +3082 -2759
- package/example-web/react2/package.json +6 -1
- package/example-web/react2/public/assets/gold_variant_icon.png +0 -0
- package/example-web/react2/public/assets/hot_cocoa_egg.png +0 -0
- package/example-web/react2/public/index.html +34 -31
- package/example-web/react2/src/App.tsx +6 -9
- package/example-web/react2/src/assets/guild_placeholder.png +0 -0
- package/example-web/react2/src/components/AchievementsComponent.tsx +78 -30
- package/example-web/react2/src/components/BoostsComponent.tsx +18 -14
- package/example-web/react2/src/components/BoothsComponent.tsx +24 -22
- package/example-web/react2/src/components/BoxesComponent.tsx +46 -21
- package/example-web/react2/src/components/BuffsComponent.tsx +83 -13
- package/example-web/react2/src/components/CharmsComponent.tsx +47 -29
- package/example-web/react2/src/components/CollectionConfigIndex.tsx +398 -35
- package/example-web/react2/src/components/CollectionsIndex.tsx +132 -23
- package/example-web/react2/src/components/CollectionsLayout.tsx +50 -0
- package/example-web/react2/src/components/CurrencyComponent.tsx +59 -50
- package/example-web/react2/src/components/DynamicCollectionConfigData.tsx +178 -11
- package/example-web/react2/src/components/EggsComponent.tsx +77 -44
- package/example-web/react2/src/components/EnchantsComponent.tsx +84 -34
- package/example-web/react2/src/components/FishingRodsComponent.tsx +38 -31
- package/example-web/react2/src/components/Footer.tsx +75 -18
- package/example-web/react2/src/components/FruitsComponent.tsx +41 -25
- package/example-web/react2/src/components/GenericFetchComponent.tsx +40 -22
- package/example-web/react2/src/components/GuildBattlesComponent.tsx +93 -65
- package/example-web/react2/src/components/Header.tsx +5 -37
- package/example-web/react2/src/components/HomePage.tsx +16 -16
- package/example-web/react2/src/components/HoverboardsComponent.tsx +25 -37
- package/example-web/react2/src/components/ImageComponent.tsx +255 -45
- package/example-web/react2/src/components/ItemCard.tsx +240 -0
- package/example-web/react2/src/components/LootboxesComponent.tsx +24 -16
- package/example-web/react2/src/components/MasteryComponent.tsx +93 -37
- package/example-web/react2/src/components/MerchantsComponent.tsx +46 -28
- package/example-web/react2/src/components/MiscItemsComponent.tsx +28 -26
- package/example-web/react2/src/components/PetsComponent.tsx +115 -46
- package/example-web/react2/src/components/PotionsComponent.tsx +53 -36
- package/example-web/react2/src/components/RandomEventsComponent.tsx +39 -35
- package/example-web/react2/src/components/RanksComponent.tsx +187 -71
- package/example-web/react2/src/components/RarityComponent.tsx +124 -13
- package/example-web/react2/src/components/RebirthsComponent.tsx +37 -26
- package/example-web/react2/src/components/SecretRoomsComponent.tsx +7 -13
- package/example-web/react2/src/components/SeedsComponent.tsx +45 -32
- package/example-web/react2/src/components/ShovelsComponent.tsx +23 -18
- package/example-web/react2/src/components/Sidebar.tsx +105 -0
- package/example-web/react2/src/components/SprinklersComponent.tsx +27 -19
- package/example-web/react2/src/components/Tooltip.tsx +36 -0
- package/example-web/react2/src/components/UltimatesComponent.tsx +29 -24
- package/example-web/react2/src/components/UpgradesComponent.tsx +99 -55
- package/example-web/react2/src/components/WateringCansComponent.tsx +23 -21
- package/example-web/react2/src/components/WorldsComponent.tsx +27 -24
- package/example-web/react2/src/components/XPPotionsComponent.tsx +29 -19
- package/example-web/react2/src/components/ZoneFlagsComponent.tsx +27 -23
- package/example-web/react2/src/components/ZonesComponent.tsx +56 -73
- package/example-web/react2/src/constants/collectionIcons.ts +29 -0
- package/example-web/react2/src/context/CollectionDataContext.tsx +62 -0
- package/example-web/react2/src/hooks/useExpandableList.ts +38 -0
- package/example-web/react2/src/hooks/useItemResolution.ts +351 -0
- package/example-web/react2/src/index.css +257 -0
- package/example-web/react2/src/index.tsx +2 -1
- package/example-web/react2/temp_model.rbxm +0 -0
- package/example-web/react2/webpack.config.js +103 -47
- package/package.json +11 -11
- package/ranks.json +1 -0
- package/repro_collection_fetch.ts +33 -0
- package/repro_image_fetch.ts +50 -0
- package/src/__tests__/__snapshots__/ps99-api-changes.ts.snap +34841 -10439
- package/src/__tests__/__snapshots__/ps99-api-live.ts.snap +160667 -67217
- package/src/ps99-api.ts +9 -5
- package/src/request-client/axios.ts +6 -2
- package/src/responses/collection/achievement.ts +2 -0
- package/src/responses/collection/guild-battle.ts +2 -4
- package/src/responses/collection/index.ts +1 -0
- package/src/responses/collection/rank.ts +1 -0
- package/src/responses/collection/rarity.ts +1 -0
- package/src/responses/collection/seed.ts +1 -0
- package/src/responses/exists.ts +2 -0
- package/tsconfig.json +1 -1
- package/example-web/react2/public/service-worker.js +0 -63
|
@@ -1,49 +1,412 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
import React, { useEffect, useState } from "react";
|
|
2
|
-
import { useParams, Link } from "react-router-dom";
|
|
3
|
-
import
|
|
3
|
+
import { useParams, Link, useNavigate } from "react-router-dom";
|
|
4
|
+
import Tooltip from "./Tooltip";
|
|
5
|
+
import { PetSimulator99API, Collections, CollectionName } from "ps99-api";
|
|
6
|
+
import ItemCard from "./ItemCard";
|
|
7
|
+
import DynamicCollectionConfigData from "./DynamicCollectionConfigData";
|
|
8
|
+
import ImageComponent from "./ImageComponent";
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
import { useItemResolution } from "../hooks/useItemResolution";
|
|
11
|
+
import { useCollectionData } from "../context/CollectionDataContext";
|
|
12
|
+
import { Grid as FixedSizeGrid } from "react-window";
|
|
13
|
+
import { AutoSizer } from "react-virtualized-auto-sizer/dist/react-virtualized-auto-sizer.cjs";
|
|
14
|
+
import ItemCard from "./ItemCard";
|
|
9
15
|
|
|
16
|
+
const GridCellRenderer = ({ columnIndex, rowIndex, style, items, columnCount, navigate, collectionName, variantFilter, shinyFilter, resolveIcon, GAP }: any) => {
|
|
17
|
+
const index = rowIndex * columnCount + columnIndex;
|
|
18
|
+
if (index >= items.length) return null;
|
|
19
|
+
const item = items[index];
|
|
20
|
+
const itemConfig = (item as any).configData || item;
|
|
21
|
+
const icon = resolveIcon(itemConfig);
|
|
22
|
+
const itemDataWithIcon = { ...itemConfig, icon };
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div style={style}>
|
|
26
|
+
<div style={{
|
|
27
|
+
position: 'absolute',
|
|
28
|
+
top: GAP / 2,
|
|
29
|
+
left: GAP / 2,
|
|
30
|
+
right: GAP / 2,
|
|
31
|
+
bottom: GAP / 2,
|
|
32
|
+
// Using explicit specific size if needed, but absolute positioning with insets works well for gaps
|
|
33
|
+
}}>
|
|
34
|
+
<div
|
|
35
|
+
onClick={() => navigate(`/collections/${collectionName}/${item.configName}`)}
|
|
36
|
+
style={{ cursor: "pointer", height: '100%' }}
|
|
37
|
+
>
|
|
38
|
+
<ItemCard
|
|
39
|
+
id={item.configName}
|
|
40
|
+
amount={""}
|
|
41
|
+
label={itemConfig.DisplayName || itemConfig.name || item.configName}
|
|
42
|
+
itemData={itemDataWithIcon}
|
|
43
|
+
rarityColor={itemConfig.rarity?.Color || (itemConfig.Rarity?.Color)}
|
|
44
|
+
variant={collectionName === "Pets" ? (variantFilter as any) : undefined}
|
|
45
|
+
shiny={collectionName === "Pets" ? shinyFilter : undefined}
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
interface CollectionConfigIndexProps { }
|
|
56
|
+
|
|
57
|
+
const CollectionConfigIndex: React.FC<CollectionConfigIndexProps> = () => {
|
|
58
|
+
const { collectionName } = useParams<{ collectionName: string }>();
|
|
59
|
+
const navigate = useNavigate();
|
|
60
|
+
// Call the hook at the top level so we can use it
|
|
61
|
+
const { resolveIcon } = useItemResolution();
|
|
62
|
+
|
|
63
|
+
const { data, fetchCollection, isLoading } = useCollectionData();
|
|
64
|
+
|
|
65
|
+
const items = (data[collectionName || ""] || []) as Collections[];
|
|
66
|
+
const loading = isLoading(collectionName || "");
|
|
67
|
+
|
|
68
|
+
const [searchTerm, setSearchTerm] = useState<string>("");
|
|
69
|
+
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
|
70
|
+
|
|
71
|
+
// State for Pets Filter
|
|
72
|
+
const [specialFilter, setSpecialFilter] = useState<"H" | "T" | "G" | null>(null);
|
|
73
|
+
const [variantFilter, setVariantFilter] = useState<"Normal" | "Golden" | "Rainbow">("Normal");
|
|
74
|
+
const [shinyFilter, setShinyFilter] = useState<boolean>(false);
|
|
75
|
+
|
|
76
|
+
// Responsive check
|
|
10
77
|
useEffect(() => {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const api = new PetSimulator99API();
|
|
15
|
-
const response = await api.getCollection(collectionName);
|
|
16
|
-
|
|
17
|
-
if (response.status === "ok") {
|
|
18
|
-
const names = response.data.map((item) => item.configName);
|
|
19
|
-
setConfigNames(names);
|
|
20
|
-
} else {
|
|
21
|
-
setError(response.error.message);
|
|
22
|
-
}
|
|
78
|
+
const handleResize = () => {
|
|
79
|
+
setWindowWidth(window.innerWidth);
|
|
23
80
|
};
|
|
81
|
+
window.addEventListener("resize", handleResize);
|
|
82
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const isMobile = windowWidth < 768;
|
|
24
86
|
|
|
25
|
-
|
|
26
|
-
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (collectionName) {
|
|
89
|
+
fetchCollection(collectionName as CollectionName);
|
|
90
|
+
}
|
|
91
|
+
}, [collectionName, fetchCollection]);
|
|
27
92
|
|
|
28
|
-
|
|
29
|
-
|
|
93
|
+
// Loading State
|
|
94
|
+
if (loading) {
|
|
95
|
+
return (
|
|
96
|
+
<div
|
|
97
|
+
style={{
|
|
98
|
+
display: "flex",
|
|
99
|
+
justifyContent: "center",
|
|
100
|
+
alignItems: "center",
|
|
101
|
+
height: "50vh",
|
|
102
|
+
fontSize: "1.5rem",
|
|
103
|
+
fontWeight: "bold",
|
|
104
|
+
color: "#666",
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
Loading {collectionName}...
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Process items for rendering
|
|
113
|
+
const processedItems = items.map((item) => {
|
|
114
|
+
const configData: any = (item as any).configData || item;
|
|
115
|
+
return {
|
|
116
|
+
...item,
|
|
117
|
+
configData,
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const filteredItems = processedItems.filter((item) => {
|
|
122
|
+
const config = item.configData;
|
|
123
|
+
const name =
|
|
124
|
+
config.DisplayName || config.name || item.configName || "";
|
|
125
|
+
if (
|
|
126
|
+
searchTerm &&
|
|
127
|
+
!name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
128
|
+
) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
// Apply Pet Filters
|
|
136
|
+
const finalItems = filteredItems.filter((item) => {
|
|
137
|
+
if (collectionName !== "Pets") return true;
|
|
138
|
+
|
|
139
|
+
const config = item.configData;
|
|
140
|
+
const name = config.DisplayName || config.name || item.configName || "";
|
|
141
|
+
|
|
142
|
+
// Special Filter
|
|
143
|
+
if (specialFilter === "H") {
|
|
144
|
+
if (!name.startsWith("Huge ")) return false;
|
|
145
|
+
} else if (specialFilter === "T") {
|
|
146
|
+
if (!name.startsWith("Titanic ")) return false;
|
|
147
|
+
} else if (specialFilter === "G") {
|
|
148
|
+
if (!name.startsWith("Gargantuan ")) return false;
|
|
149
|
+
} else {
|
|
150
|
+
// None selected: Exclude Huge, Titanic, Gargantuan
|
|
151
|
+
if (name.startsWith("Huge ") || name.startsWith("Titanic ") || name.startsWith("Gargantuan ")) return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return true;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
interface ItemData {
|
|
158
|
+
items: typeof finalItems;
|
|
159
|
+
columnCount?: number;
|
|
160
|
+
navigate: any;
|
|
161
|
+
collectionName: string | undefined;
|
|
162
|
+
variantFilter: string;
|
|
163
|
+
shinyFilter: boolean;
|
|
164
|
+
resolveIcon: (item: any) => string | null;
|
|
165
|
+
GAP: number;
|
|
30
166
|
}
|
|
31
167
|
|
|
32
168
|
return (
|
|
33
|
-
<div
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
169
|
+
<div
|
|
170
|
+
style={{
|
|
171
|
+
flex: 1,
|
|
172
|
+
display: "flex",
|
|
173
|
+
flexDirection: "column",
|
|
174
|
+
overflow: "hidden",
|
|
175
|
+
backgroundColor: "#ffffff",
|
|
176
|
+
height: "100%", // ensure it fills parent
|
|
177
|
+
}}
|
|
178
|
+
>
|
|
179
|
+
{/* Header Bar inside Window */}
|
|
180
|
+
<div
|
|
181
|
+
style={{
|
|
182
|
+
padding: "15px 20px",
|
|
183
|
+
borderBottom: "4px solid #333",
|
|
184
|
+
display: "flex",
|
|
185
|
+
alignItems: "center",
|
|
186
|
+
gap: "15px",
|
|
187
|
+
backgroundColor: "#fff",
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
<h2
|
|
191
|
+
style={{
|
|
192
|
+
margin: 0,
|
|
193
|
+
fontSize: "2.5rem",
|
|
194
|
+
fontWeight: "900",
|
|
195
|
+
color: "#333",
|
|
196
|
+
textShadow: "3px 3px 0px #eee",
|
|
197
|
+
marginRight: "auto",
|
|
198
|
+
fontFamily: "'Fredoka One', cursive, sans-serif",
|
|
199
|
+
letterSpacing: "1px",
|
|
200
|
+
}}
|
|
201
|
+
>
|
|
202
|
+
{collectionName}!
|
|
203
|
+
</h2>
|
|
204
|
+
|
|
205
|
+
{/* Filter Ribbon (Pets Only) */}
|
|
206
|
+
{collectionName === "Pets" && (
|
|
207
|
+
<div style={{ display: "flex", gap: "8px", marginRight: "20px" }}>
|
|
208
|
+
{/* Specials: H, T, G */}
|
|
209
|
+
{['H', 'T', 'G'].map((symbol) => (
|
|
210
|
+
<button
|
|
211
|
+
key={symbol}
|
|
212
|
+
onClick={() => setSpecialFilter(specialFilter === symbol ? null : symbol as any)}
|
|
213
|
+
style={{
|
|
214
|
+
width: "36px",
|
|
215
|
+
height: "36px",
|
|
216
|
+
borderRadius: "50%",
|
|
217
|
+
border: "3px solid #333",
|
|
218
|
+
backgroundColor: specialFilter === symbol ? "#333" : "#fff",
|
|
219
|
+
color: specialFilter === symbol ? "#fff" : "#333",
|
|
220
|
+
fontSize: "1.2rem",
|
|
221
|
+
fontWeight: "bold",
|
|
222
|
+
cursor: "pointer",
|
|
223
|
+
display: "flex",
|
|
224
|
+
alignItems: "center",
|
|
225
|
+
justifyContent: "center",
|
|
226
|
+
boxShadow: "0 3px 0 #ccc",
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
{symbol}
|
|
230
|
+
</button>
|
|
231
|
+
))}
|
|
232
|
+
|
|
233
|
+
{/* Variants: Normal, Golden, Rainbow */}
|
|
234
|
+
{[
|
|
235
|
+
{ symbol: '🐾', mode: 'Normal', color: '#fff' },
|
|
236
|
+
{ symbol: '★', mode: 'Golden', color: '#FFD700' },
|
|
237
|
+
{ symbol: '🌈', mode: 'Rainbow', color: 'inherit' }
|
|
238
|
+
].map(({ symbol, mode, color: iconColor }) => {
|
|
239
|
+
const isActive = variantFilter === mode;
|
|
240
|
+
const isRainbow = mode === 'Rainbow';
|
|
241
|
+
const isGold = mode === 'Golden';
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<button
|
|
245
|
+
key={mode}
|
|
246
|
+
onClick={() => setVariantFilter(mode as any)}
|
|
247
|
+
className={(isActive && isRainbow) ? "sheen-effect" : ""}
|
|
248
|
+
style={{
|
|
249
|
+
width: "48px",
|
|
250
|
+
height: "48px",
|
|
251
|
+
borderRadius: "50%",
|
|
252
|
+
border: "3px solid #333",
|
|
253
|
+
backgroundColor: isActive
|
|
254
|
+
? (mode === 'Normal' ? "#e0e0e0" : "#333")
|
|
255
|
+
: "#fff",
|
|
256
|
+
color: isActive
|
|
257
|
+
? (mode === 'Normal' ? "#333" : "#fff")
|
|
258
|
+
: "#333",
|
|
259
|
+
fontSize: "1.2rem",
|
|
260
|
+
fontWeight: "bold",
|
|
261
|
+
cursor: "pointer",
|
|
262
|
+
display: "flex",
|
|
263
|
+
alignItems: "center",
|
|
264
|
+
justifyContent: "center",
|
|
265
|
+
boxShadow: "0 3px 0 #ccc",
|
|
266
|
+
filter: "none",
|
|
267
|
+
position: 'relative',
|
|
268
|
+
overflow: 'hidden',
|
|
269
|
+
padding: 0
|
|
270
|
+
}}
|
|
271
|
+
title={mode}
|
|
272
|
+
>
|
|
273
|
+
{isGold ? (
|
|
274
|
+
<img
|
|
275
|
+
src="./assets/gold_variant_icon.png"
|
|
276
|
+
alt="Gold"
|
|
277
|
+
style={{
|
|
278
|
+
width: '70%',
|
|
279
|
+
height: '70%',
|
|
280
|
+
objectFit: 'contain',
|
|
281
|
+
filter: isActive ? 'none' : 'grayscale(1) opacity(0.5)'
|
|
282
|
+
}}
|
|
283
|
+
/>
|
|
284
|
+
) : (
|
|
285
|
+
<span style={{
|
|
286
|
+
filter: mode === 'Golden' && !isActive ? "grayscale(1)" : "none",
|
|
287
|
+
color: (isActive && mode !== 'Rainbow' && mode !== 'Normal') ? iconColor : 'inherit'
|
|
288
|
+
}}>
|
|
289
|
+
{symbol}
|
|
290
|
+
</span>
|
|
291
|
+
)}
|
|
292
|
+
</button>
|
|
293
|
+
)
|
|
294
|
+
})}
|
|
295
|
+
|
|
296
|
+
{/* Shiny Toggle */}
|
|
297
|
+
{/* Shiny Toggle */}
|
|
298
|
+
<button
|
|
299
|
+
onClick={() => setShinyFilter(!shinyFilter)}
|
|
300
|
+
className={shinyFilter ? "sheen-effect" : ""}
|
|
301
|
+
style={{
|
|
302
|
+
width: "48px",
|
|
303
|
+
height: "48px",
|
|
304
|
+
borderRadius: "50%",
|
|
305
|
+
border: "3px solid #333",
|
|
306
|
+
backgroundColor: shinyFilter ? "#333" : "#fff",
|
|
307
|
+
color: shinyFilter ? "#fff" : "#333",
|
|
308
|
+
fontSize: "1.2rem",
|
|
309
|
+
fontWeight: "bold",
|
|
310
|
+
cursor: "pointer",
|
|
311
|
+
display: "flex",
|
|
312
|
+
alignItems: "center",
|
|
313
|
+
justifyContent: "center",
|
|
314
|
+
boxShadow: "0 3px 0 #ccc",
|
|
315
|
+
position: 'relative',
|
|
316
|
+
overflow: 'hidden'
|
|
317
|
+
}}
|
|
40
318
|
>
|
|
41
|
-
|
|
42
|
-
</
|
|
43
|
-
</
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
319
|
+
✨
|
|
320
|
+
</button>
|
|
321
|
+
</div>
|
|
322
|
+
)}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
{/* Search Bar */}
|
|
327
|
+
<div style={{ position: "relative" }}>
|
|
328
|
+
<input
|
|
329
|
+
type="text"
|
|
330
|
+
placeholder="Search"
|
|
331
|
+
value={searchTerm}
|
|
332
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
333
|
+
style={{
|
|
334
|
+
padding: "10px 20px",
|
|
335
|
+
borderRadius: "50px",
|
|
336
|
+
border: "3px solid #ccc",
|
|
337
|
+
outline: "none",
|
|
338
|
+
fontSize: "1.2rem",
|
|
339
|
+
width: "200px",
|
|
340
|
+
fontWeight: "800",
|
|
341
|
+
backgroundColor: "#fff",
|
|
342
|
+
color: "#ccc",
|
|
343
|
+
textAlign: "right"
|
|
344
|
+
}}
|
|
345
|
+
/>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
{/* Red Close Button */}
|
|
349
|
+
<button
|
|
350
|
+
onClick={() => navigate("/collections")}
|
|
351
|
+
style={{
|
|
352
|
+
width: "48px",
|
|
353
|
+
height: "48px",
|
|
354
|
+
borderRadius: "12px",
|
|
355
|
+
backgroundColor: "#ff0055", // Hot pink/red
|
|
356
|
+
color: "white",
|
|
357
|
+
border: "4px solid #900", // Dark red border
|
|
358
|
+
fontSize: "24px",
|
|
359
|
+
fontWeight: "900",
|
|
360
|
+
display: "flex",
|
|
361
|
+
alignItems: "center",
|
|
362
|
+
justifyContent: "center",
|
|
363
|
+
cursor: "pointer",
|
|
364
|
+
boxShadow: "inset 0 4px 4px rgba(255,255,255,0.4), 0 4px 0 #500",
|
|
365
|
+
}}
|
|
366
|
+
>
|
|
367
|
+
X
|
|
368
|
+
</button>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
{/* Virtualized and AutoSized Content */}
|
|
372
|
+
<div style={{ height: "calc(100vh - 80px)", width: "100%" }}>
|
|
373
|
+
{/* @ts-ignore */}
|
|
374
|
+
<AutoSizer style={{ width: "100%", height: "100%" }} renderProp={({ height, width }: { height: number; width: number }) => {
|
|
375
|
+
if (!height || !width) return <div style={{ color: 'red' }}>AutoSizer returned 0 dimensions</div>;
|
|
376
|
+
const GAP = 10;
|
|
377
|
+
const MIN_COL_WIDTH = 150;
|
|
378
|
+
|
|
379
|
+
const columnCount = Math.floor(width / MIN_COL_WIDTH) || 1;
|
|
380
|
+
const columnWidth = width / columnCount;
|
|
381
|
+
const rowHeight = 220; // Increased to ensure no cutoff
|
|
382
|
+
const rowCount = Math.ceil(finalItems.length / columnCount);
|
|
383
|
+
|
|
384
|
+
return (
|
|
385
|
+
// @ts-ignore
|
|
386
|
+
<FixedSizeGrid
|
|
387
|
+
columnCount={columnCount}
|
|
388
|
+
columnWidth={columnWidth}
|
|
389
|
+
height={height}
|
|
390
|
+
rowCount={rowCount}
|
|
391
|
+
rowHeight={rowHeight}
|
|
392
|
+
width={width}
|
|
393
|
+
cellComponent={GridCellRenderer}
|
|
394
|
+
cellProps={{
|
|
395
|
+
items: finalItems,
|
|
396
|
+
columnCount,
|
|
397
|
+
navigate,
|
|
398
|
+
collectionName,
|
|
399
|
+
variantFilter,
|
|
400
|
+
shinyFilter,
|
|
401
|
+
resolveIcon,
|
|
402
|
+
GAP
|
|
403
|
+
}}
|
|
404
|
+
/>
|
|
405
|
+
);
|
|
406
|
+
}} />
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
</div >
|
|
47
410
|
);
|
|
48
411
|
};
|
|
49
412
|
|
|
@@ -1,33 +1,142 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { Link } from "react-router-dom";
|
|
3
3
|
import { PetSimulator99API, CollectionName } from "ps99-api";
|
|
4
|
+
import { COLLECTION_ICONS } from "../constants/collectionIcons";
|
|
4
5
|
|
|
5
6
|
const CollectionsIndex: React.FC = () => {
|
|
6
|
-
|
|
7
|
+
const [collections, setCollections] = useState<CollectionName[]>([]);
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const fetchCollections = async () => {
|
|
11
|
+
const api = new PetSimulator99API();
|
|
12
|
+
const response = await api.getCollections();
|
|
13
|
+
if (response.status === "ok") {
|
|
14
|
+
setCollections(response.data);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
fetchCollections();
|
|
18
|
+
}, []);
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
style={{
|
|
23
|
+
display: "flex",
|
|
24
|
+
justifyContent: "center",
|
|
25
|
+
alignItems: "center",
|
|
26
|
+
minHeight: "calc(100vh - 140px)", // Matches CollectionsLayout outer container spacing
|
|
27
|
+
padding: "20px",
|
|
28
|
+
// backgroundColor: "rgba(0,0,0,0.05)", // REMOVED
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<div style={{
|
|
32
|
+
width: "100%",
|
|
33
|
+
maxWidth: "1200px", // Match CollectionsLayout
|
|
34
|
+
height: "80vh", // Match CollectionsLayout
|
|
35
|
+
backgroundColor: "#fff",
|
|
36
|
+
borderRadius: "24px",
|
|
37
|
+
boxShadow: "0 10px 40px rgba(0,0,0,0.2)",
|
|
38
|
+
overflow: "hidden", // Important for internal scrolling
|
|
39
|
+
position: "relative",
|
|
40
|
+
border: "4px solid #333",
|
|
41
|
+
display: "flex",
|
|
42
|
+
flexDirection: "column",
|
|
43
|
+
}}>
|
|
44
|
+
<div style={{ position: "relative", textAlign: "center", margin: "20px 0" }}>
|
|
45
|
+
<h1 style={{
|
|
46
|
+
fontFamily: "'Fredoka One', cursive, sans-serif",
|
|
47
|
+
fontSize: "3rem",
|
|
48
|
+
color: "#333",
|
|
49
|
+
margin: 0,
|
|
50
|
+
textShadow: "4px 4px 0px #eee",
|
|
51
|
+
display: "inline-block",
|
|
52
|
+
}}>
|
|
53
|
+
Select a Collection
|
|
54
|
+
</h1>
|
|
55
|
+
|
|
56
|
+
{/* Home Button (X) */}
|
|
57
|
+
<Link
|
|
58
|
+
to="/"
|
|
59
|
+
style={{
|
|
60
|
+
position: "absolute",
|
|
61
|
+
right: "20px",
|
|
62
|
+
top: "50%",
|
|
63
|
+
transform: "translateY(-50%)",
|
|
64
|
+
width: "48px",
|
|
65
|
+
height: "48px",
|
|
66
|
+
borderRadius: "12px",
|
|
67
|
+
backgroundColor: "#ff0055",
|
|
68
|
+
color: "white",
|
|
69
|
+
border: "4px solid #900",
|
|
70
|
+
fontSize: "24px",
|
|
71
|
+
fontWeight: "900",
|
|
72
|
+
display: "flex",
|
|
73
|
+
alignItems: "center",
|
|
74
|
+
justifyContent: "center",
|
|
75
|
+
textDecoration: "none",
|
|
76
|
+
boxShadow: "inset 0 4px 4px rgba(255,255,255,0.4), 0 4px 0 #500",
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
X
|
|
80
|
+
</Link>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div style={{
|
|
84
|
+
display: "grid",
|
|
85
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))",
|
|
86
|
+
gap: "20px",
|
|
87
|
+
padding: "0 40px 40px 40px",
|
|
88
|
+
overflowY: "auto", // Allow grid to scroll INSIDE the box
|
|
89
|
+
flex: 1,
|
|
90
|
+
}}>
|
|
91
|
+
{collections.map((collection, index) => {
|
|
92
|
+
const icon = COLLECTION_ICONS[collection] || "📦";
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Link
|
|
96
|
+
to={`/collections/${collection}`}
|
|
97
|
+
key={index}
|
|
98
|
+
style={{
|
|
99
|
+
display: "flex",
|
|
100
|
+
flexDirection: "column",
|
|
101
|
+
alignItems: "center",
|
|
102
|
+
justifyContent: "center",
|
|
103
|
+
backgroundColor: "#fff",
|
|
104
|
+
borderRadius: "24px",
|
|
105
|
+
padding: "15px",
|
|
106
|
+
textDecoration: "none",
|
|
107
|
+
border: "4px solid #333",
|
|
108
|
+
boxShadow: "0 6px 0 #ccc",
|
|
109
|
+
transition: "transform 0.1s ease, box-shadow 0.1s ease",
|
|
110
|
+
aspectRatio: "1/1",
|
|
111
|
+
}}
|
|
112
|
+
onMouseEnter={(e) => {
|
|
113
|
+
e.currentTarget.style.transform = "translateY(4px)";
|
|
114
|
+
e.currentTarget.style.boxShadow = "0 2px 0 #ccc";
|
|
115
|
+
}}
|
|
116
|
+
onMouseLeave={(e) => {
|
|
117
|
+
e.currentTarget.style.transform = "translateY(0)";
|
|
118
|
+
e.currentTarget.style.boxShadow = "0 6px 0 #ccc";
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
<span style={{ fontSize: "3rem", marginBottom: "8px" }}>
|
|
122
|
+
{icon}
|
|
123
|
+
</span>
|
|
124
|
+
<span style={{
|
|
125
|
+
fontSize: "1.2rem",
|
|
126
|
+
fontWeight: "900",
|
|
127
|
+
color: "#333",
|
|
128
|
+
textAlign: "center",
|
|
129
|
+
fontFamily: "'Fredoka One', cursive, sans-serif",
|
|
130
|
+
}}>
|
|
131
|
+
{collection}
|
|
132
|
+
</span>
|
|
133
|
+
</Link>
|
|
134
|
+
);
|
|
135
|
+
})}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
31
140
|
};
|
|
32
141
|
|
|
33
142
|
export default CollectionsIndex;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Outlet } from "react-router-dom";
|
|
3
|
+
// import Sidebar from "./Sidebar"; // REMOVED per user request
|
|
4
|
+
import { useParams } from "react-router-dom";
|
|
5
|
+
import { CollectionDataProvider } from "../context/CollectionDataContext";
|
|
6
|
+
|
|
7
|
+
const CollectionsLayout: React.FC = () => {
|
|
8
|
+
const { collectionName } = useParams<{ collectionName: string }>();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
style={{
|
|
13
|
+
display: "flex",
|
|
14
|
+
justifyContent: "center",
|
|
15
|
+
alignItems: "center",
|
|
16
|
+
minHeight: "calc(100vh - 140px)", // Matches previous main container spacing
|
|
17
|
+
padding: "20px",
|
|
18
|
+
// backgroundColor: "rgba(0,0,0,0.05)", // REMOVED
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
<CollectionDataProvider>
|
|
22
|
+
{/* Main Window Container - Lifted from CollectionConfigIndex */}
|
|
23
|
+
<div
|
|
24
|
+
style={{
|
|
25
|
+
display: "flex",
|
|
26
|
+
width: "100%",
|
|
27
|
+
maxWidth: "1200px",
|
|
28
|
+
height: "80vh",
|
|
29
|
+
backgroundColor: "#fff",
|
|
30
|
+
borderRadius: "24px",
|
|
31
|
+
boxShadow: "0 10px 40px rgba(0,0,0,0.2)",
|
|
32
|
+
overflow: "hidden",
|
|
33
|
+
position: "relative",
|
|
34
|
+
border: "4px solid #333",
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{/* Persistent Sidebar REMOVED */}
|
|
38
|
+
{/* <Sidebar currentCollection={collectionName} /> */}
|
|
39
|
+
|
|
40
|
+
{/* Content Area */}
|
|
41
|
+
<div style={{ flex: 1, overflow: "hidden", display: "flex", flexDirection: "column" }}>
|
|
42
|
+
<Outlet />
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</CollectionDataProvider>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default CollectionsLayout;
|