let AdminMassMutationCatalogue = {}; let AdminMassMutationPlantCatalogue = {}; let AdminMassMutationSavePending = false; async function AdminMassMutationRequest( Endpoint, 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 + "/" + Endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ SaveKey: SaveKey, Action: Action, ...Data }) } ); const Result = await Response.json(); if ( !Response.ok || Result.Success !== true ) { throw new Error( Result.Error ?? "Mutation admin request failed." ); } return Result; } function GetSortedAdminMassMutationKeys() { return Object.keys( AdminMassMutationCatalogue ).sort( (A, B) => Number( AdminMassMutationCatalogue[A] ?.Id ?? 0 ) - Number( AdminMassMutationCatalogue[B] ?.Id ?? 0 ) || A.localeCompare(B) ); } function RenderAdminMassMutationSelection() { const Container = document.getElementById( "AdminMassMutationSelection" ); Container.replaceChildren(); for ( const MutationKey of GetSortedAdminMassMutationKeys() ) { const Mutation = AdminMassMutationCatalogue[ MutationKey ]; const Label = document.createElement("label"); Label.className = "AdminPlantChecklistItem"; const Input = document.createElement("input"); Input.type = "checkbox"; Input.value = MutationKey; Input.dataset.adminMassTarget = "mutation"; const Text = document.createElement("span"); Text.textContent = String(Mutation.Id).padStart( 3, "0" ) + " — " + (Mutation.Name ?? MutationKey) + ( Mutation.Archived === true ? " (archived)" : "" ); Label.append(Input, Text); Container.appendChild(Label); } } function SetAdminMassMutationSelection( Checked ) { for ( const Input of document.querySelectorAll( '[data-admin-mass-target="mutation"]' ) ) { Input.checked = Checked; } } function UpdateAdminMassMutationFieldStates() { const FieldMap = { Name: ["AdminMassMutationName"], Description: [ "AdminMassMutationDescription" ], Hint: ["AdminMassMutationHint"], Priority: [ "AdminMassMutationPriority" ], Chance: ["AdminMassMutationChance"], Cooldown: [ "AdminMassMutationCooldownHours", "AdminMassMutationCooldownMinutes", "AdminMassMutationCooldownSeconds" ], Rotation: [ "AdminMassMutationRotation" ], AllowImmature: [ "AdminMassMutationAllowImmature" ], Lists: [ "AdminMassMutationLists" ], Pattern: [ "AdminMassMutationPattern" ], Success: [ "AdminMassMutationSuccess" ], Failure: [ "AdminMassMutationFailure" ], PlantsUsed: [ "AdminMassMutationPlantsUsed" ], PlantsCreated: [ "AdminMassMutationPlantsCreated" ] }; for ( const Apply of document.querySelectorAll( "[data-admin-mass-apply]" ) ) { const FieldName = Apply.dataset.adminMassApply; for ( const ElementId of FieldMap[FieldName] ?? [] ) { document.getElementById( ElementId ).disabled = !Apply.checked; } } } function AdminMassMutationFieldEnabled( FieldName ) { return document.querySelector( '[data-admin-mass-apply="' + FieldName + '"]' )?.checked === true; } function GetAdminMassMutationSelectedKeys() { return [ ...document.querySelectorAll( '[data-admin-mass-target="mutation"]:checked' ) ].map(Input => Input.value); } function GetAdminMassMutationCooldown() { const Hours = Number( document.getElementById( "AdminMassMutationCooldownHours" ).value ); const Minutes = Number( document.getElementById( "AdminMassMutationCooldownMinutes" ).value ); const Seconds = Number( document.getElementById( "AdminMassMutationCooldownSeconds" ).value ); if ( !Number.isInteger(Hours) || !Number.isInteger(Minutes) || !Number.isInteger(Seconds) || Hours < 0 || Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59 ) { throw new Error( "Cooldown is invalid." ); } return ( Hours * 3600000 + Minutes * 60000 + Seconds * 1000 ); } function ParseAdminMassMutationJson( ElementId, Name ) { const Raw = document.getElementById( ElementId ).value.trim(); try { return JSON.parse(Raw); } catch (Error) { throw new Error( Name + " is not valid JSON." ); } } function ParseAdminMassMutationPlantKeys( ElementId, Name ) { const Keys = [ ...new Set( document.getElementById( ElementId ).value .split(",") .map(Value => Value.trim()) .filter(Value => Value !== "") ) ]; return Keys.map( PlantKey => { const Plant = AdminMassMutationPlantCatalogue[ PlantKey ]; if (Plant === undefined) { throw new Error( Name + " references unknown Plant Key: " + PlantKey ); } return Number(Plant.Id); } ).sort((A, B) => A - B); } function GetAdminMassMutationPatch() { const Patch = {}; if (AdminMassMutationFieldEnabled("Name")) { Patch.Name = document.getElementById( "AdminMassMutationName" ).value.trim(); } if ( AdminMassMutationFieldEnabled( "Description" ) ) { Patch.Description = document.getElementById( "AdminMassMutationDescription" ).value.trim(); } if (AdminMassMutationFieldEnabled("Hint")) { Patch.Hint = document.getElementById( "AdminMassMutationHint" ).value.trim(); } if ( AdminMassMutationFieldEnabled( "Priority" ) ) { Patch.Priority = Number( document.getElementById( "AdminMassMutationPriority" ).value ); } if ( AdminMassMutationFieldEnabled( "Chance" ) ) { Patch.Chance = Number( document.getElementById( "AdminMassMutationChance" ).value ) / 100; } if ( AdminMassMutationFieldEnabled( "Cooldown" ) ) { Patch.Cooldown = GetAdminMassMutationCooldown(); } if ( AdminMassMutationFieldEnabled( "Rotation" ) ) { Patch.Rotation = document.getElementById( "AdminMassMutationRotation" ).value; } if ( AdminMassMutationFieldEnabled( "AllowImmature" ) ) { Patch.AllowImmature = document.getElementById( "AdminMassMutationAllowImmature" ).value === "true"; } if ( AdminMassMutationFieldEnabled( "Lists" ) ) { Patch.Lists = ParseAdminMassMutationJson( "AdminMassMutationLists", "Lists JSON" ); } if ( AdminMassMutationFieldEnabled( "Pattern" ) ) { Patch.Pattern = ParseAdminMassMutationJson( "AdminMassMutationPattern", "Pattern JSON" ); } if ( AdminMassMutationFieldEnabled( "Success" ) ) { Patch.Success = ParseAdminMassMutationJson( "AdminMassMutationSuccess", "Success result JSON" ); } if ( AdminMassMutationFieldEnabled( "Failure" ) ) { Patch.Failure = ParseAdminMassMutationJson( "AdminMassMutationFailure", "Failure result JSON" ); } if ( AdminMassMutationFieldEnabled( "PlantsUsed" ) ) { Patch.PlantsUsed = ParseAdminMassMutationPlantKeys( "AdminMassMutationPlantsUsed", "Plants used" ); } if ( AdminMassMutationFieldEnabled( "PlantsCreated" ) ) { Patch.PlantsCreated = ParseAdminMassMutationPlantKeys( "AdminMassMutationPlantsCreated", "Plants created" ); } return Patch; } function BuildAdminMassMutationPayload( MutationKey, Patch ) { const Source = AdminMassMutationCatalogue[ MutationKey ]; const Mutation = { Id: Number(Source.Id), MutationKey: MutationKey, Name: Source.Name ?? "", Description: Source.Description ?? "", Hint: Source.Hint ?? "", Priority: Number(Source.Priority ?? 0), Chance: Number(Source.Chance ?? 0), Cooldown: Number(Source.Cooldown ?? 0), Rotation: Source.Rotation ?? "None", AllowImmature: Source.AllowImmature === true, Lists: JSON.parse( JSON.stringify( Source.Lists ?? [] ) ), Pattern: JSON.parse( JSON.stringify( Source.Pattern ?? [] ) ), Success: JSON.parse( JSON.stringify( Source.Success ?? [] ) ), Failure: JSON.parse( JSON.stringify( Source.Failure ?? "Keep" ) ), Relations: { PlantsUsed: [ ...(Source.Relations ?.PlantsUsed ?? []) ], PlantsCreated: [ ...(Source.Relations ?.PlantsCreated ?? []) ] }, IsNew: false }; for ( const [Key, Value] of Object.entries(Patch) ) { if ( Key === "PlantsUsed" || Key === "PlantsCreated" ) { Mutation.Relations[Key] = [ ...Value ]; continue; } Mutation[Key] = Value !== null && typeof Value === "object" ? JSON.parse( JSON.stringify(Value) ) : Value; } return Mutation; } async function SaveAdminMassMutations( Event ) { Event.preventDefault(); if (AdminMassMutationSavePending) { return; } const SelectedKeys = GetAdminMassMutationSelectedKeys(); if (SelectedKeys.length === 0) { SetAdminMassMutationMessage( "Mark at least one mutation first." ); return; } let Patch; try { Patch = GetAdminMassMutationPatch(); } catch (Error) { SetAdminMassMutationMessage( Error.message ); return; } if (Object.keys(Patch).length === 0) { SetAdminMassMutationMessage( "Enable at least one field to update." ); return; } const Payloads = []; try { for ( const MutationKey of SelectedKeys ) { const Mutation = BuildAdminMassMutationPayload( MutationKey, Patch ); ValidateAdminMutation( Mutation, AdminMassMutationCatalogue, AdminMassMutationPlantCatalogue, MutationKey ); Payloads.push(Mutation); } } catch (Error) { SetAdminMassMutationMessage( Error.message ); return; } AdminMassMutationSavePending = true; const SaveButton = document.getElementById( "AdminMassMutationSaveButton" ); SaveButton.disabled = true; let SavedCount = 0; try { for (const Mutation of Payloads) { SetAdminMassMutationMessage( "Saving " + (SavedCount + 1) + " of " + Payloads.length + "..." ); await AdminMassMutationRequest( "AdminMutations.php", "Save", {Mutation: Mutation} ); SavedCount++; } localStorage.removeItem( "SarahtoninGardenContent" ); await LoadAdminMassMutationData(); SetAdminMassMutationMessage( SavedCount === 1 ? "1 mutation updated." : SavedCount + " mutations updated." ); } catch (Error) { SetAdminMassMutationMessage( "Saved " + SavedCount + " before stopping: " + (Error.message ?? "Couldn't save mutations.") ); } finally { AdminMassMutationSavePending = false; SaveButton.disabled = false; } } function BindAdminMassMutationEditor() { document.getElementById( "AdminMassMutationSelectAll" ).addEventListener( "click", () => SetAdminMassMutationSelection( true ) ); document.getElementById( "AdminMassMutationClear" ).addEventListener( "click", () => SetAdminMassMutationSelection( false ) ); document.getElementById( "AdminMassMutationForm" ).addEventListener( "submit", SaveAdminMassMutations ); for ( const Apply of document.querySelectorAll( "[data-admin-mass-apply]" ) ) { Apply.addEventListener( "change", UpdateAdminMassMutationFieldStates ); } UpdateAdminMassMutationFieldStates(); } async function LoadAdminMassMutationData() { const [MutationResult, PlantResult] = await Promise.all([ AdminMassMutationRequest( "AdminMutations.php", "List" ), AdminMassMutationRequest( "AdminPlants.php", "List" ) ]); AdminMassMutationCatalogue = MutationResult.Mutations ?? {}; AdminMassMutationPlantCatalogue = PlantResult.Plants ?? {}; RenderAdminMassMutationSelection(); } function SetAdminMassMutationMessage( Message ) { document.getElementById( "AdminMassMutationMessage" ).textContent = Message; } async function StartAdminMassMutationEditor() { try { const Status = await GetAdminStatus(); if ( Status.Success !== true || Status.IsAdmin !== true ) { SetAdminMassMutationMessage( "Admin access required." ); return; } BindAdminMassMutationEditor(); await LoadAdminMassMutationData(); SetAdminMassMutationMessage( "Mark mutations and enable the fields you want to change." ); } catch (Error) { console.error( "Couldn't start Mass mutation editor:", Error ); SetAdminMassMutationMessage( Error.message ?? "Couldn't load the Mass mutation editor." ); } } document.addEventListener( "DOMContentLoaded", StartAdminMassMutationEditor );