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.
Files changed (92) hide show
  1. package/.github/workflows/release-on-main.yml +1 -2
  2. package/.idea/runConfigurations/test_changing.xml +1 -1
  3. package/debug_currency.json +57 -0
  4. package/debug_goals.json +271 -0
  5. package/dist/ps99-api.d.ts +2 -0
  6. package/dist/ps99-api.js +4 -1
  7. package/dist/ps99-api.js.map +1 -1
  8. package/dist/request-client/axios.js +6 -1
  9. package/dist/request-client/axios.js.map +1 -1
  10. package/dist/responses/collection/index.d.ts +1 -0
  11. package/dist/responses/collection/index.js +15 -0
  12. package/dist/responses/collection/index.js.map +1 -1
  13. package/dist/responses/collection/rarity.d.ts +1 -0
  14. package/example-web/react/package-lock.json +1504 -1470
  15. package/example-web/react2/package-lock.json +3089 -2766
  16. package/example-web/react2/package.json +6 -1
  17. package/example-web/react2/public/assets/gold_variant_icon.png +0 -0
  18. package/example-web/react2/public/assets/hot_cocoa_egg.png +0 -0
  19. package/example-web/react2/public/index.html +34 -31
  20. package/example-web/react2/src/App.tsx +15 -15
  21. package/example-web/react2/src/assets/guild_placeholder.png +0 -0
  22. package/example-web/react2/src/components/AchievementsComponent.tsx +74 -19
  23. package/example-web/react2/src/components/AutoSizer.tsx +49 -0
  24. package/example-web/react2/src/components/BoostsComponent.tsx +16 -5
  25. package/example-web/react2/src/components/BoothsComponent.tsx +22 -52
  26. package/example-web/react2/src/components/BoxesComponent.tsx +48 -16
  27. package/example-web/react2/src/components/BuffsComponent.tsx +82 -34
  28. package/example-web/react2/src/components/CharmsComponent.tsx +84 -24
  29. package/example-web/react2/src/components/CollectionConfigIndex.tsx +867 -33
  30. package/example-web/react2/src/components/CollectionsIndex.tsx +380 -27
  31. package/example-web/react2/src/components/CollectionsLayout.tsx +60 -0
  32. package/example-web/react2/src/components/CurrencyComponent.tsx +57 -39
  33. package/example-web/react2/src/components/DynamicCollectionConfigData.tsx +172 -15
  34. package/example-web/react2/src/components/EggsComponent.tsx +50 -12
  35. package/example-web/react2/src/components/EnchantsComponent.tsx +88 -42
  36. package/example-web/react2/src/components/FishingRodsComponent.tsx +36 -22
  37. package/example-web/react2/src/components/Footer.tsx +18 -8
  38. package/example-web/react2/src/components/FruitsComponent.tsx +40 -17
  39. package/example-web/react2/src/components/GenericFetchComponent.tsx +9 -1
  40. package/example-web/react2/src/components/GuildBattlesComponent.tsx +41 -34
  41. package/example-web/react2/src/components/Header.tsx +39 -52
  42. package/example-web/react2/src/components/HomePage.tsx +15 -17
  43. package/example-web/react2/src/components/HoverboardsComponent.tsx +23 -99
  44. package/example-web/react2/src/components/ImageComponent.tsx +255 -45
  45. package/example-web/react2/src/components/ItemCard.tsx +240 -0
  46. package/example-web/react2/src/components/LootboxesComponent.tsx +22 -7
  47. package/example-web/react2/src/components/MasteryComponent.tsx +165 -30
  48. package/example-web/react2/src/components/MerchantsComponent.tsx +41 -16
  49. package/example-web/react2/src/components/MiscItemsComponent.tsx +26 -31
  50. package/example-web/react2/src/components/PetsComponent.tsx +100 -61
  51. package/example-web/react2/src/components/PotionsComponent.tsx +121 -27
  52. package/example-web/react2/src/components/RandomEventsComponent.tsx +32 -23
  53. package/example-web/react2/src/components/RanksComponent.tsx +187 -62
  54. package/example-web/react2/src/components/RarityComponent.tsx +123 -5
  55. package/example-web/react2/src/components/ReactWindowMock.tsx +73 -0
  56. package/example-web/react2/src/components/RebirthsComponent.tsx +36 -19
  57. package/example-web/react2/src/components/SecretRoomsComponent.tsx +5 -4
  58. package/example-web/react2/src/components/SeedsComponent.tsx +41 -21
  59. package/example-web/react2/src/components/ShovelsComponent.tsx +21 -9
  60. package/example-web/react2/src/components/Sidebar.tsx +105 -0
  61. package/example-web/react2/src/components/SprinklersComponent.tsx +25 -10
  62. package/example-web/react2/src/components/Tooltip.tsx +36 -0
  63. package/example-web/react2/src/components/UltimatesComponent.tsx +28 -16
  64. package/example-web/react2/src/components/UpgradesComponent.tsx +97 -47
  65. package/example-web/react2/src/components/WateringCansComponent.tsx +20 -14
  66. package/example-web/react2/src/components/WorldsComponent.tsx +21 -11
  67. package/example-web/react2/src/components/XPPotionsComponent.tsx +28 -11
  68. package/example-web/react2/src/components/ZoneFlagsComponent.tsx +25 -14
  69. package/example-web/react2/src/components/ZonesComponent.tsx +43 -60
  70. package/example-web/react2/src/constants/collectionIcons.ts +29 -0
  71. package/example-web/react2/src/context/CollectionDataContext.tsx +62 -0
  72. package/example-web/react2/src/context/ScrollContext.tsx +35 -0
  73. package/example-web/react2/src/hooks/useCollapsibleHeader.ts +69 -0
  74. package/example-web/react2/src/hooks/useExpandableList.ts +38 -0
  75. package/example-web/react2/src/hooks/useItemResolution.ts +351 -0
  76. package/example-web/react2/src/index.css +257 -0
  77. package/example-web/react2/src/index.tsx +2 -1
  78. package/example-web/react2/src/utils/gigantix.ts +40 -0
  79. package/example-web/react2/temp_model.rbxm +0 -0
  80. package/example-web/react2/webpack.config.js +103 -47
  81. package/package.json +11 -11
  82. package/ranks.json +1 -0
  83. package/repro_collection_fetch.ts +33 -0
  84. package/repro_image_fetch.ts +50 -0
  85. package/src/__tests__/__snapshots__/ps99-api-changes.ts.snap +34841 -10439
  86. package/src/__tests__/__snapshots__/ps99-api-live.ts.snap +160667 -67217
  87. package/src/ps99-api.ts +9 -5
  88. package/src/request-client/axios.ts +6 -2
  89. package/src/responses/collection/index.ts +1 -0
  90. package/src/responses/collection/rarity.ts +1 -0
  91. package/tsconfig.json +1 -1
  92. package/example-web/react2/public/service-worker.js +0 -63
