Added search and sort.

This commit is contained in:
sarah committed 2026-09-05 10:26:09 +02:00
1 parent ba27ff9fd7
commit fbc44883d4
7 files changed
+979 -40

No files matched your search

+25
View File
@@ -25,6 +25,31 @@ $PageSection = "Mutations";
currently capable of discovering.
</p>
<div
class="CatalogueControls"
aria-label="Mutation search and sorting"
>
<input
id="MutationEncyclopediaSearchInput"
type="search"
placeholder="Search mutations..."
aria-label="Search mutations"
autocomplete="off"
>
<select
id="MutationEncyclopediaSortSelect"
aria-label="Sort mutations"
>
<option value="IdAsc">ID</option>
<option value="NameAsc">Name A-Z</option>
<option value="ChanceDesc">Chance high-low</option>
<option value="ChanceAsc">Chance low-high</option>
<option value="CooldownAsc">Cooldown short-long</option>
<option value="CooldownDesc">Cooldown long-short</option>
</select>
</div>
<div
id="MutationEncyclopediaList"
class="MutationEncyclopediaList"
+27
View File
@@ -23,6 +23,33 @@ $PageSection = "Plants";
them in your Garden.
</p>
<div
class="CatalogueControls"
aria-label="Plant search and sorting"
>
<input
id="PlantEncyclopediaSearchInput"
type="search"
placeholder="Search plants..."
aria-label="Search plants"
autocomplete="off"
>
<select
id="PlantEncyclopediaSortSelect"
aria-label="Sort plants"
>
<option value="IdAsc">ID</option>
<option value="NameAsc">Name A-Z</option>
<option value="GrowthAsc">Growth short-long</option>
<option value="GrowthDesc">Growth long-short</option>
<option value="RewardAsc">Reward low-high</option>
<option value="RewardDesc">Reward high-low</option>
<option value="DphDesc">DPH high-low</option>
<option value="DphAsc">DPH low-high</option>
</select>
</div>
<div
id="PlantEncyclopediaList"
class="PlantEncyclopediaList"
+31
View File
@@ -95,6 +95,37 @@ $PageSection = "Shop";
id="SeedsContent"
class="ShopSectionContent"
>
<div
class="CatalogueControls"
aria-label="Seed search and sorting"
>
<input
id="ShopSeedSearchInput"
type="search"
placeholder="Search plants..."
aria-label="Search plants"
autocomplete="off"
>
<select
id="ShopSeedSortSelect"
aria-label="Sort seeds"
>
<option value="IdAsc">ID</option>
<option value="NameAsc">Name A-Z</option>
<option value="PriceAsc">Price low-high</option>
<option value="PriceDesc">Price high-low</option>
<option value="GrowthAsc">Growth short-long</option>
<option value="GrowthDesc">Growth long-short</option>
<option value="RewardAsc">Reward low-high</option>
<option value="RewardDesc">Reward high-low</option>
<option value="DphDesc">DPH high-low</option>
<option value="DphAsc">DPH low-high</option>
<option value="InventoryDesc">Inventory high-low</option>
<option value="InventoryAsc">Inventory low-high</option>
</select>
</div>
<div
id="ShopSeedList"
class="ShopSeedList"
+304 -16
View File
@@ -1,4 +1,6 @@
let MutationEncyclopediaSave;
let MutationEncyclopediaSearchQuery = "";
let MutationEncyclopediaSortMode = "IdAsc";
async function StartMutationEncyclopedia() {
@@ -8,6 +10,7 @@ async function StartMutationEncyclopedia() {
MutationEncyclopediaSave =
await LoadGame();
BindMutationEncyclopediaControls();
RenderMutationEncyclopedia();
} catch (Error) {
console.error(
@@ -22,6 +25,51 @@ async function StartMutationEncyclopedia() {
}
function BindMutationEncyclopediaControls() {
const SearchInput =
document.getElementById(
"MutationEncyclopediaSearchInput"
);
const SortSelect =
document.getElementById(
"MutationEncyclopediaSortSelect"
);
if (SearchInput !== null) {
SearchInput.value =
MutationEncyclopediaSearchQuery;
SearchInput.addEventListener(
"input",
() => {
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
) {
+274 -6
View File
@@ -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
) {
+272 -16
View File
@@ -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(
+46 -2
View File
@@ -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 {