From fbc44883d4c2912479ec9ff97b6359636f5594a1 Mon Sep 17 00:00:00 2001 From: Sarahtonin Date: Sat, 5 Sep 2026 10:26:09 +0200 Subject: [PATCH] Added search and sort. --- Pages/Mutations.php | 25 ++ Pages/Plants.php | 27 +++ Pages/Shop.php | 31 +++ Scripts/Garden/MutationEncyclopedia.js | 320 +++++++++++++++++++++++-- Scripts/Garden/PlantEncyclopedia.js | 280 +++++++++++++++++++++- Scripts/Garden/Shop.js | 288 ++++++++++++++++++++-- Styles/Style.css | 48 +++- 7 files changed, 979 insertions(+), 40 deletions(-) diff --git a/Pages/Mutations.php b/Pages/Mutations.php index 3a33d9c..f915608 100644 --- a/Pages/Mutations.php +++ b/Pages/Mutations.php @@ -25,6 +25,31 @@ $PageSection = "Mutations"; currently capable of discovering.

+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
{ + MutationEncyclopediaSearchQuery = + SearchInput.value; + + RenderMutationEncyclopedia(); + } + ); + } + + + if (SortSelect !== null) { + SortSelect.value = + MutationEncyclopediaSortMode; + + SortSelect.addEventListener( + "change", + () => { + MutationEncyclopediaSortMode = + SortSelect.value; + + RenderMutationEncyclopedia(); + } + ); + } +} + + function RenderMutationEncyclopedia() { const List = document.getElementById( @@ -69,10 +117,37 @@ function RenderMutationEncyclopedia() { ) : []; + const SearchQuery = + MutationEncyclopediaSearchQuery + .trim() + .toLocaleLowerCase(); + + const VisibleHints = + AvailableHints.filter( + Mutation => + DoesMutationHintMatchSearch( + Mutation, + SearchQuery + ) + ); + + const VisibleMutations = + DiscoveredMutations + .filter( + Mutation => + DoesMutationEncyclopediaMatchSearch( + Mutation, + SearchQuery + ) + ) + .sort( + CompareMutationEncyclopediaMutations + ); + for ( const Mutation - of AvailableHints + of VisibleHints ) { List.appendChild( CreateMutationHintCard( @@ -84,7 +159,7 @@ function RenderMutationEncyclopedia() { for ( const Mutation - of DiscoveredMutations + of VisibleMutations ) { List.appendChild( CreateMutationEncyclopediaCard( @@ -106,24 +181,60 @@ function RenderMutationEncyclopedia() { } + if ( + VisibleMutations.length === 0 && + VisibleHints.length === 0 + ) { + SetMutationEncyclopediaMessage( + "No mutations or hints match your search." + ); + + return; + } + + const MessageParts = []; - MessageParts.push( - DiscoveredMutations.length === 1 - ? "1 mutation discovered." - : DiscoveredMutations.length - .toLocaleString() + - " mutations discovered." - ); - - if (AvailableHints.length > 0) { + if (SearchQuery.length > 0) { MessageParts.push( - AvailableHints.length === 1 - ? "1 hint available." - : AvailableHints.length - .toLocaleString() + - " hints available." + "Showing " + + VisibleMutations.length + .toLocaleString() + + " of " + + DiscoveredMutations.length + .toLocaleString() + + " discovered mutations." ); + + if (AvailableHints.length > 0) { + MessageParts.push( + "Showing " + + VisibleHints.length + .toLocaleString() + + " of " + + AvailableHints.length + .toLocaleString() + + " available hints." + ); + } + } else { + MessageParts.push( + DiscoveredMutations.length === 1 + ? "1 mutation discovered." + : DiscoveredMutations.length + .toLocaleString() + + " mutations discovered." + ); + + if (AvailableHints.length > 0) { + MessageParts.push( + AvailableHints.length === 1 + ? "1 hint available." + : AvailableHints.length + .toLocaleString() + + " hints available." + ); + } } SetMutationEncyclopediaMessage( @@ -134,6 +245,183 @@ function RenderMutationEncyclopedia() { } +function DoesMutationHintMatchSearch( + Mutation, + SearchQuery +) { + if (SearchQuery.length === 0) { + return true; + } + + return String( + Mutation.Hint ?? "" + ) + .toLocaleLowerCase() + .includes( + SearchQuery + ); +} + + +function DoesMutationEncyclopediaMatchSearch( + Mutation, + SearchQuery +) { + if (SearchQuery.length === 0) { + return true; + } + + const SearchParts = [ + Mutation.Id, + Mutation.Name, + Mutation.Description, + Mutation.Hint, + FormatMutationChance( + Mutation.Chance + ), + FormatMutationEncyclopediaTime( + Mutation.Cooldown + ), + Mutation.Rotation + ]; + + const RelationPlantIds = [ + ...(Mutation.Relations + ?.PlantsUsed ?? []), + ...(Mutation.Relations + ?.PlantsCreated ?? []) + ]; + + for ( + const PlantId + of RelationPlantIds + ) { + if ( + !HasDiscoveredPlant( + MutationEncyclopediaSave, + PlantId + ) + ) { + continue; + } + + const Plant = + GetPlantById( + PlantId + ); + + if (Plant !== null) { + SearchParts.push( + Plant.Name + ); + } + } + + return SearchParts + .filter( + Value => + Value !== null && + Value !== undefined + ) + .join(" ") + .toLocaleLowerCase() + .includes( + SearchQuery + ); +} + + +function CompareMutationEncyclopediaMutations( + A, + B +) { + switch (MutationEncyclopediaSortMode) { + case "NameAsc": + return String(A.Name ?? "") + .localeCompare( + String(B.Name ?? ""), + undefined, + {sensitivity: "base"} + ) || A.Id - B.Id; + + case "ChanceAsc": + return CompareMutationEncyclopediaNumbers( + A.Chance, + B.Chance, + 1 + ) || A.Id - B.Id; + + case "ChanceDesc": + return CompareMutationEncyclopediaNumbers( + A.Chance, + B.Chance, + -1 + ) || A.Id - B.Id; + + case "CooldownAsc": + return CompareMutationEncyclopediaNumbers( + A.Cooldown, + B.Cooldown, + 1 + ) || A.Id - B.Id; + + case "CooldownDesc": + return CompareMutationEncyclopediaNumbers( + A.Cooldown, + B.Cooldown, + -1 + ) || A.Id - B.Id; + + case "IdAsc": + default: + return A.Id - B.Id; + } +} + + +function CompareMutationEncyclopediaNumbers( + A, + B, + Direction +) { + const MissingA = + A === null || + A === undefined || + A === ""; + + const MissingB = + B === null || + B === undefined || + B === ""; + + const NumberA = Number(A); + const NumberB = Number(B); + + const ValidA = + !MissingA && + Number.isFinite(NumberA); + + const ValidB = + !MissingB && + Number.isFinite(NumberB); + + if (!ValidA && !ValidB) { + return 0; + } + + if (!ValidA) { + return 1; + } + + if (!ValidB) { + return -1; + } + + return (NumberA - NumberB) * + Direction; +} + + function HasUsableMutationHint( Mutation ) { diff --git a/Scripts/Garden/PlantEncyclopedia.js b/Scripts/Garden/PlantEncyclopedia.js index abdb87f..0b2297b 100644 --- a/Scripts/Garden/PlantEncyclopedia.js +++ b/Scripts/Garden/PlantEncyclopedia.js @@ -1,4 +1,6 @@ let PlantEncyclopediaSave; +let PlantEncyclopediaSearchQuery = ""; +let PlantEncyclopediaSortMode = "IdAsc"; async function StartPlantEncyclopedia() { @@ -7,6 +9,7 @@ async function StartPlantEncyclopedia() { PlantEncyclopediaSave = await LoadGame(); + BindPlantEncyclopediaControls(); RenderPlantEncyclopedia(); } catch (Error) { console.error( @@ -21,6 +24,51 @@ async function StartPlantEncyclopedia() { } +function BindPlantEncyclopediaControls() { + const SearchInput = + document.getElementById( + "PlantEncyclopediaSearchInput" + ); + + const SortSelect = + document.getElementById( + "PlantEncyclopediaSortSelect" + ); + + + if (SearchInput !== null) { + SearchInput.value = + PlantEncyclopediaSearchQuery; + + SearchInput.addEventListener( + "input", + () => { + PlantEncyclopediaSearchQuery = + SearchInput.value; + + RenderPlantEncyclopedia(); + } + ); + } + + + if (SortSelect !== null) { + SortSelect.value = + PlantEncyclopediaSortMode; + + SortSelect.addEventListener( + "change", + () => { + PlantEncyclopediaSortMode = + SortSelect.value; + + RenderPlantEncyclopedia(); + } + ); + } +} + + function RenderPlantEncyclopedia() { const List = document.getElementById( @@ -38,16 +86,28 @@ function RenderPlantEncyclopedia() { PlantEncyclopediaSave, Plant.Id ) + ); + + const SearchQuery = + PlantEncyclopediaSearchQuery + .trim() + .toLocaleLowerCase(); + + const VisiblePlants = + DiscoveredPlants + .filter( + Plant => + DoesPlantEncyclopediaMatchSearch( + Plant, + SearchQuery + ) ) .sort( - (A, B) => - A.Id - B.Id + ComparePlantEncyclopediaPlants ); - if ( - DiscoveredPlants.length === 0 - ) { + if (DiscoveredPlants.length === 0) { SetPlantEncyclopediaMessage( "No plants discovered yet." ); @@ -56,9 +116,18 @@ function RenderPlantEncyclopedia() { } + if (VisiblePlants.length === 0) { + SetPlantEncyclopediaMessage( + "No discovered plants match your search." + ); + + return; + } + + for ( const Plant - of DiscoveredPlants + of VisiblePlants ) { List.appendChild( CreatePlantEncyclopediaCard( @@ -68,6 +137,21 @@ function RenderPlantEncyclopedia() { } + if (SearchQuery.length > 0) { + SetPlantEncyclopediaMessage( + "Showing " + + VisiblePlants.length + .toLocaleString() + + " of " + + DiscoveredPlants.length + .toLocaleString() + + " discovered plants." + ); + + return; + } + + SetPlantEncyclopediaMessage( DiscoveredPlants.length === 1 ? "1 plant discovered." @@ -78,6 +162,190 @@ function RenderPlantEncyclopedia() { } +function DoesPlantEncyclopediaMatchSearch( + Plant, + SearchQuery +) { + if (SearchQuery.length === 0) { + return true; + } + + const SearchParts = [ + Plant.Id, + Plant.Name, + Plant.Description, + ...(Array.isArray(Plant.Tags) + ? Plant.Tags + : []) + ]; + + for ( + const Mutation + of GetPlantRelatedMutations( + Plant.Id, + "PlantsCreated" + ).concat( + GetPlantRelatedMutations( + Plant.Id, + "PlantsUsed" + ) + ) + ) { + if ( + HasDiscoveredMutation( + PlantEncyclopediaSave, + Mutation.Id + ) + ) { + SearchParts.push( + Mutation.Name + ); + } + } + + return SearchParts + .filter( + Value => + Value !== null && + Value !== undefined + ) + .join(" ") + .toLocaleLowerCase() + .includes( + SearchQuery + ); +} + + +function ComparePlantEncyclopediaPlants( + A, + B +) { + switch (PlantEncyclopediaSortMode) { + case "NameAsc": + return String(A.Name ?? "") + .localeCompare( + String(B.Name ?? ""), + undefined, + {sensitivity: "base"} + ) || A.Id - B.Id; + + case "GrowthAsc": + return ComparePlantEncyclopediaNumbers( + A.GrowthTime, + B.GrowthTime, + 1 + ) || A.Id - B.Id; + + case "GrowthDesc": + return ComparePlantEncyclopediaNumbers( + A.GrowthTime, + B.GrowthTime, + -1 + ) || A.Id - B.Id; + + case "RewardAsc": + return ComparePlantEncyclopediaNumbers( + GetPlantHarvestReward( + PlantEncyclopediaSave, + A.Id + ), + GetPlantHarvestReward( + PlantEncyclopediaSave, + B.Id + ), + 1 + ) || A.Id - B.Id; + + case "RewardDesc": + return ComparePlantEncyclopediaNumbers( + GetPlantHarvestReward( + PlantEncyclopediaSave, + A.Id + ), + GetPlantHarvestReward( + PlantEncyclopediaSave, + B.Id + ), + -1 + ) || A.Id - B.Id; + + case "DphAsc": + return ComparePlantEncyclopediaNumbers( + GetPlantDewPerHour( + PlantEncyclopediaSave, + A.Id + ), + GetPlantDewPerHour( + PlantEncyclopediaSave, + B.Id + ), + 1 + ) || A.Id - B.Id; + + case "DphDesc": + return ComparePlantEncyclopediaNumbers( + GetPlantDewPerHour( + PlantEncyclopediaSave, + A.Id + ), + GetPlantDewPerHour( + PlantEncyclopediaSave, + B.Id + ), + -1 + ) || A.Id - B.Id; + + case "IdAsc": + default: + return A.Id - B.Id; + } +} + + +function ComparePlantEncyclopediaNumbers( + A, + B, + Direction +) { + const MissingA = + A === null || + A === undefined || + A === ""; + + const MissingB = + B === null || + B === undefined || + B === ""; + + const NumberA = Number(A); + const NumberB = Number(B); + + const ValidA = + !MissingA && + Number.isFinite(NumberA); + + const ValidB = + !MissingB && + Number.isFinite(NumberB); + + if (!ValidA && !ValidB) { + return 0; + } + + if (!ValidA) { + return 1; + } + + if (!ValidB) { + return -1; + } + + return (NumberA - NumberB) * + Direction; +} + + function CreatePlantEncyclopediaCard( Plant ) { diff --git a/Scripts/Garden/Shop.js b/Scripts/Garden/Shop.js index af7e673..eaa2baa 100644 --- a/Scripts/Garden/Shop.js +++ b/Scripts/Garden/Shop.js @@ -1,5 +1,7 @@ let ShopSave; let ShopPurchasePending = false; +let ShopSeedSearchQuery = ""; +let ShopSeedSortMode = "IdAsc"; async function StartShop() { @@ -22,6 +24,7 @@ async function StartShop() { ShopSave = await LoadGame(); BindShopSectionToggles(); + BindShopSeedControls(); RenderShop(); } @@ -84,6 +87,51 @@ function BindShopSectionToggle( } +function BindShopSeedControls() { + const SearchInput = + document.getElementById( + "ShopSeedSearchInput" + ); + + const SortSelect = + document.getElementById( + "ShopSeedSortSelect" + ); + + + if (SearchInput !== null) { + SearchInput.value = + ShopSeedSearchQuery; + + SearchInput.addEventListener( + "input", + () => { + ShopSeedSearchQuery = + SearchInput.value; + + RenderShopSeeds(); + } + ); + } + + + if (SortSelect !== null) { + SortSelect.value = + ShopSeedSortMode; + + SortSelect.addEventListener( + "change", + () => { + ShopSeedSortMode = + SortSelect.value; + + RenderShopSeeds(); + } + ); + } +} + + function RenderShop() { RenderShopCurrency(); RenderShopSeeds(); @@ -124,29 +172,41 @@ function RenderShopSeeds() { ShopSave, Plant ) + ); + + const SearchQuery = + ShopSeedSearchQuery + .trim() + .toLocaleLowerCase(); + + const VisiblePlants = + AvailablePlants + .filter( + Plant => + DoesShopPlantMatchSearch( + Plant, + SearchQuery + ) ) .sort( - (A, B) => - A.Id - B.Id + CompareShopPlants ); - if ( - AvailablePlants.length === 0 - ) { - const EmptyMessage = - document.createElement( - "p" - ); + if (AvailablePlants.length === 0) { + AppendShopSeedEmptyMessage( + SeedList, + "No seeds are available yet." + ); - EmptyMessage.className = - "ShopEmpty"; + return; + } - EmptyMessage.textContent = - "No seeds are available yet."; - SeedList.appendChild( - EmptyMessage + if (VisiblePlants.length === 0) { + AppendShopSeedEmptyMessage( + SeedList, + "No seeds match your search." ); return; @@ -155,7 +215,7 @@ function RenderShopSeeds() { for ( const Plant - of AvailablePlants + of VisiblePlants ) { SeedList.appendChild( CreateShopSeedCard( @@ -166,6 +226,202 @@ function RenderShopSeeds() { } +function AppendShopSeedEmptyMessage( + SeedList, + Message +) { + const EmptyMessage = + document.createElement( + "p" + ); + + EmptyMessage.className = + "ShopEmpty"; + + EmptyMessage.textContent = + Message; + + SeedList.appendChild( + EmptyMessage + ); +} + + +function DoesShopPlantMatchSearch( + Plant, + SearchQuery +) { + if (SearchQuery.length === 0) { + return true; + } + + const SearchParts = [ + Plant.Id, + Plant.Name, + Plant.Description, + ...(Array.isArray(Plant.Tags) + ? Plant.Tags + : []) + ]; + + return SearchParts + .filter( + Value => + Value !== null && + Value !== undefined + ) + .join(" ") + .toLocaleLowerCase() + .includes( + SearchQuery + ); +} + + +function CompareShopPlants( + A, + B +) { + switch (ShopSeedSortMode) { + case "NameAsc": + return CompareShopText( + A.Name, + B.Name + ) || A.Id - B.Id; + + case "PriceAsc": + return CompareShopNumbers( + GetPlantShopCost(ShopSave, A.Id), + GetPlantShopCost(ShopSave, B.Id), + 1 + ) || A.Id - B.Id; + + case "PriceDesc": + return CompareShopNumbers( + GetPlantShopCost(ShopSave, A.Id), + GetPlantShopCost(ShopSave, B.Id), + -1 + ) || A.Id - B.Id; + + case "GrowthAsc": + return CompareShopNumbers( + A.GrowthTime, + B.GrowthTime, + 1 + ) || A.Id - B.Id; + + case "GrowthDesc": + return CompareShopNumbers( + A.GrowthTime, + B.GrowthTime, + -1 + ) || A.Id - B.Id; + + case "RewardAsc": + return CompareShopNumbers( + GetPlantHarvestReward(ShopSave, A.Id), + GetPlantHarvestReward(ShopSave, B.Id), + 1 + ) || A.Id - B.Id; + + case "RewardDesc": + return CompareShopNumbers( + GetPlantHarvestReward(ShopSave, A.Id), + GetPlantHarvestReward(ShopSave, B.Id), + -1 + ) || A.Id - B.Id; + + case "DphAsc": + return CompareShopNumbers( + GetPlantDewPerHour(ShopSave, A.Id), + GetPlantDewPerHour(ShopSave, B.Id), + 1 + ) || A.Id - B.Id; + + case "DphDesc": + return CompareShopNumbers( + GetPlantDewPerHour(ShopSave, A.Id), + GetPlantDewPerHour(ShopSave, B.Id), + -1 + ) || A.Id - B.Id; + + case "InventoryAsc": + return CompareShopNumbers( + GetSeedCount(ShopSave, A.Id), + GetSeedCount(ShopSave, B.Id), + 1 + ) || A.Id - B.Id; + + case "InventoryDesc": + return CompareShopNumbers( + GetSeedCount(ShopSave, A.Id), + GetSeedCount(ShopSave, B.Id), + -1 + ) || A.Id - B.Id; + + case "IdAsc": + default: + return A.Id - B.Id; + } +} + + +function CompareShopText( + A, + B +) { + return String(A ?? "") + .localeCompare( + String(B ?? ""), + undefined, + {sensitivity: "base"} + ); +} + + +function CompareShopNumbers( + A, + B, + Direction +) { + const MissingA = + A === null || + A === undefined || + A === ""; + + const MissingB = + B === null || + B === undefined || + B === ""; + + const NumberA = Number(A); + const NumberB = Number(B); + + const ValidA = + !MissingA && + Number.isFinite(NumberA); + + const ValidB = + !MissingB && + Number.isFinite(NumberB); + + if (!ValidA && !ValidB) { + return 0; + } + + if (!ValidA) { + return 1; + } + + if (!ValidB) { + return -1; + } + + return (NumberA - NumberB) * + Direction; +} + + function RenderGardenUpgrades() { const UpgradeList = document.getElementById( diff --git a/Styles/Style.css b/Styles/Style.css index 4ed2f03..eff0b72 100644 --- a/Styles/Style.css +++ b/Styles/Style.css @@ -1210,6 +1210,50 @@ input, button, textarea, select { display: none; } + +.CatalogueControls { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 14rem); + gap: 0.75rem; + margin: 0 0 1rem; + align-items: center; +} + +.CatalogueControls input, .CatalogueControls select { + width: 100%; + min-width: 0; + padding: 0.65rem 0.75rem; + color: var(--Text); + background-color: var(--Mantle); + border: 3px solid var(--Surface2); + border-radius: 0; + font-size: 1rem; + box-shadow: 3px 3px 0 var(--Crust); +} + +.CatalogueControls input::placeholder { + color: var(--Overlay1); +} + +.CatalogueControls input:hover, .CatalogueControls select:hover { + border-color: var(--Sky); +} + +.CatalogueControls input:focus, .CatalogueControls select:focus { + outline: 3px solid var(--Yellow); + outline-offset: 3px; +} + +.CatalogueControls select { + cursor: pointer; +} + +@media (max-width: 500px) { + .CatalogueControls { + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + } +} + .ShopSeedList { display: grid; grid-template-columns: repeat( auto-fit, minmax(15rem, 1fr) ); @@ -1450,7 +1494,7 @@ input, button, textarea, select { display: flex; flex-direction: column; gap: 1rem; - margin-top: 1.5rem; + margin-top: 0; } .PlantEncyclopediaCard { @@ -1594,7 +1638,7 @@ input, button, textarea, select { display: flex; flex-direction: column; gap: 1rem; - margin-top: 1.5rem; + margin-top: 0; } .MutationEncyclopediaCard {