@@ -1,67 +1,117 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
+ import ItemCard from "./ItemCard";
3
4
  import ImageComponent from "./ImageComponent";
5
+ import { useExpandableList } from "../hooks/useExpandableList";
6
+ import { formatGigantix } from "../utils/gigantix";
4
7
 
5
8
  const UpgradeComponent: React.FC<{
6
9
  configData: CollectionConfigData<"Upgrades">;
7
10
  }> = ({ configData }) => {
11
+ const { expandedIndices, toggle, expandAll, collapseAll, isExpanded } = useExpandableList(configData.TierCurrencies.length);
12
+
8
13
  return (
9
- <div>
10
- <h2>Upgrade</h2>
14
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
11
15
  {configData.Icon && (
12
- <ImageComponent src={configData.Icon} alt="Upgrade Icon" />
16
+ <div style={{ maxWidth: '100px', margin: '0 auto 20px' }}>
17
+ <ImageComponent src={configData.Icon} alt="Upgrade Icon" />
18
+ </div>
13
19
  )}
14
- <h3>Tier Powers</h3>
15
- <ul>
16
- {configData.TierPowers.map((power, index) => (
17
- <li key={index}>
18
- Tier {index + 1}: {power}
19
- </li>
20
- ))}
21
- </ul>
22
- <h3>Tier Costs</h3>
23
- <ul>
24
- {configData.TierCosts.map((cost, index) => (
25
- <li key={index}>
26
- Tier {index + 1}: {cost}
27
- </li>
28
- ))}
29
- </ul>
30
- <h3>Tier Currencies</h3>
31
- {configData.TierCurrencies.map((currency, index) => (
32
- <div key={index}>
33
- <h4>{currency.DisplayName}</h4>
34
- <p>Rarity Number: {currency.Rarity.RarityNumber}</p>
35
- <p>Description: {currency.Desc}</p>
36
- {currency.Tradable && <p>Tradable</p>}
37
- <p>Max Amount: {currency.MaxAmount}</p>
38
- <h5>Bag Tiers</h5>
39
- <ul>
40
- {currency.BagTiers.map((bagTier, bagIndex) => (
41
- <li key={bagIndex}>
42
- <p>Value: {bagTier.value}</p>
43
- <ImageComponent
44
- src={bagTier.image}
45
- alt={`Bag Tier ${bagTier.value}`}
46
- />
20
+
21
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px', marginBottom: '30px' }}>
22
+ <div>
23
+ <h3 style={{ borderBottom: '1px solid #eee', paddingBottom: '5px' }}>Tier Powers</h3>
24
+ <ul style={{ listStyle: 'none', padding: 0, textAlign: 'left' }}>
25
+ {configData.TierPowers.map((power, index) => (
26
+ <li key={index} style={{ padding: '5px 0', borderBottom: '1px dashed #eee' }}>
27
+ <strong>Tier {index + 1}:</strong> {formatGigantix(power)}
47
28
  </li>
48
29
  ))}
49
30
  </ul>
50
- <h5>Tiers</h5>
51
- <ul>
52
- {currency.Tiers.map((tier, tierIndex) => (
53
- <li key={tierIndex}>
54
- <p>Tier Name: {tier.tierName}</p>
55
- <p>Order: {tier.Order}</p>
56
- <p>Value: {tier.value}</p>
57
- {tier.isBottom && <p>Is Bottom</p>}
58
- <ImageComponent src={tier.orbImage} alt="Orb" />
59
- <ImageComponent src={tier.imageOutline} alt="Outline" />
31
+ </div>
32
+ <div>
33
+ <h3 style={{ borderBottom: '1px solid #eee', paddingBottom: '5px' }}>Tier Costs</h3>
34
+ <ul style={{ listStyle: 'none', padding: 0, textAlign: 'left' }}>
35
+ {configData.TierCosts.map((cost, index) => (
36
+ <li key={index} style={{ padding: '5px 0', borderBottom: '1px dashed #eee' }}>
37
+ <strong>Tier {index + 1}:</strong> {formatGigantix(cost)}
60
38
  </li>
61
39
  ))}
62
40
  </ul>
63
41
  </div>
64
- ))}
42
+ </div>
43
+
44
+ <div>
45
+ <h3 style={{ fontSize: '1.2em', marginBottom: '15px' }}>Tier Currencies</h3>
46
+ <div style={{ marginBottom: '20px', display: 'flex', gap: '10px', justifyContent: 'center' }}>
47
+ <button onClick={expandAll} style={{ padding: '8px 16px', borderRadius: '8px', border: '1px solid #ddd', background: '#fff', cursor: 'pointer' }}>Expand All</button>
48
+ <button onClick={collapseAll} style={{ padding: '8px 16px', borderRadius: '8px', border: '1px solid #ddd', background: '#fff', cursor: 'pointer' }}>Collapse All</button>
49
+ </div>
50
+ {configData.TierCurrencies.map((currency, index) => (
51
+ <div key={index} style={{ background: '#f9f9f9', padding: '15px', borderRadius: '12px', marginBottom: '20px' }}>
52
+ <div
53
+ onClick={() => toggle(index)}
54
+ style={{ cursor: 'pointer' }}
55
+ >
56
+ <h4 style={{ marginTop: 0, display: 'flex', alignItems: 'center', gap: '8px' }}>
57
+ {isExpanded(index) ? 'โ–ผ' : 'โ–ถ'} {currency.DisplayName}
58
+ </h4>
59
+ <div style={{ display: 'flex', gap: '10px', justifyContent: 'center', marginBottom: '10px' }}>
60
+ <span className="badge" style={{ borderColor: (currency.Rarity?.Color as string) || '#ccc' }}>{currency.Rarity?.DisplayName}</span>
61
+ {currency.Tradable && <span className="badge">Tradable</span>}
62
+ <span className="badge">Max: {formatGigantix(currency.MaxAmount)}</span>
63
+ </div>
64
+ </div>
65
+
66
+ {isExpanded(index) && (
67
+ <>
68
+ <p style={{ fontStyle: 'italic', fontSize: '0.9em', color: '#666' }}>{currency.Desc}</p>
69
+
70
+ <h5 style={{ marginTop: '15px', borderBottom: '1px solid #e0e0e0' }}>Bag Tiers</h5>
71
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(150px, 1fr))", gap: "10px", marginTop: '10px' }}>
72
+ {currency.BagTiers.map((bagTier, bagIndex) => (
73
+ <div key={bagIndex}>
74
+ <ItemCard
75
+ id={`bag-tier-${bagIndex}`}
76
+ amount={bagTier.value}
77
+ label={`Bag Tier ${bagIndex + 1}`}
78
+ itemData={{
79
+ icon: bagTier.image,
80
+ rarity: currency.Rarity,
81
+ name: `Bag Tier ${bagIndex + 1}`
82
+ }}
83
+ rarityColor={(currency.Rarity?.Color as string) || null}
84
+ />
85
+ </div>
86
+ ))}
87
+ </div>
88
+
89
+ <h5 style={{ marginTop: '15px', borderBottom: '1px solid #e0e0e0' }}>Tiers</h5>
90
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(150px, 1fr))", gap: "10px", marginTop: '10px' }}>
91
+ {currency.Tiers.map((tier, tierIndex) => (
92
+ <div key={tierIndex}>
93
+ <ItemCard
94
+ id={tier.tierName}
95
+ amount={tier.value}
96
+ label={tier.tierName}
97
+ itemData={{
98
+ icon: tier.orbImage,
99
+ rarity: currency.Rarity,
100
+ name: tier.tierName
101
+ }}
102
+ rarityColor={(currency.Rarity?.Color as string) || null}
103
+ />
104
+ <div style={{ marginTop: '5px', fontSize: '0.8em', color: '#666', textAlign: 'center' }}>
105
+ Order: {tier.Order}
106
+ </div>
107
+ </div>
108
+ ))}
109
+ </div>
110
+ </>
111
+ )}
112
+ </div>
113
+ ))}
114
+ </div>
65
115
  </div>
66
116
  );
67
117
  };
@@ -1,25 +1,31 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
- import ImageComponent from "./ImageComponent";
3
+ import ItemCard from "./ItemCard";
4
4
 
5
5
  const WateringCanComponent: React.FC<{
6
6
  configData: CollectionConfigData<"WateringCans">;
7
7
  }> = ({ configData }) => {
8
8
  return (
9
- <div>
10
- <h2>Watering Can</h2>
11
- <h3>{configData.DisplayName}</h3>
12
- {configData.Icon && (
13
- <ImageComponent
14
- src={configData.Icon}
15
- alt={`${configData.DisplayName} Icon`}
9
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
10
+
11
+ <div style={{ maxWidth: '300px', margin: '0 auto 15px auto' }}>
12
+ <ItemCard
13
+ id={configData.DisplayName}
14
+ amount={1}
15
+ label={configData.DisplayName}
16
+ itemData={{
17
+ icon: configData.Icon,
18
+ rarity: undefined,
19
+ name: configData.DisplayName
20
+ }}
21
+ rarityColor={null}
16
22
  />
17
- )}
18
- <p>Associated Item ID: {configData.AssociatedItemID}</p>
19
- <p>Plant Time Multiplier: {configData.PlantTimeMultiplier}</p>
20
- <p>
21
- Plant Time Multiplier Duration: {configData.PlantTimeMultiplierDuration}
22
- </p>
23
+ </div>
24
+
25
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px', fontSize: '0.9em', color: '#666', textAlign: 'center' }}>
26
+ <p><strong>Time Mult:</strong> {configData.PlantTimeMultiplier}x</p>
27
+ <p><strong>Duration:</strong> {configData.PlantTimeMultiplierDuration}</p>
28
+ </div>
23
29
  </div>
24
30
  );
25
31
  };
@@ -5,21 +5,31 @@ const WorldComponent: React.FC<{
5
5
  configData: CollectionConfigData<"Worlds">;
6
6
  }> = ({ configData }) => {
7
7
  return (
8
- <div>
9
- <h2>World</h2>
10
- <h3>{configData.MapName}</h3>
11
- <p>Spawn ID: {configData.SpawnId}</p>
12
- <p>World Currency: {configData.WorldCurrency}</p>
13
- <p>Place ID: {configData.PlaceId}</p>
14
- <p>World Number: {configData.WorldNumber}</p>
8
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
9
+ <div style={{ textAlign: 'left', width: '100%' }}>
10
+ <p><strong>World #:</strong> {configData.WorldNumber}</p>
11
+ <p><strong>Spawn ID:</strong> {configData.SpawnId}</p>
12
+ <p><strong>Currency:</strong> {configData.WorldCurrency}</p>
13
+ <p><strong>Place ID:</strong> {configData.PlaceId}</p>
14
+ </div>
15
+
15
16
  {configData.AdditionalMusic && configData.AdditionalMusic.length > 0 && (
16
- <div>
17
+ <div style={{ marginTop: '15px', width: '100%' }}>
17
18
  <h4>Additional Music</h4>
18
- <ul>
19
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', justifyContent: 'center' }}>
19
20
  {configData.AdditionalMusic.map((music, index) => (
20
- <li key={index}>{music}</li>
21
+ <span key={index} style={{
22
+ background: '#e0f7fa',
23
+ color: '#006064',
24
+ padding: '5px 10px',
25
+ borderRadius: '15px',
26
+ fontSize: '0.9em',
27
+ fontWeight: 'bold'
28
+ }}>
29
+ {music}
30
+ </span>
21
31
  ))}
22
- </ul>
32
+ </div>
23
33
  </div>
24
34
  )}
25
35
  </div>
@@ -1,21 +1,38 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
- import ImageComponent from "./ImageComponent";
3
+ import ItemCard from "./ItemCard";
4
+ import { useItemResolution } from "../hooks/useItemResolution";
4
5
 
5
6
  const XPPotionsComponent: React.FC<{
6
7
  configData: CollectionConfigData<"XPPotions">;
7
8
  }> = ({ configData }) => {
9
+ const { getRarityColor } = useItemResolution();
10
+ const rarityColor = configData.Rarity ? getRarityColor(configData.Rarity) : null;
11
+
8
12
  return (
9
- <div>
10
- <h2>{configData.DisplayName}</h2>
11
- <ImageComponent src={configData.Icon} alt={configData.DisplayName} />
12
- <p>Amount: {configData.Amount}</p>
13
- <p>Description: {configData.Desc}</p>
14
- <p>Item ID: {configData.ItemId}</p>
15
- <h3>Rarity</h3>
16
- <p>Display Name: {configData.Rarity.DisplayName}</p>
17
- <p>Rarity Number: {configData.Rarity.RarityNumber}</p>
18
- <p>Announce: {configData.Rarity.Announce ? "Yes" : "No"}</p>
13
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
14
+
15
+ <div style={{ maxWidth: '300px', margin: '0 auto 15px auto' }}>
16
+ <ItemCard
17
+ id={configData.DisplayName}
18
+ amount={1}
19
+ label={configData.DisplayName}
20
+ itemData={{
21
+ icon: configData.Icon,
22
+ rarity: configData.Rarity,
23
+ name: configData.DisplayName
24
+ }}
25
+ rarityColor={rarityColor}
26
+ />
27
+ </div>
28
+
29
+ <div style={{ fontSize: '0.9em', color: '#666', textAlign: 'center' }}>
30
+ <p style={{ marginBottom: '10px' }}>{configData.Desc}</p>
31
+ <div style={{ display: 'flex', gap: '10px', justifyContent: 'center' }}>
32
+ <span className="badge">Amount: {configData.Amount}</span>
33
+ <span className="badge" style={{ background: '#eee', color: '#333' }}>ID: {configData.ItemId}</span>
34
+ </div>
35
+ </div>
19
36
  </div>
20
37
  );
21
38
  };
@@ -1,24 +1,35 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
- import ImageComponent from "./ImageComponent";
3
+ import ItemCard from "./ItemCard";
4
+ import { useItemResolution } from "../hooks/useItemResolution";
4
5
 
5
6
  const ZoneFlagComponent: React.FC<{
6
7
  configData: CollectionConfigData<"ZoneFlags">;
7
8
  }> = ({ configData }) => {
9
+ const { getRarityColor } = useItemResolution();
10
+ const rarityColor = configData.Rarity ? getRarityColor(configData.Rarity) : null;
11
+
8
12
  return (
9
- <div>
10
- <h2>Zone Flag</h2>
11
- <h3>{configData.Name}</h3>
12
- <p>Description: {configData.Desc}</p>
13
- <p>Duration: {configData.Duration} seconds</p>
14
- <p>Color: {configData.Color}</p>
15
- <p>
16
- Icon: <ImageComponent src={configData.Icon} alt={configData.Name} />
17
- </p>
18
- <h4>Rarity</h4>
19
- <p>Rarity: {configData.Rarity.DisplayName}</p>
20
- <p>Rarity Number: {configData.Rarity.RarityNumber}</p>
21
- <p>Announce: {configData.Rarity.Announce.toString()}</p>
13
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
14
+
15
+ <div style={{ maxWidth: '300px', margin: '0 auto 15px 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
+ />
27
+ </div>
28
+
29
+ <div style={{ fontSize: '0.9em', color: '#666', textAlign: 'center' }}>
30
+ <p>{configData.Desc}</p>
31
+ <p style={{ fontWeight: 'bold', marginTop: '5px' }}>Duration: {configData.Duration}s</p>
32
+ </div>
22
33
  </div>
23
34
  );
24
35
  };
@@ -5,79 +5,62 @@ const ZoneComponent: React.FC<{
5
5
  configData: CollectionConfigData<"Zones">;
6
6
  }> = ({ configData }) => {
7
7
  return (
8
- <div>
9
- <h2>Zone</h2>
10
- <h3>{configData.ZoneName}</h3>
11
- <p>Zone Number: {configData.ZoneNumber}</p>
12
- <p>World Number: {configData.WorldNumber}</p>
13
- <p>Currency: {configData.Currency}</p>
14
- <p>Maximum Available Egg: {configData.MaximumAvailableEgg}</p>
15
- {configData.Price && <p>Price: {configData.Price}</p>}
16
- {configData.GateHealth && <p>Gate Health: {configData.GateHealth}</p>}
17
- {configData.TeleportToZoneOnFall && (
18
- <p>
19
- Teleport to Zone on Fall: {configData.TeleportToZoneOnFall.toString()}
20
- </p>
21
- )}
22
- {configData.Ambience && (
23
- <p>Ambience Sound ID: {configData.Ambience.SoundId}</p>
24
- )}
8
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
9
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px', width: '100%', textAlign: 'left' }}>
10
+ <p><strong>Zone #:</strong> {configData.ZoneNumber}</p>
11
+ <p><strong>World #:</strong> {configData.WorldNumber}</p>
12
+ <p><strong>Currency:</strong> {configData.Currency}</p>
13
+ <p><strong>Max Egg:</strong> {configData.MaximumAvailableEgg}</p>
14
+ {configData.Price && <p><strong>Price:</strong> {configData.Price}</p>}
15
+ {configData.GateHealth && <p><strong>Gate Health:</strong> {configData.GateHealth}</p>}
16
+ {configData.TeleportToZoneOnFall && (
17
+ <p><strong>Teleport on Fall:</strong> Yes</p>
18
+ )}
19
+ {configData.Ambience && (
20
+ <p><strong>Ambience ID:</strong> {configData.Ambience.SoundId}</p>
21
+ )}
22
+ </div>
23
+
25
24
  {configData.QuestsRequired && configData.QuestsRequired.length > 0 && (
26
- <div>
25
+ <div style={{ width: '100%', marginTop: '20px' }}>
27
26
  <h4>Quests Required</h4>
28
- <ul>
27
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', justifyContent: 'center' }}>
29
28
  {configData.QuestsRequired.map((quest, index) => (
30
- <li key={index}>
31
- Type: {quest.Type}, Amount: {quest.Amount}
32
- </li>
29
+ <div key={index} style={{
30
+ background: '#f0f0f0',
31
+ padding: '10px',
32
+ borderRadius: '8px',
33
+ border: '1px solid #ddd'
34
+ }}>
35
+ <strong>{quest.Type}</strong>: {quest.Amount}
36
+ </div>
33
37
  ))}
34
- </ul>
38
+ </div>
35
39
  </div>
36
40
  )}
41
+
37
42
  {configData.Lighting && (
38
- <div>
39
- <h4>Lighting</h4>
43
+ <div style={{ width: '100%', marginTop: '20px', textAlign: 'left', background: '#f9f9f9', padding: '15px', borderRadius: '8px' }}>
44
+ <h4 style={{ textAlign: 'center' }}>Lighting Settings</h4>
40
45
  <p>Brightness: {configData.Lighting.Brightness}</p>
41
46
  <p>Clock Time: {configData.Lighting.ClockTime}</p>
47
+
42
48
  {configData.Lighting.Bloom && (
43
- <div>
44
- <h5>Bloom</h5>
45
- <p>Enabled: {configData.Lighting.Bloom.Enabled.toString()}</p>
46
- <p>Intensity: {configData.Lighting.Bloom.Intensity}</p>
47
- <p>Size: {configData.Lighting.Bloom.Size}</p>
48
- <p>Threshold: {configData.Lighting.Bloom.Threshold}</p>
49
- </div>
50
- )}
51
- {configData.Lighting.ColorCorrection && (
52
- <div>
53
- <h5>Color Correction</h5>
54
- <p>
55
- Enabled:{" "}
56
- {configData.Lighting.ColorCorrection.Enabled.toString()}
57
- </p>
58
- <p>
59
- Saturation: {configData.Lighting.ColorCorrection.Saturation}
60
- </p>
61
- <p>Contrast: {configData.Lighting.ColorCorrection.Contrast}</p>
62
- <p>
63
- Brightness: {configData.Lighting.ColorCorrection.Brightness}
64
- </p>
49
+ <div style={{ marginTop: '10px' }}>
50
+ <strong>Bloom:</strong>
51
+ <span style={{ fontSize: '0.9em', marginLeft: '10px' }}>
52
+ Enabled: {configData.Lighting.Bloom.Enabled.toString()},
53
+ Intensity: {configData.Lighting.Bloom.Intensity}
54
+ </span>
65
55
  </div>
66
56
  )}
57
+
67
58
  {configData.Lighting.Sky && (
68
- <div>
69
- <h5>Sky</h5>
70
- <p>Star Count: {configData.Lighting.Sky.StarCount}</p>
71
- <p>
72
- Celestial Bodies Shown:{" "}
73
- {configData.Lighting.Sky.CelestialBodiesShown.toString()}
74
- </p>
75
- <p>Skybox Up: {configData.Lighting.Sky.SkyboxUp}</p>
76
- <p>Skybox Bk: {configData.Lighting.Sky.SkyboxBk}</p>
77
- <p>Skybox Dn: {configData.Lighting.Sky.SkyboxDn}</p>
78
- <p>Skybox Lf: {configData.Lighting.Sky.SkyboxLf}</p>
79
- <p>Skybox Rt: {configData.Lighting.Sky.SkyboxRt}</p>
80
- <p>Skybox Ft: {configData.Lighting.Sky.SkyboxFt}</p>
59
+ <div style={{ marginTop: '10px' }}>
60
+ <strong>Sky:</strong>
61
+ <span style={{ fontSize: '0.9em', marginLeft: '10px' }}>
62
+ Stars: {configData.Lighting.Sky.StarCount}
63
+ </span>
81
64
  </div>
82
65
  )}
83
66
  </div>
@@ -0,0 +1,29 @@
1
+ export const COLLECTION_ICONS: Record<string, string> = {
2
+ Pets: "๐Ÿฑ",
3
+ Eggs: "๐Ÿฅš",
4
+ Enchants: "๐Ÿ“˜",
5
+ Potions: "๐Ÿงช",
6
+ Items: "๐ŸŽ’",
7
+ Hoverboards: "๐Ÿ›น",
8
+ Booths: "๐Ÿช",
9
+ Ultimates: "๐ŸŒ€",
10
+ MiscItems: "๐Ÿ“ฆ",
11
+ Keys: "๐Ÿ—๏ธ",
12
+ Tickets: "๐ŸŽŸ๏ธ",
13
+ Charms: "๐Ÿงฟ",
14
+ Fruits: "๐ŸŽ",
15
+ Seeds: "๐ŸŒฑ",
16
+ Flags: "๐Ÿšฉ",
17
+ Glitch: "๐Ÿ‘พ",
18
+ Achievements: "๐Ÿ†",
19
+ Boosts: "๐Ÿš€",
20
+ Upgrades: "โฌ†๏ธ",
21
+ Relics: "๐Ÿ—ฟ",
22
+ Ranks: "๐Ÿ“ˆ",
23
+ RandomEvents: "๐ŸŽฒ",
24
+ SecretRooms: "๐Ÿ”’",
25
+ Zones: "uq",
26
+ World: "๐ŸŒ",
27
+ Clan: "๐Ÿ›ก๏ธ",
28
+ Mastery: "๐ŸŽ“",
29
+ };
@@ -0,0 +1,62 @@
1
+ import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
2
+ import { PetSimulator99API, Collections, CollectionName } from "ps99-api";
3
+
4
+ const api = new PetSimulator99API();
5
+
6
+ type CollectionDataCache = Partial<Record<string, Collections[]>>;
7
+
8
+ interface CollectionDataContextType {
9
+ data: CollectionDataCache;
10
+ fetchCollection: (collectionName: CollectionName) => Promise<void>;
11
+ isLoading: (collectionName: string) => boolean;
12
+ }
13
+
14
+ const CollectionDataContext = createContext<CollectionDataContextType | undefined>(undefined);
15
+
16
+ export const CollectionDataProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
17
+ const [data, setData] = useState<CollectionDataCache>({});
18
+ const [loadingStates, setLoadingStates] = useState<Record<string, boolean>>({});
19
+
20
+ const fetchCollection = useCallback(async (collectionName: CollectionName) => {
21
+ // If data already exists, don't fetch again unless force refresh is needed (not implemented yet)
22
+ if (data[collectionName]) {
23
+ return;
24
+ }
25
+
26
+ if (loadingStates[collectionName]) return; // Already loading
27
+
28
+ setLoadingStates(prev => ({ ...prev, [collectionName]: true }));
29
+
30
+ try {
31
+ console.log(`[CollectionDataContext] Fetching ${collectionName}...`);
32
+ const result = await api.getCollection(collectionName);
33
+ if (result.status === "ok") {
34
+ setData(prev => ({ ...prev, [collectionName]: result.data as Collections[] }));
35
+ } else {
36
+ console.error(`Status error fetching ${collectionName}:`, result);
37
+ }
38
+ } catch (error) {
39
+ console.error(`Failed to fetch collection ${collectionName}`, error);
40
+ } finally {
41
+ setLoadingStates(prev => ({ ...prev, [collectionName]: false }));
42
+ }
43
+ }, [data, loadingStates]);
44
+
45
+ const isLoading = useCallback((collectionName: string) => {
46
+ return !!loadingStates[collectionName];
47
+ }, [loadingStates]);
48
+
49
+ return (
50
+ <CollectionDataContext.Provider value={{ data, fetchCollection, isLoading }}>
51
+ {children}
52
+ </CollectionDataContext.Provider>
53
+ );
54
+ };
55
+
56
+ export const useCollectionData = () => {
57
+ const context = useContext(CollectionDataContext);
58
+ if (!context) {
59
+ throw new Error('useCollectionData must be used within a CollectionDataProvider');
60
+ }
61
+ return context;
62
+ };
@@ -0,0 +1,35 @@
1
+ import React, { createContext, useContext, useRef, useCallback } from 'react';
2
+
3
+ interface ScrollContextType {
4
+ scrollPositions: React.MutableRefObject<Record<string, number>>;
5
+ saveScrollPosition: (key: string, offset: number) => void;
6
+ getScrollPosition: (key: string) => number;
7
+ }
8
+
9
+ const ScrollContext = createContext<ScrollContextType | undefined>(undefined);
10
+
11
+ export const ScrollProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
12
+ const scrollPositions = useRef<Record<string, number>>({});
13
+
14
+ const saveScrollPosition = useCallback((key: string, offset: number) => {
15
+ scrollPositions.current[key] = offset;
16
+ }, []);
17
+
18
+ const getScrollPosition = useCallback((key: string) => {
19
+ return scrollPositions.current[key] || 0;
20
+ }, []);
21
+
22
+ return (
23
+ <ScrollContext.Provider value={{ scrollPositions, saveScrollPosition, getScrollPosition }}>
24
+ {children}
25
+ </ScrollContext.Provider>
26
+ );
27
+ };
28
+
29
+ export const useScrollPersistence = () => {
30
+ const context = useContext(ScrollContext);
31
+ if (!context) {
32
+ throw new Error("useScrollPersistence must be used within a ScrollProvider");
33
+ }
34
+ return context;
35
+ };