ps99-api 2.4.0 → 2.6.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/runConfigurations/test_changing.xml +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/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/rarity.d.ts +1 -0
- package/example-web/react/package-lock.json +1504 -1470
- package/example-web/react2/package-lock.json +3089 -2766
- 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 +15 -15
- package/example-web/react2/src/assets/guild_placeholder.png +0 -0
- package/example-web/react2/src/components/AchievementsComponent.tsx +74 -19
- package/example-web/react2/src/components/AutoSizer.tsx +49 -0
- package/example-web/react2/src/components/BoostsComponent.tsx +16 -5
- package/example-web/react2/src/components/BoothsComponent.tsx +22 -52
- package/example-web/react2/src/components/BoxesComponent.tsx +48 -16
- package/example-web/react2/src/components/BuffsComponent.tsx +82 -34
- package/example-web/react2/src/components/CharmsComponent.tsx +84 -24
- package/example-web/react2/src/components/CollectionConfigIndex.tsx +867 -33
- package/example-web/react2/src/components/CollectionsIndex.tsx +380 -27
- package/example-web/react2/src/components/CollectionsLayout.tsx +60 -0
- package/example-web/react2/src/components/CurrencyComponent.tsx +57 -39
- package/example-web/react2/src/components/DynamicCollectionConfigData.tsx +172 -15
- package/example-web/react2/src/components/EggsComponent.tsx +50 -12
- package/example-web/react2/src/components/EnchantsComponent.tsx +88 -42
- package/example-web/react2/src/components/FishingRodsComponent.tsx +36 -22
- package/example-web/react2/src/components/Footer.tsx +18 -8
- package/example-web/react2/src/components/FruitsComponent.tsx +40 -17
- package/example-web/react2/src/components/GenericFetchComponent.tsx +9 -1
- package/example-web/react2/src/components/GuildBattlesComponent.tsx +41 -34
- package/example-web/react2/src/components/Header.tsx +39 -52
- package/example-web/react2/src/components/HomePage.tsx +15 -17
- package/example-web/react2/src/components/HoverboardsComponent.tsx +23 -99
- 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 +22 -7
- package/example-web/react2/src/components/MasteryComponent.tsx +165 -30
- package/example-web/react2/src/components/MerchantsComponent.tsx +41 -16
- package/example-web/react2/src/components/MiscItemsComponent.tsx +26 -31
- package/example-web/react2/src/components/PetsComponent.tsx +100 -61
- package/example-web/react2/src/components/PotionsComponent.tsx +121 -27
- package/example-web/react2/src/components/RandomEventsComponent.tsx +32 -23
- package/example-web/react2/src/components/RanksComponent.tsx +187 -62
- package/example-web/react2/src/components/RarityComponent.tsx +123 -5
- package/example-web/react2/src/components/ReactWindowMock.tsx +73 -0
- package/example-web/react2/src/components/RebirthsComponent.tsx +36 -19
- package/example-web/react2/src/components/SecretRoomsComponent.tsx +5 -4
- package/example-web/react2/src/components/SeedsComponent.tsx +41 -21
- package/example-web/react2/src/components/ShovelsComponent.tsx +21 -9
- package/example-web/react2/src/components/Sidebar.tsx +105 -0
- package/example-web/react2/src/components/SprinklersComponent.tsx +25 -10
- package/example-web/react2/src/components/Tooltip.tsx +36 -0
- package/example-web/react2/src/components/UltimatesComponent.tsx +28 -16
- package/example-web/react2/src/components/UpgradesComponent.tsx +97 -47
- package/example-web/react2/src/components/WateringCansComponent.tsx +20 -14
- package/example-web/react2/src/components/WorldsComponent.tsx +21 -11
- package/example-web/react2/src/components/XPPotionsComponent.tsx +28 -11
- package/example-web/react2/src/components/ZoneFlagsComponent.tsx +25 -14
- package/example-web/react2/src/components/ZonesComponent.tsx +43 -60
- 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/context/ScrollContext.tsx +35 -0
- package/example-web/react2/src/hooks/useCollapsibleHeader.ts +69 -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/src/utils/gigantix.ts +40 -0
- 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/index.ts +1 -0
- package/src/responses/collection/rarity.ts +1 -0
- package/tsconfig.json +1 -1
- package/example-web/react2/public/service-worker.js +0 -63
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { lazy, Suspense } from "react";
|
|
2
|
-
import { useParams } from "react-router-dom";
|
|
1
|
+
import React, { lazy, Suspense, useState } from "react";
|
|
2
|
+
import { useParams, useNavigate } from "react-router-dom";
|
|
3
3
|
import { CollectionName, CollectionConfigData } from "ps99-api";
|
|
4
4
|
import { GenericFetchComponent } from "./GenericFetchComponent";
|
|
5
5
|
|
|
@@ -7,6 +7,7 @@ interface DynamicCollectionConfigDataProps {
|
|
|
7
7
|
collectionName?: CollectionName;
|
|
8
8
|
configName?: string;
|
|
9
9
|
render?: (configData: CollectionConfigData<any>) => React.ReactNode; // Add render prop
|
|
10
|
+
additionalProps?: any; // Add additional props to pass to the rendered component
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const DynamicCollectionConfigData: React.FC<
|
|
@@ -17,26 +18,182 @@ const DynamicCollectionConfigData: React.FC<
|
|
|
17
18
|
configName: string;
|
|
18
19
|
}>();
|
|
19
20
|
|
|
21
|
+
const navigate = useNavigate();
|
|
20
22
|
const collectionName = props.collectionName || params.collectionName;
|
|
21
23
|
const configName = props.configName || params.configName;
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
const [viewMode, setViewMode] = useState<'render' | 'schema'>('render');
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* We need to use useMemo here to prevent the component from being re-created
|
|
29
|
+
* on every render. This usage of useMemo is safe because the import path
|
|
30
|
+
* depends only on collectionName, and we want to re-create the component
|
|
31
|
+
* only when collectionName changes.
|
|
32
|
+
*/
|
|
33
|
+
/*
|
|
34
|
+
* We need to use useMemo here to prevent the component from being re-created
|
|
35
|
+
* on every render. This usage of useMemo is safe because the import path
|
|
36
|
+
* depends only on collectionName, and we want to re-create the component
|
|
37
|
+
* only when collectionName changes.
|
|
38
|
+
*/
|
|
39
|
+
const Component = React.useMemo(() => {
|
|
40
|
+
if (!collectionName) return null;
|
|
41
|
+
// Capitalize first letter to match file naming convention (e.g. "pets" -> "Pets")
|
|
42
|
+
// This fixes issues where navigation uses lowercase but files are Capitalized
|
|
43
|
+
const formattedName = collectionName.charAt(0).toUpperCase() + collectionName.slice(1);
|
|
44
|
+
return lazy(() => import(`./${formattedName}Component`));
|
|
45
|
+
}, [collectionName]);
|
|
46
|
+
|
|
47
|
+
if (!collectionName || !configName || !Component) {
|
|
24
48
|
return <div>Invalid collection or config name</div>;
|
|
25
49
|
}
|
|
26
50
|
|
|
27
|
-
const Component = lazy(() => import(`./${collectionName}Component`));
|
|
28
|
-
|
|
29
51
|
return (
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
52
|
+
<div style={{ display: "flex", flexDirection: "column", height: "100%", width: "100%" }}>
|
|
53
|
+
{/* Header with Close Button */}
|
|
54
|
+
<div style={{
|
|
55
|
+
padding: "15px 20px",
|
|
56
|
+
borderBottom: "4px solid #333",
|
|
57
|
+
display: "flex",
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
justifyContent: "space-between",
|
|
60
|
+
backgroundColor: "#fff",
|
|
61
|
+
flexShrink: 0,
|
|
62
|
+
}}>
|
|
63
|
+
<div
|
|
64
|
+
style={{ display: "flex", alignItems: "center", gap: "10px", cursor: "pointer" }}
|
|
65
|
+
onClick={() => {
|
|
66
|
+
navigator.clipboard.writeText(window.location.href);
|
|
67
|
+
// visual feedback could be added here, e.g. a small alert or changing the icon color briefly
|
|
68
|
+
const icon = document.getElementById('link-icon-path');
|
|
69
|
+
if (icon) icon.style.fill = '#4caf50';
|
|
70
|
+
setTimeout(() => { if (icon) icon.style.fill = '#ccc'; }, 1000);
|
|
71
|
+
}}
|
|
72
|
+
title="Click to copy link to this item"
|
|
73
|
+
onMouseEnter={(e) => {
|
|
74
|
+
const icon = e.currentTarget.querySelector('.link-icon');
|
|
75
|
+
if (icon) (icon as HTMLElement).style.opacity = '1';
|
|
76
|
+
}}
|
|
77
|
+
onMouseLeave={(e) => {
|
|
78
|
+
const icon = e.currentTarget.querySelector('.link-icon');
|
|
79
|
+
if (icon) (icon as HTMLElement).style.opacity = '0';
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<h2 style={{
|
|
83
|
+
margin: 0,
|
|
84
|
+
fontSize: "2rem",
|
|
85
|
+
fontWeight: "900",
|
|
86
|
+
color: "#333",
|
|
87
|
+
textShadow: "2px 2px 0px #eee",
|
|
88
|
+
fontFamily: "'Fredoka One', cursive, sans-serif",
|
|
89
|
+
textTransform: "capitalize",
|
|
90
|
+
}}>
|
|
91
|
+
{configName}
|
|
92
|
+
</h2>
|
|
93
|
+
<svg
|
|
94
|
+
className="link-icon"
|
|
95
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
96
|
+
width="24"
|
|
97
|
+
height="24"
|
|
98
|
+
viewBox="0 0 24 24"
|
|
99
|
+
fill="none"
|
|
100
|
+
stroke="#ccc"
|
|
101
|
+
strokeWidth="2"
|
|
102
|
+
strokeLinecap="round"
|
|
103
|
+
strokeLinejoin="round"
|
|
104
|
+
style={{ opacity: 0, transition: 'opacity 0.2s' }}
|
|
105
|
+
>
|
|
106
|
+
<path id="link-icon-path" d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
|
|
107
|
+
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
|
|
108
|
+
</svg>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div style={{ display: 'flex', gap: '10px' }}>
|
|
112
|
+
<button
|
|
113
|
+
onClick={() => setViewMode(prev => prev === 'render' ? 'schema' : 'render')}
|
|
114
|
+
style={{
|
|
115
|
+
width: "48px",
|
|
116
|
+
height: "48px",
|
|
117
|
+
borderRadius: "12px",
|
|
118
|
+
backgroundColor: viewMode === 'render' ? "#2196f3" : "#4caf50",
|
|
119
|
+
color: "white",
|
|
120
|
+
border: `4px solid ${viewMode === 'render' ? "#1565c0" : "#2e7d32"}`,
|
|
121
|
+
fontSize: "20px",
|
|
122
|
+
fontWeight: "900",
|
|
123
|
+
display: "flex",
|
|
124
|
+
alignItems: "center",
|
|
125
|
+
justifyContent: "center",
|
|
126
|
+
cursor: "pointer",
|
|
127
|
+
boxShadow: `inset 0 4px 4px rgba(255,255,255,0.4), 0 4px 0 ${viewMode === 'render' ? "#0d47a1" : "#1b5e20"}`,
|
|
128
|
+
fontFamily: "monospace"
|
|
129
|
+
}}
|
|
130
|
+
title={viewMode === 'render' ? "View Raw Schema" : "View Rendered Item"}
|
|
131
|
+
>
|
|
132
|
+
{viewMode === 'render' ? "{}" : "👁️"}
|
|
133
|
+
</button>
|
|
134
|
+
|
|
135
|
+
<button
|
|
136
|
+
onClick={() => navigate(`/collections/${collectionName}`)}
|
|
137
|
+
style={{
|
|
138
|
+
width: "48px",
|
|
139
|
+
height: "48px",
|
|
140
|
+
borderRadius: "12px",
|
|
141
|
+
backgroundColor: "#ff0055",
|
|
142
|
+
color: "white",
|
|
143
|
+
border: "4px solid #900",
|
|
144
|
+
fontSize: "24px",
|
|
145
|
+
fontWeight: "900",
|
|
146
|
+
display: "flex",
|
|
147
|
+
alignItems: "center",
|
|
148
|
+
justifyContent: "center",
|
|
149
|
+
cursor: "pointer",
|
|
150
|
+
boxShadow: "inset 0 4px 4px rgba(255,255,255,0.4), 0 4px 0 #500",
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
X
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div style={{ flex: 1, overflowY: "auto", padding: "20px" }}>
|
|
159
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
160
|
+
<GenericFetchComponent
|
|
161
|
+
// We use the key prop to force a re-mount of the component when the
|
|
162
|
+
// collectionName or configName changes. This ensures that the internal
|
|
163
|
+
// state of the GenericFetchComponent is reset and the new data is
|
|
164
|
+
// fetched correctly, preventing stale data from being passed to the
|
|
165
|
+
// child component.
|
|
166
|
+
key={`${collectionName}-${configName}`}
|
|
167
|
+
collectionName={collectionName}
|
|
168
|
+
configName={configName}
|
|
169
|
+
render={
|
|
170
|
+
(configData: CollectionConfigData<any>) => {
|
|
171
|
+
if (viewMode === 'schema') {
|
|
172
|
+
return (
|
|
173
|
+
<div style={{
|
|
174
|
+
padding: '20px',
|
|
175
|
+
background: '#1e1e1e',
|
|
176
|
+
color: '#d4d4d4',
|
|
177
|
+
borderRadius: '12px',
|
|
178
|
+
overflow: 'auto',
|
|
179
|
+
height: '100%',
|
|
180
|
+
fontFamily: 'Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace',
|
|
181
|
+
fontSize: '0.9rem',
|
|
182
|
+
textAlign: 'left',
|
|
183
|
+
boxShadow: 'inset 0 0 20px rgba(0,0,0,0.5)',
|
|
184
|
+
border: '2px solid #333'
|
|
185
|
+
}}>
|
|
186
|
+
<pre style={{ margin: 0 }}>{JSON.stringify(configData, null, 2)}</pre>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return props.render ? props.render(configData) : <Component configData={configData} {...props.additionalProps} />;
|
|
191
|
+
}
|
|
192
|
+
} // Use render prop if provided
|
|
193
|
+
/>
|
|
194
|
+
</Suspense>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
40
197
|
);
|
|
41
198
|
};
|
|
42
199
|
|
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { CollectionConfigData } from "ps99-api";
|
|
3
3
|
import ImageComponent from "./ImageComponent";
|
|
4
|
+
import ItemCard from "./ItemCard";
|
|
5
|
+
import { useItemResolution } from "../hooks/useItemResolution";
|
|
4
6
|
|
|
5
7
|
const EggsComponent: React.FC<{
|
|
6
8
|
configData: CollectionConfigData<"Eggs">;
|
|
7
9
|
}> = ({ configData }) => {
|
|
10
|
+
const { getRarityColor, resolveItem } = useItemResolution();
|
|
11
|
+
const rarityColor = configData.rarity ? getRarityColor(configData.rarity) : null;
|
|
12
|
+
|
|
8
13
|
return (
|
|
9
|
-
<div>
|
|
10
|
-
<
|
|
11
|
-
|
|
14
|
+
<div style={{ width: "100%", height: "100%", boxSizing: "border-box" }}>
|
|
15
|
+
<div style={{ width: '300px', margin: '0 auto 20px auto' }}>
|
|
16
|
+
<ItemCard
|
|
17
|
+
id={configData.name}
|
|
18
|
+
amount={1}
|
|
19
|
+
label={configData.name}
|
|
20
|
+
itemData={{
|
|
21
|
+
icon: configData.icon,
|
|
22
|
+
rarity: configData.rarity,
|
|
23
|
+
name: configData.name
|
|
24
|
+
}}
|
|
25
|
+
rarityColor={rarityColor}
|
|
26
|
+
typeId={(configData as any)._index}
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
12
29
|
<p>Currency: {configData.currency}</p>
|
|
13
30
|
<p>Override Cost: {configData.overrideCost}</p>
|
|
14
31
|
{configData.isCustomEgg && <p>Custom Egg: Yes</p>}
|
|
@@ -29,15 +46,36 @@ const EggsComponent: React.FC<{
|
|
|
29
46
|
</div>
|
|
30
47
|
)}
|
|
31
48
|
<h3>Pets:</h3>
|
|
32
|
-
<
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
<div style={{ marginTop: '15px' }}>
|
|
50
|
+
<h3 style={{ borderBottom: '1px solid #eee', paddingBottom: '5px' }}>Pets</h3>
|
|
51
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))', gap: '16px' }}>
|
|
52
|
+
{configData.pets.map((pet, index) => {
|
|
53
|
+
const petName = pet[0];
|
|
54
|
+
const chance = pet[1];
|
|
55
|
+
// The 3rd element appears to be the tier (tn), based on existing code usage
|
|
56
|
+
const tn = pet[2];
|
|
57
|
+
const resolvedItem = resolveItem(petName, tn);
|
|
58
|
+
const rarityColor = resolvedItem?.rarity ? getRarityColor(resolvedItem.rarity) : null;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div key={index} style={{
|
|
62
|
+
// Auto-sizing handled by grid
|
|
63
|
+
}}>
|
|
64
|
+
<ItemCard
|
|
65
|
+
id={petName}
|
|
66
|
+
// Formatting chance as string to include %
|
|
67
|
+
amount={`${chance}%`}
|
|
68
|
+
label={resolvedItem?.name || petName}
|
|
69
|
+
itemData={resolvedItem}
|
|
70
|
+
rarityColor={rarityColor}
|
|
71
|
+
tn={tn}
|
|
72
|
+
// weight or typeId not strictly needed here unless we want debug info
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
41
79
|
{configData.productIds && (
|
|
42
80
|
<div>
|
|
43
81
|
<h3>Product IDs:</h3>
|
|
@@ -1,52 +1,98 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { CollectionConfigData } from "ps99-api";
|
|
3
|
-
import
|
|
3
|
+
import ItemCard from "./ItemCard";
|
|
4
|
+
import { useItemResolution } from "../hooks/useItemResolution";
|
|
4
5
|
|
|
5
6
|
const EnchantsComponent: React.FC<{
|
|
6
7
|
configData: CollectionConfigData<"Enchants">;
|
|
7
8
|
}> = ({ configData }) => {
|
|
9
|
+
const { getRarityColor } = useItemResolution();
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const isSingleTier = configData.Tiers.length === 1;
|
|
13
|
+
|
|
14
|
+
if (isSingleTier) {
|
|
15
|
+
const tier = configData.Tiers[0];
|
|
16
|
+
const rarityColor = tier.Rarity ? getRarityColor(tier.Rarity) : null;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div style={{ width: '100%', height: '100%', boxSizing: 'border-box', display: 'flex', flexDirection: 'column', alignItems: 'center', paddingTop: '20px' }}>
|
|
20
|
+
<div style={{
|
|
21
|
+
width: '120px',
|
|
22
|
+
height: '120px',
|
|
23
|
+
borderRadius: '16px',
|
|
24
|
+
border: rarityColor ? `4px solid ${rarityColor}` : '4px solid #eee',
|
|
25
|
+
padding: '10px',
|
|
26
|
+
backgroundColor: '#fff',
|
|
27
|
+
boxShadow: '0 4px 10px rgba(0,0,0,0.1)',
|
|
28
|
+
display: 'flex',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
justifyContent: 'center',
|
|
31
|
+
marginBottom: '20px'
|
|
32
|
+
}}>
|
|
33
|
+
<img src={tier.Icon} alt={tier.DisplayName} style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div style={{ textAlign: 'center', maxWidth: '400px' }}>
|
|
37
|
+
{configData.BaseTier !== 1 && <p><strong>Base Tier:</strong> {configData.BaseTier}</p>}
|
|
38
|
+
{configData.MaxTier !== 1 && <p><strong>Max Tier:</strong> {configData.MaxTier}</p>}
|
|
39
|
+
{configData.DiminishPowerThreshold && <p><strong>Diminish:</strong> {configData.DiminishPowerThreshold}</p>}
|
|
40
|
+
{configData.EmpoweredBoost && <p><strong>Empowered Boost:</strong> {configData.EmpoweredBoost}</p>}
|
|
41
|
+
|
|
42
|
+
<h3 style={{ margin: '10px 0', fontSize: '1.5em', color: '#333' }}>{tier.DisplayName}</h3>
|
|
43
|
+
<p style={{ color: '#666', fontSize: '1.1em', marginBottom: '15px' }}>{tier.Desc}</p>
|
|
44
|
+
<p style={{ fontWeight: '900', fontSize: '1.2em', color: '#333' }}>Power: {tier.Power}</p>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
8
50
|
return (
|
|
9
|
-
<div>
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
<div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
|
|
52
|
+
<div style={{ textAlign: 'left', marginBottom: '20px', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '10px' }}>
|
|
53
|
+
<p><strong>Base Tier:</strong> {configData.BaseTier}</p>
|
|
54
|
+
<p><strong>Max Tier:</strong> {configData.MaxTier}</p>
|
|
55
|
+
<p><strong>Max Page:</strong> {configData.MaxPage}</p>
|
|
56
|
+
{configData.DiminishPowerThreshold && (
|
|
57
|
+
<p><strong>Diminish Threshold:</strong> {configData.DiminishPowerThreshold}</p>
|
|
58
|
+
)}
|
|
59
|
+
{configData.EmpoweredBoost && (
|
|
60
|
+
<p><strong>Empowered Boost:</strong> {configData.EmpoweredBoost}</p>
|
|
61
|
+
)}
|
|
62
|
+
{configData.ProductId && <p><strong>Product ID:</strong> {configData.ProductId}</p>}
|
|
63
|
+
</div>
|
|
64
|
+
<h3>Tiers</h3>
|
|
65
|
+
<div style={{ display: "grid", gridTemplateColumns: 'repeat(auto-fill, minmax(150px, 1fr))', gap: "15px" }}>
|
|
66
|
+
{configData.Tiers.map((tier, index) => {
|
|
67
|
+
const rarityColor = tier.Rarity ? getRarityColor(tier.Rarity) : null;
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
key={index}
|
|
71
|
+
style={{
|
|
72
|
+
display: 'flex',
|
|
73
|
+
flexDirection: 'column',
|
|
74
|
+
height: '100%'
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<ItemCard
|
|
78
|
+
id={tier.DisplayName}
|
|
79
|
+
amount={1}
|
|
80
|
+
label={tier.DisplayName}
|
|
81
|
+
tn={index + 1}
|
|
82
|
+
itemData={{
|
|
83
|
+
icon: tier.Icon,
|
|
84
|
+
rarity: tier.Rarity,
|
|
85
|
+
name: tier.DisplayName
|
|
86
|
+
}}
|
|
87
|
+
rarityColor={rarityColor}
|
|
88
|
+
/>
|
|
89
|
+
<div style={{ marginTop: '10px', fontSize: '0.9em', color: '#666', textAlign: 'center' }}>
|
|
90
|
+
<p>{tier.Desc}</p>
|
|
91
|
+
<p style={{ fontWeight: 'bold', color: '#333' }}>Power: {tier.Power}</p>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
})}
|
|
50
96
|
</div>
|
|
51
97
|
</div>
|
|
52
98
|
);
|
|
@@ -1,35 +1,49 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { CollectionConfigData } from "ps99-api";
|
|
3
|
-
import
|
|
3
|
+
import ItemCard from "./ItemCard";
|
|
4
|
+
import { useItemResolution } from "../hooks/useItemResolution";
|
|
4
5
|
|
|
5
6
|
const FishingRodsComponent: React.FC<{
|
|
6
7
|
configData: CollectionConfigData<"FishingRods">;
|
|
7
8
|
}> = ({ configData }) => {
|
|
8
9
|
return (
|
|
9
|
-
<div>
|
|
10
|
+
<div className="game-card" style={{ maxWidth: '500px', margin: '0 auto' }}>
|
|
10
11
|
<h2>{configData.DisplayName}</h2>
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
|
|
13
|
+
<div style={{ maxWidth: '300px', margin: '0 auto 15px auto' }}>
|
|
14
|
+
<ItemCard
|
|
15
|
+
id={configData.DisplayName}
|
|
16
|
+
amount={1}
|
|
17
|
+
label={configData.DisplayName}
|
|
18
|
+
itemData={{
|
|
19
|
+
icon: configData.Icon,
|
|
20
|
+
rarity: undefined,
|
|
21
|
+
name: configData.DisplayName
|
|
22
|
+
}}
|
|
23
|
+
rarityColor={null}
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px', marginBottom: '20px', textAlign: 'left' }}>
|
|
28
|
+
<p><strong>Chance:</strong> {configData.FishingChance}%</p>
|
|
29
|
+
<p><strong>Currency:</strong> {configData.FishingCurrencyMultiplier}x</p>
|
|
30
|
+
<p><strong>Speed:</strong> {configData.FishingGameSpeedMultiplier}x</p>
|
|
31
|
+
<p><strong>Bar Size:</strong> {configData.BarSize}</p>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div style={{ borderTop: '1px solid #eee', paddingTop: '15px' }}>
|
|
35
|
+
<h3 style={{ fontSize: '1.1em', marginBottom: '10px' }}>Fishing Odds</h3>
|
|
36
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', justifyContent: 'center' }}>
|
|
37
|
+
{configData.FishingOdds.map((odds, index) => (
|
|
38
|
+
<span key={index} className="badge" style={{ background: '#e3f2fd', color: '#1565c0', border: '1px solid #90caf9' }}>
|
|
39
|
+
{odds[0]}: {odds[1]}%
|
|
40
|
+
</span>
|
|
41
|
+
))}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
31
44
|
</div>
|
|
32
45
|
);
|
|
33
46
|
};
|
|
34
47
|
|
|
48
|
+
|
|
35
49
|
export default FishingRodsComponent;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { useLocation } from "react-router-dom";
|
|
2
3
|
import { useOnlineStatus } from "../hooks/useOnlineStatus";
|
|
3
4
|
|
|
4
5
|
const Footer: React.FC = () => {
|
|
@@ -6,6 +7,18 @@ const Footer: React.FC = () => {
|
|
|
6
7
|
const [lastUpdate, setLastUpdate] = useState<string | null>(null);
|
|
7
8
|
const [loading, setLoading] = useState(false);
|
|
8
9
|
|
|
10
|
+
const location = useLocation();
|
|
11
|
+
const isCollectionsRoute = location.pathname.startsWith("/collections");
|
|
12
|
+
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const handleResize = () => setIsMobile(window.innerWidth < 768);
|
|
16
|
+
window.addEventListener("resize", handleResize);
|
|
17
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
9
22
|
const updateLastUpdate = () => {
|
|
10
23
|
setLastUpdate(new Date().toLocaleString());
|
|
11
24
|
};
|
|
@@ -30,15 +43,12 @@ const Footer: React.FC = () => {
|
|
|
30
43
|
};
|
|
31
44
|
}, []);
|
|
32
45
|
|
|
46
|
+
if (isMobile && isCollectionsRoute) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
33
50
|
return (
|
|
34
|
-
<footer
|
|
35
|
-
style={{
|
|
36
|
-
textAlign: "center",
|
|
37
|
-
padding: "1em",
|
|
38
|
-
background: "#f8f9fa",
|
|
39
|
-
borderTop: "1px solid #ccc",
|
|
40
|
-
}}
|
|
41
|
-
>
|
|
51
|
+
<footer className="game-footer">
|
|
42
52
|
<div
|
|
43
53
|
style={{
|
|
44
54
|
display: "flex",
|
|
@@ -1,27 +1,50 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { CollectionConfigData } from "ps99-api";
|
|
3
|
-
import
|
|
3
|
+
import ItemCard from "./ItemCard";
|
|
4
|
+
import { useItemResolution } from "../hooks/useItemResolution";
|
|
4
5
|
|
|
5
6
|
const FruitsComponent: React.FC<{
|
|
6
7
|
configData: CollectionConfigData<"Fruits">;
|
|
7
8
|
}> = ({ configData }) => {
|
|
9
|
+
const { getRarityColor } = useItemResolution();
|
|
10
|
+
const rarityColor = configData.Rarity ? getRarityColor(configData.Rarity) : null;
|
|
11
|
+
|
|
8
12
|
return (
|
|
9
|
-
<div>
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
<div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
|
|
14
|
+
<ItemCard
|
|
15
|
+
id={configData.DisplayName}
|
|
16
|
+
amount={1}
|
|
17
|
+
label={configData.DisplayName}
|
|
18
|
+
itemData={{
|
|
19
|
+
icon: configData.Icon,
|
|
20
|
+
rarity: configData.Rarity,
|
|
21
|
+
name: configData.DisplayName
|
|
22
|
+
}}
|
|
23
|
+
rarityColor={rarityColor}
|
|
24
|
+
/>
|
|
25
|
+
<div style={{ marginTop: '10px', fontSize: '0.9em', color: '#666', textAlign: 'center' }}>
|
|
26
|
+
<p>Duration: {configData.Duration}s</p>
|
|
27
|
+
{configData.Desc && <p>{configData.Desc}</p>}
|
|
28
|
+
{configData.IgnoreFruitMachine && <p>Ignore Fruit Machine</p>}
|
|
29
|
+
</div>
|
|
30
|
+
<div style={{ marginTop: '15px' }}>
|
|
31
|
+
<h3 style={{ textAlign: 'center', fontSize: '1.1em' }}>Boosts</h3>
|
|
32
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', justifyContent: 'center' }}>
|
|
33
|
+
{configData.Boost.map((boost, index) => (
|
|
34
|
+
<span key={index} style={{
|
|
35
|
+
background: '#e8f5e9',
|
|
36
|
+
color: '#2e7d32',
|
|
37
|
+
padding: '5px 10px',
|
|
38
|
+
borderRadius: '12px',
|
|
39
|
+
fontSize: '0.9em',
|
|
40
|
+
fontWeight: 'bold',
|
|
41
|
+
border: '1px solid #a5d6a7'
|
|
42
|
+
}}>
|
|
43
|
+
{boost.Type}: {boost.Amount}
|
|
44
|
+
</span>
|
|
45
|
+
))}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
25
48
|
</div>
|
|
26
49
|
);
|
|
27
50
|
};
|
|
@@ -59,5 +59,13 @@ export const GenericFetchComponent = <T,>({
|
|
|
59
59
|
return <div>Loading...</div>;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
return
|
|
62
|
+
return (
|
|
63
|
+
<div style={{
|
|
64
|
+
width: '100%',
|
|
65
|
+
padding: '20px',
|
|
66
|
+
boxSizing: 'border-box'
|
|
67
|
+
}}>
|
|
68
|
+
{render(data)}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
63
71
|
};
|