mirror of
https://git.srhtnin.garden/sarah/srhtnin.github.io.git
synced 2026-10-13 18:38:11 +02:00
Added editor pages for plants and mutations.
This commit is contained in:
1 parent
953df07878
commit
c2a78b18b4
6 files changed
+4288
-2
No files matched your search
+24
-2
@@ -30,9 +30,31 @@ $PageSection = "Admin";
|
|||||||
hidden
|
hidden
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Admin access verified. Garden content
|
Content tools for editing the Garden catalogue.
|
||||||
editors will live here.
|
Changes here affect the live game data.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div class="AdminEditorNavigation">
|
||||||
|
<a
|
||||||
|
class="ActionButton AdminEditorLink"
|
||||||
|
href="/Pages/AdminPlants.html"
|
||||||
|
>
|
||||||
|
<strong>Plant editor</strong>
|
||||||
|
<span>
|
||||||
|
Add plants and edit their catalogue data.
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="ActionButton AdminEditorLink"
|
||||||
|
href="/Pages/AdminMutations.html"
|
||||||
|
>
|
||||||
|
<strong>Mutation editor</strong>
|
||||||
|
<span>
|
||||||
|
Build mutation recipes with a visual grid.
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,432 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$PageTitle = "Admin mutations";
|
||||||
|
$PageSection = "Admin";
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="en" data-palette="CatppuccinMocha">
|
||||||
|
<?php require __DIR__ . "/Bits/Head.php"; ?>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<?php require __DIR__ . "/Bits/Navbar.php"; ?>
|
||||||
|
|
||||||
|
<main class="MainContainer AdminMutationMain">
|
||||||
|
<h1>Mutation editor</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Edit mutation settings and build the recipe visually.
|
||||||
|
Select a cell in either grid to change what it means.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p
|
||||||
|
id="AdminAccessMessage"
|
||||||
|
class="PageMessage"
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
Checking admin access...
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="AdminContent"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<div class="AdminEditorToolbar">
|
||||||
|
<label for="AdminMutationSelect">
|
||||||
|
Mutation
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<select id="AdminMutationSelect"></select>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="AdminMutationNewButton"
|
||||||
|
class="ActionButton AdminInlineButton"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
New mutation
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form
|
||||||
|
id="AdminMutationForm"
|
||||||
|
class="Panel AdminEditorForm"
|
||||||
|
>
|
||||||
|
<div class="AdminEditorGrid">
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>ID</span>
|
||||||
|
<input
|
||||||
|
id="AdminMutationId"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
step="1"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<small>
|
||||||
|
Permanent. New mutations default to the next ID.
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Mutation Key</span>
|
||||||
|
<input
|
||||||
|
id="AdminMutationKey"
|
||||||
|
type="text"
|
||||||
|
maxlength="64"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField AdminEditorWideField">
|
||||||
|
<span>Name</span>
|
||||||
|
<input
|
||||||
|
id="AdminMutationName"
|
||||||
|
type="text"
|
||||||
|
maxlength="128"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField AdminEditorWideField">
|
||||||
|
<span>Description</span>
|
||||||
|
<textarea
|
||||||
|
id="AdminMutationDescription"
|
||||||
|
rows="4"
|
||||||
|
required
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Priority</span>
|
||||||
|
<input
|
||||||
|
id="AdminMutationPriority"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Chance (%)</span>
|
||||||
|
<input
|
||||||
|
id="AdminMutationChance"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="0.01"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Cooldown (ms)</span>
|
||||||
|
<input
|
||||||
|
id="AdminMutationCooldown"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1000"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<small id="AdminMutationCooldownHint"></small>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Rotation</span>
|
||||||
|
<select id="AdminMutationRotation">
|
||||||
|
<option value="None">None</option>
|
||||||
|
<option value="Any">Any</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorCheckField">
|
||||||
|
<input
|
||||||
|
id="AdminMutationAllowImmature"
|
||||||
|
type="checkbox"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Allow immature plants
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="AdminMutationRecipeSection">
|
||||||
|
<div class="AdminMutationRecipeHeader">
|
||||||
|
<div>
|
||||||
|
<h2>Visual recipe</h2>
|
||||||
|
<p>
|
||||||
|
Pattern and result always use the same dimensions.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="AdminMutationDimensions">
|
||||||
|
<label>
|
||||||
|
Width
|
||||||
|
<input
|
||||||
|
id="AdminMutationWidth"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="7"
|
||||||
|
step="1"
|
||||||
|
value="3"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Height
|
||||||
|
<input
|
||||||
|
id="AdminMutationHeight"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="7"
|
||||||
|
step="1"
|
||||||
|
value="1"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="AdminMutationResizeButton"
|
||||||
|
class="ActionButton AdminInlineButton"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Resize
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="AdminMutationRecipeFlow">
|
||||||
|
<div class="AdminMutationGridPanel">
|
||||||
|
<h3>Arrange</h3>
|
||||||
|
<div
|
||||||
|
id="AdminMutationPatternGrid"
|
||||||
|
class="GuideRecipe AdminMutationGrid"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="MutationEncyclopediaRecipeArrow"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
→
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="AdminMutationGridPanel">
|
||||||
|
<h3>Result</h3>
|
||||||
|
<div
|
||||||
|
id="AdminMutationResultGrid"
|
||||||
|
class="GuideRecipe AdminMutationGrid"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="AdminMutationCellEditor"
|
||||||
|
class="AdminMutationCellEditor"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<h3 id="AdminMutationCellEditorTitle">
|
||||||
|
Cell
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div id="AdminMutationPatternCellControls">
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Type</span>
|
||||||
|
<select id="AdminMutationPatternCellType">
|
||||||
|
<option value="Any">Any</option>
|
||||||
|
<option value="Empty">Empty</option>
|
||||||
|
<option value="Plant">Exact plant</option>
|
||||||
|
<option value="Matcher">Tag matcher</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label
|
||||||
|
id="AdminMutationPatternPlantRow"
|
||||||
|
class="AdminEditorField"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<span>Plant</span>
|
||||||
|
<select id="AdminMutationPatternPlant"></select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="AdminMutationMatcherRows"
|
||||||
|
class="AdminMatcherFields"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Exact plant (optional)</span>
|
||||||
|
<select id="AdminMutationMatcherPlant"></select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Required tags</span>
|
||||||
|
<input
|
||||||
|
id="AdminMutationMatcherTags"
|
||||||
|
type="text"
|
||||||
|
placeholder="Rose, Red"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Any of these tags</span>
|
||||||
|
<input
|
||||||
|
id="AdminMutationMatcherTagsAny"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Excluded tags</span>
|
||||||
|
<input
|
||||||
|
id="AdminMutationMatcherTagsNot"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Capture name</span>
|
||||||
|
<input
|
||||||
|
id="AdminMutationMatcherCapture"
|
||||||
|
type="text"
|
||||||
|
placeholder="ParentA"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="AdminMutationResultCellControls"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Type</span>
|
||||||
|
<select id="AdminMutationResultCellType">
|
||||||
|
<option value="Keep">Keep</option>
|
||||||
|
<option value="Empty">Empty</option>
|
||||||
|
<option value="Plant">Plant</option>
|
||||||
|
<option value="Capture">Captured plant</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label
|
||||||
|
id="AdminMutationResultPlantRow"
|
||||||
|
class="AdminEditorField"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<span>Plant</span>
|
||||||
|
<select id="AdminMutationResultPlant"></select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label
|
||||||
|
id="AdminMutationResultCaptureRow"
|
||||||
|
class="AdminEditorField"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<span>Capture name</span>
|
||||||
|
<input
|
||||||
|
id="AdminMutationResultCapture"
|
||||||
|
type="text"
|
||||||
|
placeholder="ParentA"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="AdminMutationRelationsSection">
|
||||||
|
<h2>Plant relationships</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
These power discoveries and encyclopedia links.
|
||||||
|
They are metadata, so tag-based recipes may still
|
||||||
|
need explicit plants selected here.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="AdminMutationRelationColumns">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Plants used</legend>
|
||||||
|
<div
|
||||||
|
id="AdminMutationPlantsUsed"
|
||||||
|
class="AdminPlantChecklist"
|
||||||
|
></div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Plants created</legend>
|
||||||
|
<div
|
||||||
|
id="AdminMutationPlantsCreated"
|
||||||
|
class="AdminPlantChecklist"
|
||||||
|
></div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="AdminMutationFailureSection">
|
||||||
|
<h2>Failure result</h2>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>On failure</span>
|
||||||
|
<select id="AdminMutationFailureType">
|
||||||
|
<option value="Keep">Keep everything</option>
|
||||||
|
<option value="Clear">Clear matched area</option>
|
||||||
|
<option value="Custom">Custom result JSON</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label
|
||||||
|
id="AdminMutationFailureJsonRow"
|
||||||
|
class="AdminEditorField"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<span>Custom failure result JSON</span>
|
||||||
|
<textarea
|
||||||
|
id="AdminMutationFailureJson"
|
||||||
|
rows="6"
|
||||||
|
spellcheck="false"
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<details class="AdminGeneratedJson">
|
||||||
|
<summary>Generated mutation JSON</summary>
|
||||||
|
<pre id="AdminMutationJsonPreview"></pre>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<p id="AdminMutationArchiveNote" hidden>
|
||||||
|
This mutation is currently archived.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="AdminEditorActions">
|
||||||
|
<button
|
||||||
|
id="AdminMutationSaveButton"
|
||||||
|
class="ActionButton AdminSaveButton"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Save mutation
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="ActionButton AdminInlineButton AdminEditorBackLink"
|
||||||
|
href="/Pages/Admin.html"
|
||||||
|
>
|
||||||
|
Back to Admin
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p
|
||||||
|
id="AdminMutationMessage"
|
||||||
|
class="PageMessage"
|
||||||
|
aria-live="polite"
|
||||||
|
></p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="/Scripts/Garden/PlantImages.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Plants.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Mutations.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Save.js"></script>
|
||||||
|
<script src="/Scripts/Garden/AdminMutationEditor.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$PageTitle = "Admin plants";
|
||||||
|
$PageSection = "Admin";
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="en" data-palette="CatppuccinMocha">
|
||||||
|
<?php require __DIR__ . "/Bits/Head.php"; ?>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<?php require __DIR__ . "/Bits/Navbar.php"; ?>
|
||||||
|
|
||||||
|
<main class="MainContainer">
|
||||||
|
<h1>Plant editor</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Add plants or edit existing catalogue data. Numeric IDs
|
||||||
|
and existing Plant Keys stay permanent once created.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p
|
||||||
|
id="AdminAccessMessage"
|
||||||
|
class="PageMessage"
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
Checking admin access...
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="AdminContent"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<div class="AdminEditorToolbar">
|
||||||
|
<label for="AdminPlantSelect">
|
||||||
|
Plant
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<select id="AdminPlantSelect"></select>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="AdminPlantNewButton"
|
||||||
|
class="ActionButton AdminInlineButton"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
New plant
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form
|
||||||
|
id="AdminPlantForm"
|
||||||
|
class="Panel AdminEditorForm"
|
||||||
|
>
|
||||||
|
<div class="AdminEditorGrid">
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>ID</span>
|
||||||
|
<input
|
||||||
|
id="AdminPlantId"
|
||||||
|
name="Id"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
step="1"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<small>
|
||||||
|
Permanent. New plants default to the next ID.
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Plant Key</span>
|
||||||
|
<input
|
||||||
|
id="AdminPlantKey"
|
||||||
|
name="PlantKey"
|
||||||
|
type="text"
|
||||||
|
maxlength="64"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<small>
|
||||||
|
Used by saves, recipes and the sprite folder.
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField AdminEditorWideField">
|
||||||
|
<span>Name</span>
|
||||||
|
<input
|
||||||
|
id="AdminPlantName"
|
||||||
|
name="Name"
|
||||||
|
type="text"
|
||||||
|
maxlength="64"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField AdminEditorWideField">
|
||||||
|
<span>Description</span>
|
||||||
|
<textarea
|
||||||
|
id="AdminPlantDescription"
|
||||||
|
name="Description"
|
||||||
|
rows="4"
|
||||||
|
required
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField AdminEditorWideField">
|
||||||
|
<span>Tags</span>
|
||||||
|
<input
|
||||||
|
id="AdminPlantTags"
|
||||||
|
name="Tags"
|
||||||
|
type="text"
|
||||||
|
placeholder="Rose, Flower, Red"
|
||||||
|
>
|
||||||
|
<small>
|
||||||
|
Comma-separated. Spaces around tags are ignored.
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Growth time (ms)</span>
|
||||||
|
<input
|
||||||
|
id="AdminPlantGrowthTime"
|
||||||
|
name="GrowthTime"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1000"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<small id="AdminPlantGrowthTimeHint"></small>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField">
|
||||||
|
<span>Base seed cost</span>
|
||||||
|
<input
|
||||||
|
id="AdminPlantBaseCost"
|
||||||
|
name="BaseCost"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
>
|
||||||
|
<small>
|
||||||
|
Used only for plants stocked from the start.
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorCheckField">
|
||||||
|
<input
|
||||||
|
id="AdminPlantShopPlant"
|
||||||
|
name="ShopPlant"
|
||||||
|
type="checkbox"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Available in the Shop from the beginning
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="AdminEditorField AdminEditorWideField">
|
||||||
|
<span>Effects JSON</span>
|
||||||
|
<textarea
|
||||||
|
id="AdminPlantEffects"
|
||||||
|
name="Effects"
|
||||||
|
rows="6"
|
||||||
|
spellcheck="false"
|
||||||
|
></textarea>
|
||||||
|
<small>
|
||||||
|
Keep this as <code>{}</code> when the plant has no effects.
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="AdminEditorPreview">
|
||||||
|
<h2>Preview</h2>
|
||||||
|
|
||||||
|
<div class="AdminPlantPreviewRow">
|
||||||
|
<div
|
||||||
|
id="AdminPlantPreviewTile"
|
||||||
|
class="PlantTile AdminPlantPreviewTile"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<strong id="AdminPlantPreviewName">
|
||||||
|
New plant
|
||||||
|
</strong>
|
||||||
|
<p id="AdminPlantPreviewKey"></p>
|
||||||
|
<p id="AdminPlantArchiveNote" hidden>
|
||||||
|
This plant is currently archived.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="AdminEditorActions">
|
||||||
|
<button
|
||||||
|
id="AdminPlantSaveButton"
|
||||||
|
class="ActionButton AdminSaveButton"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Save plant
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="ActionButton AdminInlineButton AdminEditorBackLink"
|
||||||
|
href="/Pages/Admin.html"
|
||||||
|
>
|
||||||
|
Back to Admin
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p
|
||||||
|
id="AdminPlantMessage"
|
||||||
|
class="PageMessage"
|
||||||
|
aria-live="polite"
|
||||||
|
></p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="/Scripts/Garden/PlantImages.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Plants.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Save.js"></script>
|
||||||
|
<script src="/Scripts/Garden/AdminPlantEditor.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,863 @@
|
|||||||
|
let AdminPlantCatalogue = {};
|
||||||
|
let AdminPlantEditingKey = null;
|
||||||
|
let AdminPlantSavePending = false;
|
||||||
|
|
||||||
|
|
||||||
|
async function StartAdminPlantEditor() {
|
||||||
|
try {
|
||||||
|
const Status =
|
||||||
|
await GetAdminStatus();
|
||||||
|
|
||||||
|
if (
|
||||||
|
Status.Success !== true ||
|
||||||
|
Status.IsAdmin !== true
|
||||||
|
) {
|
||||||
|
SetAdminPlantMessage(
|
||||||
|
"Admin access required."
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BindAdminPlantEditor();
|
||||||
|
await LoadAdminPlants();
|
||||||
|
} catch (Error) {
|
||||||
|
console.error(
|
||||||
|
"Couldn't start Plant editor:",
|
||||||
|
Error
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantMessage(
|
||||||
|
Error.message ??
|
||||||
|
"Couldn't load the Plant editor."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function BindAdminPlantEditor() {
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantSelect"
|
||||||
|
).addEventListener(
|
||||||
|
"change",
|
||||||
|
Event => {
|
||||||
|
const PlantKey =
|
||||||
|
Event.target.value;
|
||||||
|
|
||||||
|
if (PlantKey === "") {
|
||||||
|
StartNewAdminPlant();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadAdminPlantIntoForm(
|
||||||
|
PlantKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantNewButton"
|
||||||
|
).addEventListener(
|
||||||
|
"click",
|
||||||
|
StartNewAdminPlant
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantForm"
|
||||||
|
).addEventListener(
|
||||||
|
"submit",
|
||||||
|
SaveAdminPlant
|
||||||
|
);
|
||||||
|
|
||||||
|
for (
|
||||||
|
const ElementId
|
||||||
|
of [
|
||||||
|
"AdminPlantId",
|
||||||
|
"AdminPlantKey",
|
||||||
|
"AdminPlantName",
|
||||||
|
"AdminPlantGrowthTime",
|
||||||
|
"AdminPlantShopPlant",
|
||||||
|
"AdminPlantBaseCost"
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
document.getElementById(
|
||||||
|
ElementId
|
||||||
|
).addEventListener(
|
||||||
|
"input",
|
||||||
|
RenderAdminPlantPreview
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
ElementId
|
||||||
|
).addEventListener(
|
||||||
|
"change",
|
||||||
|
RenderAdminPlantPreview
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantShopPlant"
|
||||||
|
).addEventListener(
|
||||||
|
"change",
|
||||||
|
UpdateAdminPlantShopFields
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function AdminPlantRequest(
|
||||||
|
Action,
|
||||||
|
Data = {}
|
||||||
|
) {
|
||||||
|
const SaveKey =
|
||||||
|
localStorage.getItem(
|
||||||
|
SaveKeyName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof SaveKey !== "string" ||
|
||||||
|
!/^[0-9A-Fa-f]{64}$/.test(
|
||||||
|
SaveKey
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"A valid Account Key is required."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Response = await fetch(
|
||||||
|
ApiUrl +
|
||||||
|
"/AdminPlants.php",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
|
||||||
|
headers: {
|
||||||
|
"Content-Type":
|
||||||
|
"application/json"
|
||||||
|
},
|
||||||
|
|
||||||
|
body: JSON.stringify({
|
||||||
|
SaveKey: SaveKey,
|
||||||
|
Action: Action,
|
||||||
|
...Data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let Result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Result = await Response.json();
|
||||||
|
} catch (Error) {
|
||||||
|
throw new Error(
|
||||||
|
"Plant admin API returned invalid JSON."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Response.ok ||
|
||||||
|
Result.Success !== true
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
Result.Error ??
|
||||||
|
"Plant admin request failed."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function LoadAdminPlants(
|
||||||
|
PreferredPlantKey = null
|
||||||
|
) {
|
||||||
|
SetAdminPlantMessage(
|
||||||
|
"Loading plants..."
|
||||||
|
);
|
||||||
|
|
||||||
|
const Result =
|
||||||
|
await AdminPlantRequest(
|
||||||
|
"List"
|
||||||
|
);
|
||||||
|
|
||||||
|
AdminPlantCatalogue =
|
||||||
|
Result.Plants ?? {};
|
||||||
|
|
||||||
|
Plants =
|
||||||
|
AdminPlantCatalogue;
|
||||||
|
|
||||||
|
RenderAdminPlantSelect();
|
||||||
|
|
||||||
|
const Keys =
|
||||||
|
GetSortedAdminPlantKeys();
|
||||||
|
|
||||||
|
const PlantKey =
|
||||||
|
PreferredPlantKey !== null &&
|
||||||
|
AdminPlantCatalogue[
|
||||||
|
PreferredPlantKey
|
||||||
|
] !== undefined
|
||||||
|
? PreferredPlantKey
|
||||||
|
: Keys[0] ?? null;
|
||||||
|
|
||||||
|
if (PlantKey === null) {
|
||||||
|
StartNewAdminPlant();
|
||||||
|
} else {
|
||||||
|
LoadAdminPlantIntoForm(
|
||||||
|
PlantKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetAdminPlantMessage(
|
||||||
|
Keys.length === 1
|
||||||
|
? "1 plant loaded."
|
||||||
|
: Keys.length +
|
||||||
|
" plants loaded."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetSortedAdminPlantKeys() {
|
||||||
|
return Object.keys(
|
||||||
|
AdminPlantCatalogue
|
||||||
|
).sort(
|
||||||
|
(A, B) => {
|
||||||
|
const PlantA =
|
||||||
|
AdminPlantCatalogue[A];
|
||||||
|
|
||||||
|
const PlantB =
|
||||||
|
AdminPlantCatalogue[B];
|
||||||
|
|
||||||
|
return (
|
||||||
|
Number(PlantA.Id) -
|
||||||
|
Number(PlantB.Id) ||
|
||||||
|
A.localeCompare(B)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RenderAdminPlantSelect() {
|
||||||
|
const Select =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantSelect"
|
||||||
|
);
|
||||||
|
|
||||||
|
Select.replaceChildren();
|
||||||
|
|
||||||
|
for (
|
||||||
|
const PlantKey
|
||||||
|
of GetSortedAdminPlantKeys()
|
||||||
|
) {
|
||||||
|
const Plant =
|
||||||
|
AdminPlantCatalogue[
|
||||||
|
PlantKey
|
||||||
|
];
|
||||||
|
|
||||||
|
const Option =
|
||||||
|
document.createElement(
|
||||||
|
"option"
|
||||||
|
);
|
||||||
|
|
||||||
|
Option.value =
|
||||||
|
PlantKey;
|
||||||
|
|
||||||
|
Option.textContent =
|
||||||
|
String(
|
||||||
|
Plant.Id
|
||||||
|
).padStart(
|
||||||
|
3,
|
||||||
|
"0"
|
||||||
|
) +
|
||||||
|
" — " +
|
||||||
|
Plant.Name +
|
||||||
|
(
|
||||||
|
Plant.Archived === true
|
||||||
|
? " (archived)"
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
|
||||||
|
Select.appendChild(
|
||||||
|
Option
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function LoadAdminPlantIntoForm(
|
||||||
|
PlantKey
|
||||||
|
) {
|
||||||
|
const Plant =
|
||||||
|
AdminPlantCatalogue[
|
||||||
|
PlantKey
|
||||||
|
];
|
||||||
|
|
||||||
|
if (Plant === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminPlantEditingKey =
|
||||||
|
PlantKey;
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantSelect"
|
||||||
|
).value = PlantKey;
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantId",
|
||||||
|
Plant.Id
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantKey",
|
||||||
|
PlantKey
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantName",
|
||||||
|
Plant.Name ?? ""
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantDescription",
|
||||||
|
Plant.Description ?? ""
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantTags",
|
||||||
|
Array.isArray(
|
||||||
|
Plant.Tags
|
||||||
|
)
|
||||||
|
? Plant.Tags.join(", ")
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantGrowthTime",
|
||||||
|
Number(
|
||||||
|
Plant.GrowthTime ?? 0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantShopPlant"
|
||||||
|
).checked =
|
||||||
|
Plant.Shop?.ShopPlant === true;
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantBaseCost",
|
||||||
|
Plant.Shop?.BaseCost ?? ""
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantEffects",
|
||||||
|
JSON.stringify(
|
||||||
|
Plant.Effects ?? {},
|
||||||
|
null,
|
||||||
|
4
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantId"
|
||||||
|
).readOnly = true;
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantKey"
|
||||||
|
).readOnly = true;
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantArchiveNote"
|
||||||
|
).hidden =
|
||||||
|
Plant.Archived !== true;
|
||||||
|
|
||||||
|
UpdateAdminPlantShopFields();
|
||||||
|
RenderAdminPlantPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function StartNewAdminPlant() {
|
||||||
|
AdminPlantEditingKey = null;
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantSelect"
|
||||||
|
).value = "";
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantId",
|
||||||
|
GetNextAdminPlantId()
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantKey",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantName",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantDescription",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantTags",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantGrowthTime",
|
||||||
|
3600000
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantShopPlant"
|
||||||
|
).checked = false;
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantBaseCost",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantField(
|
||||||
|
"AdminPlantEffects",
|
||||||
|
"{}"
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantId"
|
||||||
|
).readOnly = false;
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantKey"
|
||||||
|
).readOnly = false;
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantArchiveNote"
|
||||||
|
).hidden = true;
|
||||||
|
|
||||||
|
UpdateAdminPlantShopFields();
|
||||||
|
RenderAdminPlantPreview();
|
||||||
|
SetAdminPlantMessage(
|
||||||
|
"Creating a new plant."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetNextAdminPlantId() {
|
||||||
|
return Math.max(
|
||||||
|
0,
|
||||||
|
...Object.values(
|
||||||
|
AdminPlantCatalogue
|
||||||
|
).map(
|
||||||
|
Plant =>
|
||||||
|
Number(
|
||||||
|
Plant.Id ?? 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function SetAdminPlantField(
|
||||||
|
ElementId,
|
||||||
|
Value
|
||||||
|
) {
|
||||||
|
document.getElementById(
|
||||||
|
ElementId
|
||||||
|
).value =
|
||||||
|
Value ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function UpdateAdminPlantShopFields() {
|
||||||
|
const ShopPlant =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantShopPlant"
|
||||||
|
).checked;
|
||||||
|
|
||||||
|
const BaseCost =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantBaseCost"
|
||||||
|
);
|
||||||
|
|
||||||
|
BaseCost.disabled =
|
||||||
|
!ShopPlant;
|
||||||
|
|
||||||
|
BaseCost.required =
|
||||||
|
ShopPlant;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParseAdminPlantTags() {
|
||||||
|
const Value =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantTags"
|
||||||
|
).value;
|
||||||
|
|
||||||
|
return [
|
||||||
|
...new Set(
|
||||||
|
Value
|
||||||
|
.split(",")
|
||||||
|
.map(
|
||||||
|
Tag =>
|
||||||
|
Tag.trim()
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
Tag =>
|
||||||
|
Tag.length > 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParseAdminPlantEffects() {
|
||||||
|
const Value =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantEffects"
|
||||||
|
).value.trim();
|
||||||
|
|
||||||
|
if (Value === "") {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Effects =
|
||||||
|
JSON.parse(Value);
|
||||||
|
|
||||||
|
if (
|
||||||
|
Effects === null ||
|
||||||
|
typeof Effects !== "object" ||
|
||||||
|
Array.isArray(Effects)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Effects JSON must be an object."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Effects;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetAdminPlantFormData() {
|
||||||
|
const ShopPlant =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantShopPlant"
|
||||||
|
).checked;
|
||||||
|
|
||||||
|
const BaseCostValue =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantBaseCost"
|
||||||
|
).value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
Id: Number(
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantId"
|
||||||
|
).value
|
||||||
|
),
|
||||||
|
|
||||||
|
PlantKey:
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantKey"
|
||||||
|
).value.trim(),
|
||||||
|
|
||||||
|
Name:
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantName"
|
||||||
|
).value.trim(),
|
||||||
|
|
||||||
|
Description:
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantDescription"
|
||||||
|
).value.trim(),
|
||||||
|
|
||||||
|
Tags:
|
||||||
|
ParseAdminPlantTags(),
|
||||||
|
|
||||||
|
GrowthTime: Number(
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantGrowthTime"
|
||||||
|
).value
|
||||||
|
),
|
||||||
|
|
||||||
|
Effects:
|
||||||
|
ParseAdminPlantEffects(),
|
||||||
|
|
||||||
|
ShopPlant: ShopPlant,
|
||||||
|
|
||||||
|
BaseCost:
|
||||||
|
ShopPlant
|
||||||
|
? Number(BaseCostValue)
|
||||||
|
: null,
|
||||||
|
|
||||||
|
IsNew:
|
||||||
|
AdminPlantEditingKey ===
|
||||||
|
null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function SaveAdminPlant(
|
||||||
|
Event
|
||||||
|
) {
|
||||||
|
Event.preventDefault();
|
||||||
|
|
||||||
|
if (AdminPlantSavePending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Form =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantForm"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!Form.reportValidity()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Plant;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Plant =
|
||||||
|
GetAdminPlantFormData();
|
||||||
|
} catch (Error) {
|
||||||
|
SetAdminPlantMessage(
|
||||||
|
Error.message
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminPlantSavePending = true;
|
||||||
|
|
||||||
|
const SaveButton =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantSaveButton"
|
||||||
|
);
|
||||||
|
|
||||||
|
SaveButton.disabled = true;
|
||||||
|
SaveButton.textContent =
|
||||||
|
"Saving...";
|
||||||
|
|
||||||
|
SetAdminPlantMessage(
|
||||||
|
"Saving plant..."
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const Result =
|
||||||
|
await AdminPlantRequest(
|
||||||
|
"Save",
|
||||||
|
{
|
||||||
|
Plant: Plant
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
localStorage.removeItem(
|
||||||
|
"SarahtoninGardenContent"
|
||||||
|
);
|
||||||
|
|
||||||
|
await LoadAdminPlants(
|
||||||
|
Result.PlantKey ??
|
||||||
|
Plant.PlantKey
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantMessage(
|
||||||
|
"Plant saved."
|
||||||
|
);
|
||||||
|
} catch (Error) {
|
||||||
|
console.error(
|
||||||
|
"Couldn't save plant:",
|
||||||
|
Error
|
||||||
|
);
|
||||||
|
|
||||||
|
SetAdminPlantMessage(
|
||||||
|
Error.message ??
|
||||||
|
"Couldn't save plant."
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
AdminPlantSavePending = false;
|
||||||
|
SaveButton.disabled = false;
|
||||||
|
SaveButton.textContent =
|
||||||
|
"Save plant";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RenderAdminPlantPreview() {
|
||||||
|
const PlantKey =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantKey"
|
||||||
|
).value.trim();
|
||||||
|
|
||||||
|
const Name =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantName"
|
||||||
|
).value.trim() ||
|
||||||
|
"New plant";
|
||||||
|
|
||||||
|
const Tile =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantPreviewTile"
|
||||||
|
);
|
||||||
|
|
||||||
|
Tile.replaceChildren();
|
||||||
|
|
||||||
|
const Images =
|
||||||
|
PlantImages[
|
||||||
|
PlantKey
|
||||||
|
] ?? [];
|
||||||
|
|
||||||
|
if (
|
||||||
|
Array.isArray(Images) &&
|
||||||
|
Images.length > 0
|
||||||
|
) {
|
||||||
|
const Image =
|
||||||
|
document.createElement(
|
||||||
|
"img"
|
||||||
|
);
|
||||||
|
|
||||||
|
Image.className =
|
||||||
|
"PlantSprite";
|
||||||
|
|
||||||
|
Image.src =
|
||||||
|
Images[
|
||||||
|
Images.length - 1
|
||||||
|
];
|
||||||
|
|
||||||
|
Image.alt = Name;
|
||||||
|
|
||||||
|
Tile.appendChild(
|
||||||
|
Image
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const Missing =
|
||||||
|
document.createElement(
|
||||||
|
"span"
|
||||||
|
);
|
||||||
|
|
||||||
|
Missing.className =
|
||||||
|
"AdminPlantPreviewMissing";
|
||||||
|
|
||||||
|
Missing.textContent =
|
||||||
|
PlantKey === ""
|
||||||
|
? "No key"
|
||||||
|
: "No sprite";
|
||||||
|
|
||||||
|
Tile.appendChild(
|
||||||
|
Missing
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantPreviewName"
|
||||||
|
).textContent = Name;
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantPreviewKey"
|
||||||
|
).textContent =
|
||||||
|
PlantKey === ""
|
||||||
|
? "Plant Key not set"
|
||||||
|
: PlantKey;
|
||||||
|
|
||||||
|
const GrowthTime = Number(
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantGrowthTime"
|
||||||
|
).value
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantGrowthTimeHint"
|
||||||
|
).textContent =
|
||||||
|
Number.isFinite(GrowthTime)
|
||||||
|
? FormatAdminDuration(
|
||||||
|
GrowthTime
|
||||||
|
)
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FormatAdminDuration(
|
||||||
|
Milliseconds
|
||||||
|
) {
|
||||||
|
Milliseconds = Math.max(
|
||||||
|
0,
|
||||||
|
Number(Milliseconds)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Milliseconds < 1000) {
|
||||||
|
return Milliseconds + "ms";
|
||||||
|
}
|
||||||
|
|
||||||
|
const TotalSeconds =
|
||||||
|
Math.round(
|
||||||
|
Milliseconds /
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
|
||||||
|
const Hours =
|
||||||
|
Math.floor(
|
||||||
|
TotalSeconds /
|
||||||
|
3600
|
||||||
|
);
|
||||||
|
|
||||||
|
const Minutes =
|
||||||
|
Math.floor(
|
||||||
|
(
|
||||||
|
TotalSeconds %
|
||||||
|
3600
|
||||||
|
) /
|
||||||
|
60
|
||||||
|
);
|
||||||
|
|
||||||
|
const Seconds =
|
||||||
|
TotalSeconds % 60;
|
||||||
|
|
||||||
|
const Parts = [];
|
||||||
|
|
||||||
|
if (Hours > 0) {
|
||||||
|
Parts.push(
|
||||||
|
Hours + "h"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Minutes > 0) {
|
||||||
|
Parts.push(
|
||||||
|
Minutes + "m"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Seconds > 0 ||
|
||||||
|
Parts.length === 0
|
||||||
|
) {
|
||||||
|
Parts.push(
|
||||||
|
Seconds + "s"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Parts.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function SetAdminPlantMessage(
|
||||||
|
Message
|
||||||
|
) {
|
||||||
|
const Element =
|
||||||
|
document.getElementById(
|
||||||
|
"AdminPlantMessage"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Element !== null) {
|
||||||
|
Element.textContent =
|
||||||
|
Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
"DOMContentLoaded",
|
||||||
|
StartAdminPlantEditor
|
||||||
|
);
|
||||||
@@ -1722,3 +1722,481 @@ select {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Admin editors
|
||||||
|
*/
|
||||||
|
|
||||||
|
.AdminEditorNavigation {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
repeat(
|
||||||
|
2,
|
||||||
|
minmax(0, 1fr)
|
||||||
|
);
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorLink {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
|
||||||
|
min-height: 7rem;
|
||||||
|
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorLink strong {
|
||||||
|
color: var(--Green);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorLink span {
|
||||||
|
color: var(--Subtext1);
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorToolbar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
auto
|
||||||
|
minmax(0, 1fr)
|
||||||
|
auto;
|
||||||
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
margin: 1.5rem 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorToolbar label {
|
||||||
|
color: var(--Blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorForm {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
repeat(
|
||||||
|
2,
|
||||||
|
minmax(0, 1fr)
|
||||||
|
);
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorField {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorField > span,
|
||||||
|
.AdminMutationDimensions label {
|
||||||
|
color: var(--Blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorField small {
|
||||||
|
color: var(--Overlay2);
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorWideField {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorForm input,
|
||||||
|
.AdminEditorForm textarea,
|
||||||
|
.AdminEditorForm select,
|
||||||
|
.AdminEditorToolbar select,
|
||||||
|
.AdminMutationDimensions input {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0.55rem;
|
||||||
|
|
||||||
|
color: var(--Text);
|
||||||
|
background-color: var(--Surface0);
|
||||||
|
|
||||||
|
border: 3px solid var(--Surface2);
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorForm textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorForm input:focus,
|
||||||
|
.AdminEditorForm textarea:focus,
|
||||||
|
.AdminEditorForm select:focus,
|
||||||
|
.AdminEditorToolbar select:focus,
|
||||||
|
.AdminMutationDimensions input:focus {
|
||||||
|
border-color: var(--Sky);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorForm input:read-only {
|
||||||
|
color: var(--Subtext0);
|
||||||
|
background-color: var(--Base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorForm input:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorCheckField {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.65rem;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
align-self: end;
|
||||||
|
|
||||||
|
min-height: 2.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorCheckField input {
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
accent-color: var(--Green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminInlineButton {
|
||||||
|
width: auto;
|
||||||
|
min-width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminSaveButton {
|
||||||
|
--ActionAccent: var(--Green);
|
||||||
|
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorBackLink {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorActions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorPreview,
|
||||||
|
.AdminMutationRecipeSection,
|
||||||
|
.AdminMutationRelationsSection,
|
||||||
|
.AdminMutationFailureSection {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
|
||||||
|
border-top: 3px solid var(--Surface2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorPreview h2,
|
||||||
|
.AdminMutationRecipeSection h2,
|
||||||
|
.AdminMutationRelationsSection h2,
|
||||||
|
.AdminMutationFailureSection h2 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminPlantPreviewRow {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminPlantPreviewTile {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
width: 7rem;
|
||||||
|
height: 7rem;
|
||||||
|
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminPlantPreviewMissing {
|
||||||
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
color: var(--Overlay1);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#AdminPlantPreviewKey,
|
||||||
|
#AdminPlantArchiveNote,
|
||||||
|
#AdminMutationArchiveNote {
|
||||||
|
margin: 0.35rem 0 0;
|
||||||
|
|
||||||
|
color: var(--Overlay2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#AdminPlantArchiveNote,
|
||||||
|
#AdminMutationArchiveNote {
|
||||||
|
color: var(--Yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationRecipeHeader {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationRecipeHeader p,
|
||||||
|
.AdminMutationRelationsSection > p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
color: var(--Subtext1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationDimensions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.6rem;
|
||||||
|
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationDimensions label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationDimensions input {
|
||||||
|
width: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationRecipeFlow {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
minmax(0, 1fr)
|
||||||
|
auto
|
||||||
|
minmax(0, 1fr);
|
||||||
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationGridPanel {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationGridPanel h3 {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
|
||||||
|
color: var(--Subtext1);
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationGrid {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.5rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationGridCell {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationGridCell:hover {
|
||||||
|
border-color: var(--Sky);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationGridCellSelected {
|
||||||
|
border-color: var(--Yellow) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px
|
||||||
|
var(--Crust);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationCellEditor {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
background-color: var(--Base);
|
||||||
|
border: 3px solid var(--Yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationCellEditor h3 {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
|
||||||
|
color: var(--Yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
#AdminMutationPatternCellControls,
|
||||||
|
#AdminMutationResultCellControls,
|
||||||
|
.AdminMatcherFields {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
repeat(
|
||||||
|
2,
|
||||||
|
minmax(0, 1fr)
|
||||||
|
);
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMatcherFields {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationRelationColumns {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
repeat(
|
||||||
|
2,
|
||||||
|
minmax(0, 1fr)
|
||||||
|
);
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationRelationColumns fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.75rem;
|
||||||
|
|
||||||
|
background-color: var(--Base);
|
||||||
|
border: 3px solid var(--Surface2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationRelationColumns legend {
|
||||||
|
padding: 0 0.35rem;
|
||||||
|
|
||||||
|
color: var(--Peach);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminPlantChecklist {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.45rem;
|
||||||
|
|
||||||
|
max-height: 14rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminPlantChecklistItem {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.55rem;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminPlantChecklistItem input {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
accent-color: var(--Green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminGeneratedJson {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
|
||||||
|
background-color: var(--Base);
|
||||||
|
border: 3px solid var(--Surface2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminGeneratedJson summary {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--Sky);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminGeneratedJson pre {
|
||||||
|
max-height: 24rem;
|
||||||
|
margin: 0.75rem 0 0;
|
||||||
|
padding: 0.75rem;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
color: var(--Subtext1);
|
||||||
|
background-color: var(--Crust);
|
||||||
|
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.AdminEditorNavigation,
|
||||||
|
.AdminEditorGrid,
|
||||||
|
#AdminMutationPatternCellControls,
|
||||||
|
#AdminMutationResultCellControls,
|
||||||
|
.AdminMatcherFields,
|
||||||
|
.AdminMutationRelationColumns {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorToolbar {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminEditorToolbar label {
|
||||||
|
margin-bottom: -0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminInlineButton,
|
||||||
|
.AdminSaveButton {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationRecipeHeader {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationRecipeFlow {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationRecipeFlow >
|
||||||
|
.MutationEncyclopediaRecipeArrow {
|
||||||
|
justify-self: center;
|
||||||
|
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationDimensions {
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationDimensions label {
|
||||||
|
flex: 1 1 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdminMutationDimensions input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user