mirror of
https://git.srhtnin.garden/sarah/srhtnin.github.io.git
synced 2026-10-13 18:38:11 +02:00
3785 lines
75 KiB
JavaScript
3785 lines
75 KiB
JavaScript
let AdminTransferPlants = {};
|
|
let AdminTransferMutations = {};
|
|
let AdminTransferImportItems = [];
|
|
let AdminTransferExportSelection = new Set();
|
|
let AdminTransferExportSearch = "";
|
|
let AdminTransferSavePending = false;
|
|
|
|
|
|
async function StartAdminContentTransfer() {
|
|
const ImportButton =
|
|
document.getElementById(
|
|
"AdminMassImportButton"
|
|
);
|
|
|
|
if (ImportButton === null) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const Status =
|
|
await GetAdminStatus();
|
|
|
|
if (
|
|
Status.Success !== true ||
|
|
Status.IsAdmin !== true
|
|
) {
|
|
return;
|
|
}
|
|
|
|
BindAdminContentTransfer();
|
|
await LoadAdminTransferCatalogues();
|
|
} catch (Error) {
|
|
console.error(
|
|
"Couldn't start Admin content transfer:",
|
|
Error
|
|
);
|
|
|
|
SetAdminTransferImportMessage(
|
|
Error.message ??
|
|
"Couldn't load mass import and export."
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
function BindAdminContentTransfer() {
|
|
document.getElementById(
|
|
"AdminMassImportButton"
|
|
).addEventListener(
|
|
"click",
|
|
() => {
|
|
document.getElementById(
|
|
"AdminMassImportFiles"
|
|
).click();
|
|
}
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassImportChooseButton"
|
|
).addEventListener(
|
|
"click",
|
|
() => {
|
|
document.getElementById(
|
|
"AdminMassImportFiles"
|
|
).click();
|
|
}
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassImportFiles"
|
|
).addEventListener(
|
|
"change",
|
|
ImportAdminContentFiles
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassImportSaveButton"
|
|
).addEventListener(
|
|
"click",
|
|
SaveAdminImportedContent
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassImportCloseButton"
|
|
).addEventListener(
|
|
"click",
|
|
() => HideAdminTransferPanel(
|
|
"AdminMassImportPanel"
|
|
)
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassExportButton"
|
|
).addEventListener(
|
|
"click",
|
|
ShowAdminMassExport
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassExportCloseButton"
|
|
).addEventListener(
|
|
"click",
|
|
() => HideAdminTransferPanel(
|
|
"AdminMassExportPanel"
|
|
)
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassExportSearchInput"
|
|
).addEventListener(
|
|
"input",
|
|
Event => {
|
|
AdminTransferExportSearch =
|
|
Event.target.value;
|
|
|
|
RenderAdminMassExport();
|
|
}
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassExportSelectAllButton"
|
|
).addEventListener(
|
|
"click",
|
|
() => SelectAdminTransferExport(
|
|
"All"
|
|
)
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassExportSelectPlantsButton"
|
|
).addEventListener(
|
|
"click",
|
|
() => SelectAdminTransferExport(
|
|
"Plant"
|
|
)
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassExportSelectMutationsButton"
|
|
).addEventListener(
|
|
"click",
|
|
() => SelectAdminTransferExport(
|
|
"Mutation"
|
|
)
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassExportDeselectAllButton"
|
|
).addEventListener(
|
|
"click",
|
|
() => {
|
|
AdminTransferExportSelection.clear();
|
|
RenderAdminMassExport();
|
|
}
|
|
);
|
|
|
|
document.getElementById(
|
|
"AdminMassExportSaveButton"
|
|
).addEventListener(
|
|
"click",
|
|
ExportAdminSelectedContent
|
|
);
|
|
}
|
|
|
|
|
|
async function AdminTransferRequest(
|
|
Endpoint,
|
|
Action = "List",
|
|
Data = {}
|
|
) {
|
|
const SaveKey =
|
|
GetStoredAdminSaveKey();
|
|
|
|
if (SaveKey === null) {
|
|
throw new Error(
|
|
"A valid Account Key is required."
|
|
);
|
|
}
|
|
|
|
const Response = await fetch(
|
|
AdminApiUrl + "/" + Endpoint,
|
|
{
|
|
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(
|
|
"Admin API returned invalid JSON."
|
|
);
|
|
}
|
|
|
|
if (
|
|
!Response.ok ||
|
|
Result.Success !== true
|
|
) {
|
|
throw new Error(
|
|
Result.Error ??
|
|
"Admin request failed."
|
|
);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
async function LoadAdminTransferCatalogues() {
|
|
const [
|
|
PlantResult,
|
|
MutationResult
|
|
] = await Promise.all([
|
|
AdminTransferRequest(
|
|
"AdminPlants.php"
|
|
),
|
|
AdminTransferRequest(
|
|
"AdminMutations.php"
|
|
)
|
|
]);
|
|
|
|
AdminTransferPlants =
|
|
PlantResult.Plants ?? {};
|
|
|
|
AdminTransferMutations =
|
|
MutationResult.Mutations ?? {};
|
|
|
|
Plants = AdminTransferPlants;
|
|
MutationSets = AdminTransferMutations;
|
|
}
|
|
|
|
|
|
function ShowAdminTransferPanel(
|
|
PanelId
|
|
) {
|
|
for (
|
|
const Id
|
|
of [
|
|
"AdminMassImportPanel",
|
|
"AdminMassExportPanel"
|
|
]
|
|
) {
|
|
const Panel =
|
|
document.getElementById(Id);
|
|
|
|
if (Panel !== null) {
|
|
Panel.hidden =
|
|
Id !== PanelId;
|
|
}
|
|
}
|
|
|
|
document.getElementById(
|
|
PanelId
|
|
)?.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "start"
|
|
});
|
|
}
|
|
|
|
|
|
function HideAdminTransferPanel(
|
|
PanelId
|
|
) {
|
|
const Panel =
|
|
document.getElementById(
|
|
PanelId
|
|
);
|
|
|
|
if (Panel !== null) {
|
|
Panel.hidden = true;
|
|
}
|
|
}
|
|
|
|
|
|
async function ImportAdminContentFiles(
|
|
Event
|
|
) {
|
|
const Input = Event.target;
|
|
const Files = [
|
|
...(Input.files ?? [])
|
|
];
|
|
|
|
Input.value = "";
|
|
|
|
if (Files.length === 0) {
|
|
return;
|
|
}
|
|
|
|
ShowAdminTransferPanel(
|
|
"AdminMassImportPanel"
|
|
);
|
|
|
|
SetAdminTransferImportMessage(
|
|
"Reading " +
|
|
Files.length.toLocaleString() +
|
|
(Files.length === 1
|
|
? " file..."
|
|
: " files...")
|
|
);
|
|
|
|
AdminTransferImportItems = [];
|
|
|
|
for (const File of Files) {
|
|
AdminTransferImportItems.push(
|
|
await ParseAdminTransferFile(
|
|
File
|
|
)
|
|
);
|
|
}
|
|
|
|
PrepareAdminTransferImportItems();
|
|
RenderAdminMassImport();
|
|
}
|
|
|
|
|
|
async function ParseAdminTransferFile(
|
|
File
|
|
) {
|
|
try {
|
|
const DocumentData =
|
|
await ReadAdminJsonFile(
|
|
File
|
|
);
|
|
|
|
if (!IsAdminPlainObject(
|
|
DocumentData
|
|
)) {
|
|
throw new Error(
|
|
"JSON must contain an object."
|
|
);
|
|
}
|
|
|
|
let Type =
|
|
typeof DocumentData.Type ===
|
|
"string"
|
|
? DocumentData.Type
|
|
.trim()
|
|
: "";
|
|
|
|
if (Type === "") {
|
|
if (
|
|
IsAdminPlainObject(
|
|
DocumentData.Plant
|
|
) ||
|
|
DocumentData.PlantKey !==
|
|
undefined
|
|
) {
|
|
Type = "Plant";
|
|
} else if (
|
|
IsAdminPlainObject(
|
|
DocumentData.Mutation
|
|
) ||
|
|
DocumentData.MutationKey !==
|
|
undefined
|
|
) {
|
|
Type = "Mutation";
|
|
}
|
|
}
|
|
|
|
if (
|
|
Type !== "Plant" &&
|
|
Type !== "Mutation"
|
|
) {
|
|
throw new Error(
|
|
"JSON must be a Plant or Mutation export."
|
|
);
|
|
}
|
|
|
|
if (
|
|
DocumentData.Version !==
|
|
undefined &&
|
|
Number(
|
|
DocumentData.Version
|
|
) !== 1
|
|
) {
|
|
throw new Error(
|
|
"Unsupported " +
|
|
Type.toLowerCase() +
|
|
" JSON version."
|
|
);
|
|
}
|
|
|
|
const Imported =
|
|
IsAdminPlainObject(
|
|
DocumentData[Type]
|
|
)
|
|
? DocumentData[Type]
|
|
: DocumentData;
|
|
|
|
if (!IsAdminPlainObject(
|
|
Imported
|
|
)) {
|
|
throw new Error(
|
|
Type +
|
|
" JSON must contain a " +
|
|
Type.toLowerCase() +
|
|
" object."
|
|
);
|
|
}
|
|
|
|
return {
|
|
FileName: File.name,
|
|
Type: Type,
|
|
Raw: Imported,
|
|
Key: String(
|
|
Imported[
|
|
Type === "Plant"
|
|
? "PlantKey"
|
|
: "MutationKey"
|
|
] ?? ""
|
|
).trim(),
|
|
Data: null,
|
|
Existing: false,
|
|
Error: null,
|
|
Saved: false,
|
|
SaveError: null
|
|
};
|
|
} catch (Error) {
|
|
return {
|
|
FileName: File.name,
|
|
Type: null,
|
|
Raw: null,
|
|
Key: "",
|
|
Data: null,
|
|
Existing: false,
|
|
Error:
|
|
Error.message ??
|
|
"Couldn't read JSON file.",
|
|
Saved: false,
|
|
SaveError: null
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
function PrepareAdminTransferImportItems() {
|
|
const DuplicateCounts =
|
|
new Map();
|
|
|
|
for (
|
|
const Item
|
|
of AdminTransferImportItems
|
|
) {
|
|
if (
|
|
Item.Error !== null ||
|
|
Item.Type === null ||
|
|
Item.Key === ""
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
const DuplicateKey =
|
|
Item.Type + ":" +
|
|
Item.Key;
|
|
|
|
DuplicateCounts.set(
|
|
DuplicateKey,
|
|
(
|
|
DuplicateCounts.get(
|
|
DuplicateKey
|
|
) ?? 0
|
|
) + 1
|
|
);
|
|
}
|
|
|
|
for (
|
|
const Item
|
|
of AdminTransferImportItems
|
|
) {
|
|
if (
|
|
Item.Error !== null ||
|
|
Item.Type === null
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
if (Item.Key === "") {
|
|
Item.Error =
|
|
Item.Type +
|
|
" Key cannot be empty.";
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
DuplicateCounts.get(
|
|
Item.Type + ":" +
|
|
Item.Key
|
|
) > 1
|
|
) {
|
|
Item.Error =
|
|
"The batch contains more than one " +
|
|
Item.Type.toLowerCase() +
|
|
" with key " +
|
|
Item.Key + ".";
|
|
}
|
|
}
|
|
|
|
const PlantCatalogue =
|
|
CloneAdminTransferValue(
|
|
AdminTransferPlants
|
|
);
|
|
|
|
let NextPlantId =
|
|
GetNextAdminTransferId(
|
|
AdminTransferPlants
|
|
);
|
|
|
|
for (
|
|
const Item
|
|
of AdminTransferImportItems
|
|
) {
|
|
if (
|
|
Item.Type !== "Plant" ||
|
|
Item.Error !== null
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
const Existing =
|
|
AdminTransferPlants[
|
|
Item.Key
|
|
];
|
|
|
|
Item.Existing =
|
|
Existing !== undefined;
|
|
|
|
try {
|
|
const Plant =
|
|
NormalizeAdminTransferPlant(
|
|
Item.Raw,
|
|
Item.Key,
|
|
Existing,
|
|
Existing?.Id ??
|
|
NextPlantId
|
|
);
|
|
|
|
ValidateAdminPlant(
|
|
Plant,
|
|
PlantCatalogue,
|
|
Item.Existing
|
|
? Item.Key
|
|
: null
|
|
);
|
|
|
|
Item.Data = Plant;
|
|
PlantCatalogue[
|
|
Item.Key
|
|
] = CloneAdminTransferValue(
|
|
Plant
|
|
);
|
|
|
|
if (!Item.Existing) {
|
|
NextPlantId++;
|
|
}
|
|
} catch (Error) {
|
|
Item.Error =
|
|
Error.message ??
|
|
"Plant validation failed.";
|
|
}
|
|
}
|
|
|
|
const MutationCatalogue =
|
|
CloneAdminTransferValue(
|
|
AdminTransferMutations
|
|
);
|
|
|
|
let NextMutationId =
|
|
GetNextAdminTransferId(
|
|
AdminTransferMutations
|
|
);
|
|
|
|
for (
|
|
const Item
|
|
of AdminTransferImportItems
|
|
) {
|
|
if (
|
|
Item.Type !== "Mutation" ||
|
|
Item.Error !== null
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
const Existing =
|
|
AdminTransferMutations[
|
|
Item.Key
|
|
];
|
|
|
|
Item.Existing =
|
|
Existing !== undefined;
|
|
|
|
try {
|
|
const Mutation =
|
|
NormalizeAdminTransferMutation(
|
|
Item.Raw,
|
|
Item.Key,
|
|
Existing,
|
|
Existing?.Id ??
|
|
NextMutationId,
|
|
PlantCatalogue
|
|
);
|
|
|
|
ValidateAdminMutation(
|
|
Mutation,
|
|
MutationCatalogue,
|
|
PlantCatalogue,
|
|
Item.Existing
|
|
? Item.Key
|
|
: null
|
|
);
|
|
|
|
Item.Data = Mutation;
|
|
MutationCatalogue[
|
|
Item.Key
|
|
] = CloneAdminTransferValue(
|
|
Mutation
|
|
);
|
|
|
|
if (!Item.Existing) {
|
|
NextMutationId++;
|
|
}
|
|
} catch (Error) {
|
|
Item.Error =
|
|
Error.message ??
|
|
"Mutation validation failed.";
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function NormalizeAdminTransferPlant(
|
|
Imported,
|
|
PlantKey,
|
|
Existing,
|
|
PlantId
|
|
) {
|
|
const Shop =
|
|
IsAdminPlainObject(
|
|
Imported.Shop
|
|
)
|
|
? Imported.Shop
|
|
: {};
|
|
|
|
const ShopPlant =
|
|
Shop.ShopPlant === true ||
|
|
Imported.ShopPlant === true;
|
|
|
|
return {
|
|
Id: Number(PlantId),
|
|
PlantKey: PlantKey,
|
|
Name: String(
|
|
Imported.Name ?? ""
|
|
).trim(),
|
|
Description:
|
|
typeof Imported.Description ===
|
|
"string"
|
|
? Imported.Description.trim()
|
|
: "",
|
|
Tags:
|
|
Array.isArray(
|
|
Imported.Tags
|
|
)
|
|
? Imported.Tags.map(
|
|
Tag =>
|
|
String(Tag).trim()
|
|
)
|
|
: [],
|
|
GrowthTime: Number(
|
|
Imported.GrowthTime
|
|
),
|
|
HarvestMultiplier: Number(
|
|
Imported.HarvestMultiplier ??
|
|
1.5
|
|
),
|
|
Effects:
|
|
IsAdminPlainObject(
|
|
Imported.Effects
|
|
)
|
|
? CloneAdminTransferValue(
|
|
Imported.Effects
|
|
)
|
|
: {},
|
|
DirectionalSprites:
|
|
Imported.DirectionalSprites ===
|
|
true,
|
|
ShopPlant: ShopPlant,
|
|
BaseCost:
|
|
ShopPlant
|
|
? Number(
|
|
Shop.BaseCost ??
|
|
Imported.BaseCost
|
|
)
|
|
: null,
|
|
IsNew: Existing === undefined
|
|
};
|
|
}
|
|
|
|
|
|
function NormalizeAdminTransferMutation(
|
|
Imported,
|
|
MutationKey,
|
|
Existing,
|
|
MutationId,
|
|
PlantCatalogue
|
|
) {
|
|
return {
|
|
Id: Number(MutationId),
|
|
MutationKey: MutationKey,
|
|
Name: String(
|
|
Imported.Name ?? ""
|
|
).trim(),
|
|
Description:
|
|
typeof Imported.Description ===
|
|
"string"
|
|
? Imported.Description.trim()
|
|
: "",
|
|
Hint:
|
|
typeof Imported.Hint ===
|
|
"string"
|
|
? Imported.Hint.trim()
|
|
: "",
|
|
Priority: Number(
|
|
Imported.Priority ?? 0
|
|
),
|
|
Chance: Number(
|
|
Imported.Chance
|
|
),
|
|
Cooldown: Number(
|
|
Imported.Cooldown
|
|
),
|
|
Rotation:
|
|
Imported.Rotation ??
|
|
"None",
|
|
AllowImmature:
|
|
Imported.AllowImmature ===
|
|
true,
|
|
Lists:
|
|
CloneAdminTransferValue(
|
|
Imported.Lists ?? []
|
|
),
|
|
Pattern:
|
|
CloneAdminTransferValue(
|
|
Imported.Pattern
|
|
),
|
|
Success:
|
|
CloneAdminTransferValue(
|
|
Imported.Success
|
|
),
|
|
Failure:
|
|
CloneAdminTransferValue(
|
|
Imported.Failure ??
|
|
"Keep"
|
|
),
|
|
Relations:
|
|
ConvertAdminTransferRelations(
|
|
Imported.Relations,
|
|
PlantCatalogue
|
|
),
|
|
IsNew: Existing === undefined
|
|
};
|
|
}
|
|
|
|
|
|
function ConvertAdminTransferRelations(
|
|
Relations,
|
|
PlantCatalogue
|
|
) {
|
|
if (
|
|
Relations === undefined ||
|
|
Relations === null
|
|
) {
|
|
return {
|
|
PlantsUsed: [],
|
|
PlantsCreated: []
|
|
};
|
|
}
|
|
|
|
if (!IsAdminPlainObject(
|
|
Relations
|
|
)) {
|
|
throw new Error(
|
|
"Relations must be an object."
|
|
);
|
|
}
|
|
|
|
const Result = {
|
|
PlantsUsed: [],
|
|
PlantsCreated: []
|
|
};
|
|
|
|
for (
|
|
const RelationName
|
|
of [
|
|
"PlantsUsed",
|
|
"PlantsCreated"
|
|
]
|
|
) {
|
|
const Values =
|
|
Relations[RelationName] ?? [];
|
|
|
|
if (!Array.isArray(Values)) {
|
|
throw new Error(
|
|
RelationName +
|
|
" must be an array of Plant Keys."
|
|
);
|
|
}
|
|
|
|
for (const Value of Values) {
|
|
if (typeof Value !== "string") {
|
|
throw new Error(
|
|
RelationName +
|
|
" must use Plant Keys, not numeric IDs."
|
|
);
|
|
}
|
|
|
|
const Plant =
|
|
PlantCatalogue[Value];
|
|
|
|
if (Plant === undefined) {
|
|
throw new Error(
|
|
RelationName +
|
|
" references unknown Plant Key: " +
|
|
Value
|
|
);
|
|
}
|
|
|
|
Result[RelationName].push(
|
|
Number(Plant.Id)
|
|
);
|
|
}
|
|
|
|
Result[RelationName] = [
|
|
...new Set(
|
|
Result[RelationName]
|
|
)
|
|
].sort(
|
|
(A, B) => A - B
|
|
);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
function GetNextAdminTransferId(
|
|
Catalogue
|
|
) {
|
|
return Math.max(
|
|
0,
|
|
...Object.values(
|
|
Catalogue ?? {}
|
|
).map(
|
|
Item =>
|
|
Number(
|
|
Item?.Id ?? 0
|
|
)
|
|
)
|
|
) + 1;
|
|
}
|
|
|
|
|
|
function GetAdminTransferPreviewCatalogues() {
|
|
const PreviewPlants =
|
|
CloneAdminTransferValue(
|
|
AdminTransferPlants
|
|
);
|
|
|
|
const PreviewMutations =
|
|
CloneAdminTransferValue(
|
|
AdminTransferMutations
|
|
);
|
|
|
|
for (
|
|
const Item
|
|
of AdminTransferImportItems
|
|
) {
|
|
if (
|
|
Item.Error !== null ||
|
|
Item.Data === null ||
|
|
Item.Saved === true
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
if (Item.Type === "Plant") {
|
|
PreviewPlants[Item.Key] =
|
|
GetAdminTransferDisplayPlant(
|
|
Item.Key,
|
|
Item.Data,
|
|
AdminTransferPlants[
|
|
Item.Key
|
|
]
|
|
);
|
|
} else if (
|
|
Item.Type === "Mutation"
|
|
) {
|
|
PreviewMutations[Item.Key] =
|
|
GetAdminTransferDisplayMutation(
|
|
Item.Key,
|
|
Item.Data
|
|
);
|
|
}
|
|
}
|
|
|
|
for (
|
|
const [PlantKey, Plant]
|
|
of Object.entries(
|
|
PreviewPlants
|
|
)
|
|
) {
|
|
PreviewPlants[PlantKey] =
|
|
GetAdminTransferDisplayPlant(
|
|
PlantKey,
|
|
Plant,
|
|
AdminTransferPlants[
|
|
PlantKey
|
|
]
|
|
);
|
|
}
|
|
|
|
return {
|
|
Plants: PreviewPlants,
|
|
Mutations: PreviewMutations
|
|
};
|
|
}
|
|
|
|
|
|
function GetAdminTransferDisplayPlant(
|
|
PlantKey,
|
|
Plant,
|
|
Existing = null
|
|
) {
|
|
const Shop =
|
|
IsAdminPlainObject(
|
|
Plant.Shop
|
|
)
|
|
? Plant.Shop
|
|
: {};
|
|
|
|
const ShopPlant =
|
|
Plant.ShopPlant === true ||
|
|
Shop.ShopPlant === true;
|
|
|
|
return {
|
|
...Plant,
|
|
PlantKey: PlantKey,
|
|
Shop: {
|
|
ShopPlant: ShopPlant,
|
|
BaseCost:
|
|
ShopPlant
|
|
? (
|
|
Plant.BaseCost ??
|
|
Shop.BaseCost ??
|
|
null
|
|
)
|
|
: null
|
|
},
|
|
ImageStages:
|
|
Plant.ImageStages ??
|
|
Existing?.ImageStages ??
|
|
[],
|
|
DirectionalImageStages:
|
|
Plant.DirectionalImageStages ??
|
|
Existing
|
|
?.DirectionalImageStages ??
|
|
[]
|
|
};
|
|
}
|
|
|
|
|
|
function GetAdminTransferDisplayMutation(
|
|
MutationKey,
|
|
Mutation
|
|
) {
|
|
return {
|
|
...Mutation,
|
|
MutationKey: MutationKey
|
|
};
|
|
}
|
|
|
|
|
|
function RenderAdminMassImport() {
|
|
const List =
|
|
document.getElementById(
|
|
"AdminMassImportList"
|
|
);
|
|
|
|
List.replaceChildren();
|
|
|
|
const Preview =
|
|
GetAdminTransferPreviewCatalogues();
|
|
|
|
Plants = Preview.Plants;
|
|
MutationSets = Preview.Mutations;
|
|
|
|
for (
|
|
const Item
|
|
of AdminTransferImportItems
|
|
) {
|
|
if (
|
|
Item.Error !== null ||
|
|
Item.Data === null
|
|
) {
|
|
List.appendChild(
|
|
CreateAdminTransferErrorCard(
|
|
Item
|
|
)
|
|
);
|
|
|
|
continue;
|
|
}
|
|
|
|
let Card;
|
|
|
|
if (Item.Type === "Plant") {
|
|
Card =
|
|
CreateAdminTransferPlantCard(
|
|
Item.Key,
|
|
Preview.Plants[
|
|
Item.Key
|
|
],
|
|
Preview.Plants,
|
|
Preview.Mutations
|
|
);
|
|
} else {
|
|
Card =
|
|
CreateAdminTransferMutationCard(
|
|
Item.Key,
|
|
Preview.Mutations[
|
|
Item.Key
|
|
],
|
|
Preview.Plants
|
|
);
|
|
}
|
|
|
|
AddAdminTransferCardStatus(
|
|
Card,
|
|
Item.Saved
|
|
? "Saved"
|
|
: Item.Existing
|
|
? "Update"
|
|
: "New",
|
|
Item.FileName,
|
|
Item.SaveError
|
|
);
|
|
|
|
if (Item.SaveError !== null) {
|
|
Card.classList.add(
|
|
"AdminTransferCardInvalid"
|
|
);
|
|
}
|
|
|
|
List.appendChild(Card);
|
|
}
|
|
|
|
const ReadyPlants =
|
|
AdminTransferImportItems.filter(
|
|
Item =>
|
|
Item.Type === "Plant" &&
|
|
Item.Error === null &&
|
|
Item.Data !== null &&
|
|
Item.Saved !== true
|
|
).length;
|
|
|
|
const ReadyMutations =
|
|
AdminTransferImportItems.filter(
|
|
Item =>
|
|
Item.Type === "Mutation" &&
|
|
Item.Error === null &&
|
|
Item.Data !== null &&
|
|
Item.Saved !== true
|
|
).length;
|
|
|
|
const Valid =
|
|
ReadyPlants +
|
|
ReadyMutations;
|
|
|
|
const Invalid =
|
|
AdminTransferImportItems.filter(
|
|
Item =>
|
|
Item.Error !== null ||
|
|
Item.SaveError !== null
|
|
).length;
|
|
|
|
const SavedPlants =
|
|
AdminTransferImportItems.filter(
|
|
Item =>
|
|
Item.Type === "Plant" &&
|
|
Item.Saved === true
|
|
).length;
|
|
|
|
const SavedMutations =
|
|
AdminTransferImportItems.filter(
|
|
Item =>
|
|
Item.Type === "Mutation" &&
|
|
Item.Saved === true
|
|
).length;
|
|
|
|
const Saved =
|
|
SavedPlants +
|
|
SavedMutations;
|
|
|
|
const SummaryParts = [];
|
|
|
|
if (ReadyPlants > 0) {
|
|
SummaryParts.push(
|
|
ReadyPlants.toLocaleString() +
|
|
(ReadyPlants === 1
|
|
? " plant ready"
|
|
: " plants ready")
|
|
);
|
|
}
|
|
|
|
if (ReadyMutations > 0) {
|
|
SummaryParts.push(
|
|
ReadyMutations.toLocaleString() +
|
|
(ReadyMutations === 1
|
|
? " mutation ready"
|
|
: " mutations ready")
|
|
);
|
|
}
|
|
|
|
if (SavedPlants > 0) {
|
|
SummaryParts.push(
|
|
SavedPlants.toLocaleString() +
|
|
(SavedPlants === 1
|
|
? " plant saved"
|
|
: " plants saved")
|
|
);
|
|
}
|
|
|
|
if (SavedMutations > 0) {
|
|
SummaryParts.push(
|
|
SavedMutations.toLocaleString() +
|
|
(SavedMutations === 1
|
|
? " mutation saved"
|
|
: " mutations saved")
|
|
);
|
|
}
|
|
|
|
if (Invalid > 0) {
|
|
SummaryParts.push(
|
|
Invalid.toLocaleString() +
|
|
" invalid"
|
|
);
|
|
}
|
|
|
|
document.getElementById(
|
|
"AdminMassImportSummary"
|
|
).textContent =
|
|
SummaryParts.join(" · ") ||
|
|
"No files selected";
|
|
|
|
const SaveButton =
|
|
document.getElementById(
|
|
"AdminMassImportSaveButton"
|
|
);
|
|
|
|
SaveButton.disabled =
|
|
AdminTransferSavePending ||
|
|
Valid === 0;
|
|
|
|
if (
|
|
AdminTransferImportItems.length === 0
|
|
) {
|
|
SetAdminTransferImportMessage(
|
|
"Choose JSON files to review them here."
|
|
);
|
|
} else if (Invalid > 0) {
|
|
SetAdminTransferImportMessage(
|
|
"Invalid files are skipped. Valid files can still be saved."
|
|
);
|
|
} else if (Saved === 0) {
|
|
SetAdminTransferImportMessage(
|
|
"Review the imported content, then save when ready."
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
function CreateAdminTransferErrorCard(
|
|
Item
|
|
) {
|
|
const Card =
|
|
document.createElement(
|
|
"article"
|
|
);
|
|
|
|
Card.className =
|
|
"Panel AdminTransferErrorCard";
|
|
|
|
const Header =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Header.className =
|
|
"PanelHeader AdminTransferErrorHeader";
|
|
|
|
const Title =
|
|
document.createElement(
|
|
"h2"
|
|
);
|
|
|
|
Title.textContent =
|
|
Item.Key ||
|
|
Item.FileName;
|
|
|
|
const Status =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
Status.className =
|
|
"AdminTransferStatus AdminTransferStatusError";
|
|
|
|
Status.textContent =
|
|
"Invalid";
|
|
|
|
Header.append(
|
|
Title,
|
|
Status
|
|
);
|
|
|
|
const Body =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Body.className =
|
|
"AdminTransferErrorBody";
|
|
|
|
const FileName =
|
|
document.createElement(
|
|
"p"
|
|
);
|
|
|
|
FileName.className =
|
|
"AdminTransferFileName";
|
|
|
|
FileName.textContent =
|
|
Item.FileName;
|
|
|
|
const ErrorText =
|
|
document.createElement(
|
|
"p"
|
|
);
|
|
|
|
ErrorText.textContent =
|
|
Item.Error ??
|
|
Item.SaveError ??
|
|
"Unknown import error.";
|
|
|
|
Body.append(
|
|
FileName,
|
|
ErrorText
|
|
);
|
|
|
|
Card.append(
|
|
Header,
|
|
Body
|
|
);
|
|
|
|
return Card;
|
|
}
|
|
|
|
|
|
async function SaveAdminImportedContent() {
|
|
if (AdminTransferSavePending) {
|
|
return;
|
|
}
|
|
|
|
const PlantsToSave =
|
|
AdminTransferImportItems.filter(
|
|
Item =>
|
|
Item.Type === "Plant" &&
|
|
Item.Error === null &&
|
|
Item.Data !== null &&
|
|
Item.Saved !== true
|
|
);
|
|
|
|
const MutationsToSave =
|
|
AdminTransferImportItems.filter(
|
|
Item =>
|
|
Item.Type === "Mutation" &&
|
|
Item.Error === null &&
|
|
Item.Data !== null &&
|
|
Item.Saved !== true
|
|
);
|
|
|
|
if (
|
|
PlantsToSave.length === 0 &&
|
|
MutationsToSave.length === 0
|
|
) {
|
|
return;
|
|
}
|
|
|
|
AdminTransferSavePending = true;
|
|
|
|
const SaveButton =
|
|
document.getElementById(
|
|
"AdminMassImportSaveButton"
|
|
);
|
|
|
|
SaveButton.disabled = true;
|
|
SaveButton.textContent =
|
|
"Saving...";
|
|
|
|
SetAdminTransferImportMessage(
|
|
"Saving imported content..."
|
|
);
|
|
|
|
let SavedCount = 0;
|
|
let FailedCount = 0;
|
|
|
|
for (const Item of PlantsToSave) {
|
|
try {
|
|
await AdminTransferRequest(
|
|
"AdminPlants.php",
|
|
"Save",
|
|
{
|
|
Plant: Item.Data
|
|
}
|
|
);
|
|
|
|
Item.Saved = true;
|
|
Item.SaveError = null;
|
|
SavedCount++;
|
|
} catch (Error) {
|
|
Item.SaveError =
|
|
Error.message ??
|
|
"Couldn't save plant.";
|
|
FailedCount++;
|
|
}
|
|
}
|
|
|
|
for (const Item of MutationsToSave) {
|
|
try {
|
|
await AdminTransferRequest(
|
|
"AdminMutations.php",
|
|
"Save",
|
|
{
|
|
Mutation: Item.Data
|
|
}
|
|
);
|
|
|
|
Item.Saved = true;
|
|
Item.SaveError = null;
|
|
SavedCount++;
|
|
} catch (Error) {
|
|
Item.SaveError =
|
|
Error.message ??
|
|
"Couldn't save mutation.";
|
|
FailedCount++;
|
|
}
|
|
}
|
|
|
|
try {
|
|
localStorage.removeItem(
|
|
"SarahtoninGardenContent"
|
|
);
|
|
|
|
await LoadAdminTransferCatalogues();
|
|
|
|
if (
|
|
typeof StartAdminOverview ===
|
|
"function"
|
|
) {
|
|
await StartAdminOverview();
|
|
}
|
|
} catch (Error) {
|
|
console.error(
|
|
"Couldn't refresh Admin content after import:",
|
|
Error
|
|
);
|
|
}
|
|
|
|
AdminTransferSavePending = false;
|
|
SaveButton.textContent =
|
|
"Save imported content";
|
|
|
|
RenderAdminMassImport();
|
|
|
|
if (FailedCount === 0) {
|
|
SetAdminTransferImportMessage(
|
|
SavedCount.toLocaleString() +
|
|
(SavedCount === 1
|
|
? " item saved."
|
|
: " items saved.")
|
|
);
|
|
} else {
|
|
SetAdminTransferImportMessage(
|
|
SavedCount.toLocaleString() +
|
|
" saved, " +
|
|
FailedCount.toLocaleString() +
|
|
" failed. Failed items are shown above."
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
function ShowAdminMassExport() {
|
|
AdminTransferExportSearch = "";
|
|
|
|
const SearchInput =
|
|
document.getElementById(
|
|
"AdminMassExportSearchInput"
|
|
);
|
|
|
|
SearchInput.value = "";
|
|
|
|
ShowAdminTransferPanel(
|
|
"AdminMassExportPanel"
|
|
);
|
|
|
|
RenderAdminMassExport();
|
|
}
|
|
|
|
|
|
function RenderAdminMassExport() {
|
|
const List =
|
|
document.getElementById(
|
|
"AdminMassExportList"
|
|
);
|
|
|
|
List.replaceChildren();
|
|
|
|
Plants = AdminTransferPlants;
|
|
MutationSets = AdminTransferMutations;
|
|
|
|
const Query =
|
|
AdminTransferExportSearch
|
|
.trim()
|
|
.toLocaleLowerCase();
|
|
|
|
const Items =
|
|
GetAdminTransferExportItems()
|
|
.filter(
|
|
Item =>
|
|
DoesAdminTransferItemMatch(
|
|
Item,
|
|
Query
|
|
)
|
|
);
|
|
|
|
for (const Item of Items) {
|
|
let Card;
|
|
|
|
if (Item.Type === "Plant") {
|
|
Card =
|
|
CreateAdminTransferPlantCard(
|
|
Item.Key,
|
|
Item.Data,
|
|
AdminTransferPlants,
|
|
AdminTransferMutations
|
|
);
|
|
} else {
|
|
Card =
|
|
CreateAdminTransferMutationCard(
|
|
Item.Key,
|
|
Item.Data,
|
|
AdminTransferPlants
|
|
);
|
|
}
|
|
|
|
DecorateAdminTransferExportCard(
|
|
Card,
|
|
Item
|
|
);
|
|
|
|
List.appendChild(Card);
|
|
}
|
|
|
|
const PlantCount = [
|
|
...AdminTransferExportSelection
|
|
].filter(
|
|
Key => Key.startsWith(
|
|
"Plant:"
|
|
)
|
|
).length;
|
|
|
|
const MutationCount = [
|
|
...AdminTransferExportSelection
|
|
].filter(
|
|
Key => Key.startsWith(
|
|
"Mutation:"
|
|
)
|
|
).length;
|
|
|
|
document.getElementById(
|
|
"AdminMassExportSummary"
|
|
).textContent =
|
|
PlantCount.toLocaleString() +
|
|
(PlantCount === 1
|
|
? " plant"
|
|
: " plants") +
|
|
" · " +
|
|
MutationCount.toLocaleString() +
|
|
(MutationCount === 1
|
|
? " mutation"
|
|
: " mutations");
|
|
|
|
document.getElementById(
|
|
"AdminMassExportSaveButton"
|
|
).disabled =
|
|
AdminTransferExportSelection
|
|
.size === 0;
|
|
|
|
SetAdminTransferExportMessage(
|
|
Items.length === 0
|
|
? "No plants or mutations match your search."
|
|
: AdminTransferExportSelection.size === 0
|
|
? "Click cards to select content for export."
|
|
: AdminTransferExportSelection.size
|
|
.toLocaleString() +
|
|
" selected."
|
|
);
|
|
}
|
|
|
|
|
|
function GetAdminTransferExportItems() {
|
|
const Items = [];
|
|
|
|
for (
|
|
const [PlantKey, Plant]
|
|
of Object.entries(
|
|
AdminTransferPlants
|
|
).sort(
|
|
(A, B) =>
|
|
Number(A[1].Id) -
|
|
Number(B[1].Id) ||
|
|
A[0].localeCompare(B[0])
|
|
)
|
|
) {
|
|
Items.push({
|
|
Type: "Plant",
|
|
Key: PlantKey,
|
|
Data: Plant
|
|
});
|
|
}
|
|
|
|
for (
|
|
const [MutationKey, Mutation]
|
|
of Object.entries(
|
|
AdminTransferMutations
|
|
).sort(
|
|
(A, B) =>
|
|
Number(A[1].Id) -
|
|
Number(B[1].Id) ||
|
|
A[0].localeCompare(B[0])
|
|
)
|
|
) {
|
|
Items.push({
|
|
Type: "Mutation",
|
|
Key: MutationKey,
|
|
Data: Mutation
|
|
});
|
|
}
|
|
|
|
return Items;
|
|
}
|
|
|
|
|
|
function DoesAdminTransferItemMatch(
|
|
Item,
|
|
Query
|
|
) {
|
|
if (Query === "") {
|
|
return true;
|
|
}
|
|
|
|
const SearchParts = [
|
|
Item.Type,
|
|
Item.Key,
|
|
Item.Data.Id,
|
|
Item.Data.Name,
|
|
Item.Data.Description,
|
|
Item.Data.Hint,
|
|
...(Array.isArray(
|
|
Item.Data.Tags
|
|
)
|
|
? Item.Data.Tags
|
|
: [])
|
|
];
|
|
|
|
return SearchParts
|
|
.filter(
|
|
Value =>
|
|
Value !== undefined &&
|
|
Value !== null
|
|
)
|
|
.join(" ")
|
|
.toLocaleLowerCase()
|
|
.includes(Query);
|
|
}
|
|
|
|
|
|
function DecorateAdminTransferExportCard(
|
|
Card,
|
|
Item
|
|
) {
|
|
const SelectionKey =
|
|
Item.Type + ":" +
|
|
Item.Key;
|
|
|
|
const Header =
|
|
Card.querySelector(
|
|
".PanelHeader"
|
|
);
|
|
|
|
const Toggle =
|
|
document.createElement(
|
|
"label"
|
|
);
|
|
|
|
Toggle.className =
|
|
"AdminTransferSelectToggle";
|
|
|
|
const Checkbox =
|
|
document.createElement(
|
|
"input"
|
|
);
|
|
|
|
Checkbox.type = "checkbox";
|
|
Checkbox.checked =
|
|
AdminTransferExportSelection.has(
|
|
SelectionKey
|
|
);
|
|
|
|
const Label =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
Label.textContent =
|
|
Item.Type;
|
|
|
|
Toggle.append(
|
|
Checkbox,
|
|
Label
|
|
);
|
|
|
|
Header.appendChild(Toggle);
|
|
|
|
Card.classList.add(
|
|
"AdminTransferSelectableCard"
|
|
);
|
|
|
|
Card.dataset.selected =
|
|
String(Checkbox.checked);
|
|
|
|
Checkbox.addEventListener(
|
|
"change",
|
|
() => {
|
|
SetAdminTransferExportSelection(
|
|
SelectionKey,
|
|
Checkbox.checked
|
|
);
|
|
}
|
|
);
|
|
|
|
Card.addEventListener(
|
|
"click",
|
|
Event => {
|
|
if (
|
|
Event.target.closest(
|
|
"input, label, a, button"
|
|
) !== null
|
|
) {
|
|
return;
|
|
}
|
|
|
|
Checkbox.checked =
|
|
!Checkbox.checked;
|
|
|
|
SetAdminTransferExportSelection(
|
|
SelectionKey,
|
|
Checkbox.checked
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
|
|
function SetAdminTransferExportSelection(
|
|
SelectionKey,
|
|
Selected
|
|
) {
|
|
if (Selected) {
|
|
AdminTransferExportSelection.add(
|
|
SelectionKey
|
|
);
|
|
} else {
|
|
AdminTransferExportSelection.delete(
|
|
SelectionKey
|
|
);
|
|
}
|
|
|
|
RenderAdminMassExport();
|
|
}
|
|
|
|
|
|
function SelectAdminTransferExport(
|
|
Type
|
|
) {
|
|
for (
|
|
const Item
|
|
of GetAdminTransferExportItems()
|
|
) {
|
|
if (
|
|
Type !== "All" &&
|
|
Item.Type !== Type
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
AdminTransferExportSelection.add(
|
|
Item.Type + ":" +
|
|
Item.Key
|
|
);
|
|
}
|
|
|
|
RenderAdminMassExport();
|
|
}
|
|
|
|
|
|
function ExportAdminSelectedContent() {
|
|
const Files = [];
|
|
|
|
for (
|
|
const SelectionKey
|
|
of AdminTransferExportSelection
|
|
) {
|
|
const Separator =
|
|
SelectionKey.indexOf(":");
|
|
|
|
const Type =
|
|
SelectionKey.slice(
|
|
0,
|
|
Separator
|
|
);
|
|
|
|
const Key =
|
|
SelectionKey.slice(
|
|
Separator + 1
|
|
);
|
|
|
|
if (Type === "Plant") {
|
|
const Plant =
|
|
AdminTransferPlants[Key];
|
|
|
|
if (Plant === undefined) {
|
|
continue;
|
|
}
|
|
|
|
Files.push({
|
|
Name:
|
|
"Plant-" +
|
|
Key +
|
|
".json",
|
|
Text:
|
|
JSON.stringify(
|
|
{
|
|
Type: "Plant",
|
|
Version: 1,
|
|
Plant:
|
|
GetAdminTransferPortablePlant(
|
|
Key,
|
|
Plant
|
|
)
|
|
},
|
|
null,
|
|
4
|
|
) + "\n"
|
|
});
|
|
|
|
continue;
|
|
}
|
|
|
|
if (Type === "Mutation") {
|
|
const Mutation =
|
|
AdminTransferMutations[
|
|
Key
|
|
];
|
|
|
|
if (Mutation === undefined) {
|
|
continue;
|
|
}
|
|
|
|
Files.push({
|
|
Name:
|
|
"Mutation-" +
|
|
Key +
|
|
".json",
|
|
Text:
|
|
JSON.stringify(
|
|
{
|
|
Type: "Mutation",
|
|
Version: 1,
|
|
Mutation:
|
|
GetAdminTransferPortableMutation(
|
|
Key,
|
|
Mutation
|
|
)
|
|
},
|
|
null,
|
|
4
|
|
) + "\n"
|
|
});
|
|
}
|
|
}
|
|
|
|
if (Files.length === 0) {
|
|
SetAdminTransferExportMessage(
|
|
"Select at least one plant or mutation."
|
|
);
|
|
return;
|
|
}
|
|
|
|
DownloadAdminTransferZip(
|
|
"Garden-Content-Export.zip",
|
|
Files
|
|
);
|
|
|
|
SetAdminTransferExportMessage(
|
|
Files.length.toLocaleString() +
|
|
(Files.length === 1
|
|
? " JSON file exported."
|
|
: " JSON files exported.")
|
|
);
|
|
}
|
|
|
|
|
|
function GetAdminTransferPortablePlant(
|
|
PlantKey,
|
|
Plant
|
|
) {
|
|
return {
|
|
PlantKey: PlantKey,
|
|
Name: Plant.Name,
|
|
Description:
|
|
Plant.Description ?? "",
|
|
Tags:
|
|
Array.isArray(Plant.Tags)
|
|
? [...Plant.Tags]
|
|
: [],
|
|
GrowthTime:
|
|
Number(Plant.GrowthTime),
|
|
HarvestMultiplier:
|
|
Number(
|
|
Plant.HarvestMultiplier ??
|
|
1.5
|
|
),
|
|
Effects:
|
|
CloneAdminTransferValue(
|
|
Plant.Effects ?? {}
|
|
),
|
|
DirectionalSprites:
|
|
Plant.DirectionalSprites ===
|
|
true,
|
|
Shop: {
|
|
ShopPlant:
|
|
Plant.Shop?.ShopPlant ===
|
|
true,
|
|
BaseCost:
|
|
Plant.Shop?.ShopPlant ===
|
|
true
|
|
? Plant.Shop.BaseCost
|
|
: null
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
function GetAdminTransferPortableMutation(
|
|
MutationKey,
|
|
Mutation
|
|
) {
|
|
return {
|
|
MutationKey: MutationKey,
|
|
Name: Mutation.Name,
|
|
Description:
|
|
Mutation.Description ?? "",
|
|
Hint:
|
|
typeof Mutation.Hint ===
|
|
"string" &&
|
|
Mutation.Hint.length > 0
|
|
? Mutation.Hint
|
|
: null,
|
|
Priority: Number(
|
|
Mutation.Priority ?? 0
|
|
),
|
|
Chance: Number(
|
|
Mutation.Chance
|
|
),
|
|
Cooldown: Number(
|
|
Mutation.Cooldown
|
|
),
|
|
Rotation:
|
|
Mutation.Rotation ??
|
|
"None",
|
|
AllowImmature:
|
|
Mutation.AllowImmature ===
|
|
true,
|
|
Lists:
|
|
CloneAdminTransferValue(
|
|
Mutation.Lists ?? []
|
|
),
|
|
Pattern:
|
|
CloneAdminTransferValue(
|
|
Mutation.Pattern
|
|
),
|
|
Success:
|
|
CloneAdminTransferValue(
|
|
Mutation.Success
|
|
),
|
|
Failure:
|
|
CloneAdminTransferValue(
|
|
Mutation.Failure ??
|
|
"Keep"
|
|
),
|
|
Relations:
|
|
GetAdminTransferPortableRelations(
|
|
Mutation.Relations
|
|
)
|
|
};
|
|
}
|
|
|
|
|
|
function GetAdminTransferPortableRelations(
|
|
Relations
|
|
) {
|
|
const Result = {
|
|
PlantsUsed: [],
|
|
PlantsCreated: []
|
|
};
|
|
|
|
for (
|
|
const RelationName
|
|
of [
|
|
"PlantsUsed",
|
|
"PlantsCreated"
|
|
]
|
|
) {
|
|
for (
|
|
const PlantId
|
|
of Relations?.[
|
|
RelationName
|
|
] ?? []
|
|
) {
|
|
const PlantKey =
|
|
GetAdminTransferPlantKeyById(
|
|
PlantId,
|
|
AdminTransferPlants
|
|
);
|
|
|
|
if (PlantKey !== null) {
|
|
Result[RelationName].push(
|
|
PlantKey
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
function GetAdminTransferPlantKeyById(
|
|
PlantId,
|
|
PlantCatalogue
|
|
) {
|
|
return Object.entries(
|
|
PlantCatalogue ?? {}
|
|
).find(
|
|
([, Plant]) =>
|
|
Number(Plant.Id) ===
|
|
Number(PlantId)
|
|
)?.[0] ?? null;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferPlantCard(
|
|
PlantKey,
|
|
Plant,
|
|
PlantCatalogue,
|
|
MutationCatalogue
|
|
) {
|
|
const Card =
|
|
document.createElement(
|
|
"article"
|
|
);
|
|
|
|
Card.className =
|
|
"Panel PlantEncyclopediaCard AdminTransferCard";
|
|
|
|
const Header =
|
|
document.createElement(
|
|
"header"
|
|
);
|
|
|
|
Header.className =
|
|
"PanelHeader PlantEncyclopediaHeader AdminTransferCardHeader";
|
|
|
|
const NumberElement =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
NumberElement.className =
|
|
"PlantEncyclopediaNumber";
|
|
|
|
NumberElement.textContent =
|
|
String(
|
|
Plant.Id
|
|
).padStart(
|
|
3,
|
|
"0"
|
|
);
|
|
|
|
const NameElement =
|
|
document.createElement(
|
|
"h2"
|
|
);
|
|
|
|
NameElement.textContent =
|
|
Plant.Name;
|
|
|
|
Header.append(
|
|
NumberElement,
|
|
NameElement
|
|
);
|
|
|
|
const Body =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Body.className =
|
|
"PlantEncyclopediaBody";
|
|
|
|
Body.append(
|
|
CreateAdminTransferPlantVisual(
|
|
PlantKey,
|
|
Plant
|
|
),
|
|
CreateAdminTransferPlantDetails(
|
|
Plant,
|
|
PlantCatalogue,
|
|
MutationCatalogue
|
|
),
|
|
CreateAdminTransferPlantRelations(
|
|
Plant.Id,
|
|
MutationCatalogue
|
|
)
|
|
);
|
|
|
|
Card.append(
|
|
Header,
|
|
Body
|
|
);
|
|
|
|
return Card;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferPlantVisual(
|
|
PlantKey,
|
|
Plant
|
|
) {
|
|
const Visual =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Visual.className =
|
|
"PlantTile PlantEncyclopediaVisual";
|
|
|
|
const MatureImage =
|
|
GetPlantMatureImageSource(
|
|
PlantKey
|
|
);
|
|
|
|
if (MatureImage === null) {
|
|
const Missing =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
Missing.className =
|
|
"PlantEncyclopediaMissing";
|
|
Missing.textContent =
|
|
"No image";
|
|
|
|
Visual.appendChild(Missing);
|
|
return Visual;
|
|
}
|
|
|
|
const Image =
|
|
document.createElement(
|
|
"img"
|
|
);
|
|
|
|
Image.className =
|
|
"PlantSprite";
|
|
Image.src = MatureImage;
|
|
Image.alt = Plant.Name;
|
|
|
|
Visual.appendChild(Image);
|
|
return Visual;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferPlantDetails(
|
|
Plant,
|
|
PlantCatalogue,
|
|
MutationCatalogue
|
|
) {
|
|
const Details =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Details.className =
|
|
"PlantEncyclopediaDetails";
|
|
|
|
const Description =
|
|
document.createElement(
|
|
"p"
|
|
);
|
|
|
|
Description.className =
|
|
"PlantEncyclopediaDescription";
|
|
Description.textContent =
|
|
Plant.Description ||
|
|
"No description.";
|
|
|
|
Details.appendChild(Description);
|
|
|
|
const Tags =
|
|
Array.isArray(Plant.Tags)
|
|
? Plant.Tags
|
|
: [];
|
|
|
|
const Cost =
|
|
GetCataloguePlantCostInfo(
|
|
Plant.Id,
|
|
PlantCatalogue,
|
|
MutationCatalogue
|
|
)?.Cost ?? null;
|
|
|
|
const Multiplier =
|
|
Number(
|
|
Plant.HarvestMultiplier ??
|
|
1.5
|
|
);
|
|
|
|
const Reward =
|
|
Cost === null
|
|
? null
|
|
: Math.ceil(
|
|
Cost * Multiplier
|
|
);
|
|
|
|
const GrowthTime =
|
|
Number(
|
|
Plant.GrowthTime
|
|
);
|
|
|
|
const Dph =
|
|
Cost === null ||
|
|
Reward === null ||
|
|
!Number.isFinite(GrowthTime) ||
|
|
GrowthTime <= 0
|
|
? null
|
|
: (Reward - Cost) *
|
|
3600000 /
|
|
GrowthTime;
|
|
|
|
Details.append(
|
|
CreateAdminTransferStat(
|
|
"Plant Key",
|
|
Plant.PlantKey ?? ""
|
|
),
|
|
CreateAdminTransferStat(
|
|
"Tags",
|
|
Tags.length === 0
|
|
? "None"
|
|
: Tags.join(", ")
|
|
),
|
|
CreateAdminTransferStat(
|
|
"Growth",
|
|
FormatAdminTransferDuration(
|
|
GrowthTime
|
|
)
|
|
),
|
|
CreateAdminTransferStat(
|
|
"Cost",
|
|
Cost === null
|
|
? "Unavailable"
|
|
: Cost.toLocaleString() +
|
|
" Dew"
|
|
),
|
|
CreateAdminTransferStat(
|
|
"Reward",
|
|
Reward === null
|
|
? "Unavailable"
|
|
: Reward.toLocaleString() +
|
|
" Dew"
|
|
),
|
|
CreateAdminTransferStat(
|
|
"Dew / hour",
|
|
Dph === null
|
|
? "Unavailable"
|
|
: Dph.toLocaleString(
|
|
undefined,
|
|
{
|
|
maximumFractionDigits:
|
|
2
|
|
}
|
|
)
|
|
)
|
|
);
|
|
|
|
return Details;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferStat(
|
|
Name,
|
|
Value
|
|
) {
|
|
const Row =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Row.className =
|
|
"PlantEncyclopediaStat";
|
|
|
|
const NameElement =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
NameElement.className =
|
|
"PlantEncyclopediaStatName";
|
|
NameElement.textContent = Name;
|
|
|
|
const ValueElement =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
ValueElement.textContent =
|
|
Value;
|
|
|
|
Row.append(
|
|
NameElement,
|
|
ValueElement
|
|
);
|
|
|
|
return Row;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferPlantRelations(
|
|
PlantId,
|
|
MutationCatalogue
|
|
) {
|
|
const Relations =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Relations.className =
|
|
"PlantEncyclopediaRelations";
|
|
|
|
const CreatedBy = [];
|
|
const UsedIn = [];
|
|
|
|
for (
|
|
const [MutationKey, Mutation]
|
|
of Object.entries(
|
|
MutationCatalogue
|
|
)
|
|
) {
|
|
if (
|
|
Mutation.Relations
|
|
?.PlantsCreated
|
|
?.includes(
|
|
Number(PlantId)
|
|
)
|
|
) {
|
|
CreatedBy.push({
|
|
Key: MutationKey,
|
|
Name:
|
|
Mutation.Name ??
|
|
MutationKey
|
|
});
|
|
}
|
|
|
|
if (
|
|
Mutation.Relations
|
|
?.PlantsUsed
|
|
?.includes(
|
|
Number(PlantId)
|
|
)
|
|
) {
|
|
UsedIn.push({
|
|
Key: MutationKey,
|
|
Name:
|
|
Mutation.Name ??
|
|
MutationKey
|
|
});
|
|
}
|
|
}
|
|
|
|
Relations.append(
|
|
CreateAdminTransferRelationGroup(
|
|
"Created by",
|
|
CreatedBy
|
|
),
|
|
CreateAdminTransferRelationGroup(
|
|
"Used in",
|
|
UsedIn
|
|
)
|
|
);
|
|
|
|
return Relations;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferRelationGroup(
|
|
Heading,
|
|
Entries
|
|
) {
|
|
const Group =
|
|
document.createElement(
|
|
"section"
|
|
);
|
|
|
|
Group.className =
|
|
"PlantEncyclopediaRelationGroup";
|
|
|
|
const Title =
|
|
document.createElement(
|
|
"h3"
|
|
);
|
|
|
|
Title.textContent = Heading;
|
|
|
|
const List =
|
|
document.createElement(
|
|
"ul"
|
|
);
|
|
|
|
List.className =
|
|
"PlantEncyclopediaRelationList";
|
|
|
|
if (Entries.length === 0) {
|
|
const Empty =
|
|
document.createElement(
|
|
"li"
|
|
);
|
|
|
|
Empty.className =
|
|
"PlantEncyclopediaRelationEmpty";
|
|
Empty.textContent = "None";
|
|
List.appendChild(Empty);
|
|
} else {
|
|
for (
|
|
const Entry
|
|
of Entries.sort(
|
|
(A, B) =>
|
|
A.Name.localeCompare(
|
|
B.Name
|
|
)
|
|
)
|
|
) {
|
|
const Item =
|
|
document.createElement(
|
|
"li"
|
|
);
|
|
|
|
Item.textContent =
|
|
Entry.Name;
|
|
|
|
List.appendChild(Item);
|
|
}
|
|
}
|
|
|
|
Group.append(
|
|
Title,
|
|
List
|
|
);
|
|
|
|
return Group;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferMutationCard(
|
|
MutationKey,
|
|
Mutation,
|
|
PlantCatalogue
|
|
) {
|
|
const Card =
|
|
document.createElement(
|
|
"article"
|
|
);
|
|
|
|
Card.className =
|
|
"Panel MutationEncyclopediaCard AdminTransferCard";
|
|
|
|
const Header =
|
|
document.createElement(
|
|
"header"
|
|
);
|
|
|
|
Header.className =
|
|
"PanelHeader MutationEncyclopediaHeader AdminTransferCardHeader";
|
|
|
|
const NumberElement =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
NumberElement.className =
|
|
"MutationEncyclopediaNumber";
|
|
NumberElement.textContent =
|
|
String(
|
|
Mutation.Id
|
|
).padStart(
|
|
3,
|
|
"0"
|
|
);
|
|
|
|
const NameElement =
|
|
document.createElement(
|
|
"h2"
|
|
);
|
|
|
|
NameElement.textContent =
|
|
Mutation.Name;
|
|
|
|
Header.append(
|
|
NumberElement,
|
|
NameElement
|
|
);
|
|
|
|
const Body =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Body.className =
|
|
"MutationEncyclopediaBody";
|
|
|
|
const Description =
|
|
document.createElement(
|
|
"p"
|
|
);
|
|
|
|
Description.className =
|
|
"MutationEncyclopediaDescription";
|
|
Description.textContent =
|
|
Mutation.Description ||
|
|
"No description.";
|
|
|
|
Body.append(
|
|
Description,
|
|
CreateAdminTransferMutationStats(
|
|
MutationKey,
|
|
Mutation
|
|
),
|
|
CreateAdminTransferMutationRecipe(
|
|
Mutation,
|
|
PlantCatalogue
|
|
),
|
|
CreateAdminTransferMutationRelations(
|
|
Mutation,
|
|
PlantCatalogue
|
|
)
|
|
);
|
|
|
|
Card.append(
|
|
Header,
|
|
Body
|
|
);
|
|
|
|
return Card;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferMutationStats(
|
|
MutationKey,
|
|
Mutation
|
|
) {
|
|
const Stats =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Stats.className =
|
|
"MutationEncyclopediaStats";
|
|
|
|
Stats.append(
|
|
CreateAdminTransferStat(
|
|
"Mutation Key",
|
|
MutationKey
|
|
),
|
|
CreateAdminTransferStat(
|
|
"Chance",
|
|
FormatAdminTransferChance(
|
|
Mutation.Chance
|
|
)
|
|
),
|
|
CreateAdminTransferStat(
|
|
"Cooldown",
|
|
FormatAdminTransferDuration(
|
|
Mutation.Cooldown,
|
|
true
|
|
)
|
|
),
|
|
CreateAdminTransferStat(
|
|
"Rotation",
|
|
Mutation.Rotation ??
|
|
"None"
|
|
),
|
|
CreateAdminTransferStat(
|
|
"Mature plants",
|
|
Mutation.AllowImmature ===
|
|
true
|
|
? "Not required"
|
|
: "Required"
|
|
)
|
|
);
|
|
|
|
return Stats;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferMutationRecipe(
|
|
Mutation,
|
|
PlantCatalogue
|
|
) {
|
|
const Section =
|
|
document.createElement(
|
|
"section"
|
|
);
|
|
|
|
Section.className =
|
|
"MutationEncyclopediaRecipeSection";
|
|
|
|
const Title =
|
|
document.createElement(
|
|
"h3"
|
|
);
|
|
|
|
Title.textContent = "Recipe";
|
|
|
|
const Pattern =
|
|
NormalizeAdminTransferMatrix(
|
|
Mutation.Pattern,
|
|
null,
|
|
null,
|
|
"Any"
|
|
);
|
|
|
|
if (Pattern.length === 0) {
|
|
const Missing =
|
|
document.createElement(
|
|
"p"
|
|
);
|
|
|
|
Missing.className =
|
|
"MutationEncyclopediaMissingRecipe";
|
|
Missing.textContent =
|
|
"No recipe data.";
|
|
|
|
Section.append(
|
|
Title,
|
|
Missing
|
|
);
|
|
|
|
return Section;
|
|
}
|
|
|
|
const Success =
|
|
NormalizeAdminTransferMatrix(
|
|
Mutation.Success,
|
|
Pattern[0].length,
|
|
Pattern.length,
|
|
"Keep"
|
|
);
|
|
|
|
const ListOffsets =
|
|
CreateMutationRecipePreviewOffsets(
|
|
Pattern,
|
|
Success
|
|
);
|
|
|
|
const Flow =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Flow.className =
|
|
"MutationEncyclopediaRecipeFlow";
|
|
|
|
const Arrow =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
Arrow.className =
|
|
"MutationEncyclopediaRecipeArrow";
|
|
Arrow.textContent = "→";
|
|
Arrow.setAttribute(
|
|
"aria-hidden",
|
|
"true"
|
|
);
|
|
|
|
Flow.append(
|
|
CreateAdminTransferMutationRecipePanel(
|
|
"Arrange",
|
|
Pattern,
|
|
"Pattern",
|
|
PlantCatalogue,
|
|
Mutation.Lists,
|
|
ListOffsets.Pattern,
|
|
Mutation.AllowImmature === true
|
|
),
|
|
Arrow,
|
|
CreateAdminTransferMutationRecipePanel(
|
|
"Result",
|
|
Success,
|
|
"Result",
|
|
PlantCatalogue,
|
|
Mutation.Lists,
|
|
ListOffsets.Result,
|
|
false
|
|
)
|
|
);
|
|
|
|
Section.append(
|
|
Title,
|
|
Flow
|
|
);
|
|
|
|
const Lists =
|
|
CreateAdminTransferMutationLists(
|
|
Mutation.Lists,
|
|
PlantCatalogue
|
|
);
|
|
|
|
if (Lists !== null) {
|
|
Section.appendChild(Lists);
|
|
}
|
|
|
|
return Section;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferMutationLists(
|
|
Lists,
|
|
PlantCatalogue
|
|
) {
|
|
if (
|
|
!Array.isArray(Lists) ||
|
|
Lists.length === 0
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const Container =
|
|
document.createElement("div");
|
|
|
|
Container.className =
|
|
"MutationRecipeLists";
|
|
|
|
const Heading =
|
|
document.createElement("h4");
|
|
|
|
Heading.textContent = "Lists";
|
|
|
|
const OrderedList =
|
|
document.createElement("ol");
|
|
|
|
for (const List of Lists) {
|
|
const ListItem =
|
|
document.createElement("li");
|
|
|
|
const Mode =
|
|
document.createElement("strong");
|
|
|
|
Mode.textContent =
|
|
List?.Mode === "Any"
|
|
? "Any: "
|
|
: "Once: ";
|
|
|
|
const Labels =
|
|
Array.isArray(List?.Items)
|
|
? List.Items.map(
|
|
Item => {
|
|
if (Item?.Type === "Plant") {
|
|
return PlantCatalogue?.[
|
|
Item.Value
|
|
]?.Name ??
|
|
Item.Value ??
|
|
"Unknown plant";
|
|
}
|
|
|
|
if (Item?.Type === "Tag") {
|
|
return "Tag: " +
|
|
(Item.Value ?? "");
|
|
}
|
|
|
|
if (Item?.Type === "Empty") {
|
|
return "Empty";
|
|
}
|
|
|
|
return "Any";
|
|
}
|
|
)
|
|
: [];
|
|
|
|
ListItem.append(
|
|
Mode,
|
|
document.createTextNode(
|
|
Labels.length > 0
|
|
? Labels.join(", ")
|
|
: "Empty list"
|
|
)
|
|
);
|
|
|
|
OrderedList.appendChild(
|
|
ListItem
|
|
);
|
|
}
|
|
|
|
Container.append(
|
|
Heading,
|
|
OrderedList
|
|
);
|
|
|
|
return Container;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferMutationRecipePanel(
|
|
Heading,
|
|
Matrix,
|
|
Mode,
|
|
PlantCatalogue,
|
|
Lists,
|
|
ListOffsets,
|
|
CyclePlantStages = false
|
|
) {
|
|
const Panel =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Panel.className =
|
|
"MutationEncyclopediaRecipePanel";
|
|
|
|
const Title =
|
|
document.createElement(
|
|
"h4"
|
|
);
|
|
|
|
Title.textContent = Heading;
|
|
|
|
const Grid =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Grid.className =
|
|
"GuideRecipe MutationRecipeGrid";
|
|
Grid.style.setProperty(
|
|
"--RecipeWidth",
|
|
String(
|
|
Matrix[0]?.length ?? 1
|
|
)
|
|
);
|
|
|
|
for (
|
|
let Y = 0;
|
|
Y < Matrix.length;
|
|
Y++
|
|
) {
|
|
for (
|
|
let X = 0;
|
|
X < Matrix[Y].length;
|
|
X++
|
|
) {
|
|
const Value =
|
|
Matrix[Y][X];
|
|
|
|
const Cell =
|
|
CreateAdminTransferMutationCell(
|
|
Value,
|
|
Mode,
|
|
PlantCatalogue,
|
|
CyclePlantStages
|
|
);
|
|
|
|
const ListNumber =
|
|
GetMutationRecipePreviewListReference(
|
|
Value
|
|
);
|
|
|
|
if (ListNumber !== null) {
|
|
RegisterMutationRecipePreviewListCell(
|
|
Cell,
|
|
Lists?.[ListNumber - 1],
|
|
ListNumber,
|
|
ListOffsets?.[Y]?.[X],
|
|
PlantCatalogue,
|
|
"MutationRecipeText",
|
|
Mode === "Pattern" &&
|
|
CyclePlantStages
|
|
);
|
|
}
|
|
|
|
Grid.appendChild(Cell);
|
|
}
|
|
}
|
|
|
|
Panel.append(
|
|
Title,
|
|
Grid
|
|
);
|
|
|
|
return Panel;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferMutationCell(
|
|
Value,
|
|
Mode,
|
|
PlantCatalogue,
|
|
CyclePlantStages = false
|
|
) {
|
|
if (Mode === "Result") {
|
|
if (
|
|
Value === null ||
|
|
Value === "Keep"
|
|
) {
|
|
return CreateAdminTransferTextCell(
|
|
"Keep",
|
|
"MutationRecipeKeep"
|
|
);
|
|
}
|
|
|
|
if (Value === "Empty") {
|
|
return CreateAdminTransferTextCell(
|
|
"Empty",
|
|
"GuideRecipeEmpty"
|
|
);
|
|
}
|
|
|
|
const PlantValue =
|
|
typeof Value === "string"
|
|
? Value
|
|
: IsAdminPlainObject(
|
|
Value
|
|
) &&
|
|
typeof Value.Plant ===
|
|
"string"
|
|
? Value.Plant
|
|
: null;
|
|
|
|
if (PlantValue === null) {
|
|
return CreateAdminTransferTextCell(
|
|
"Keep",
|
|
"MutationRecipeKeep"
|
|
);
|
|
}
|
|
|
|
if (PlantValue.startsWith("$")) {
|
|
return CreateAdminTransferTextCell(
|
|
PlantValue.slice(1),
|
|
"MutationRecipeCapture"
|
|
);
|
|
}
|
|
|
|
const ListMatch =
|
|
/^List:([1-9][0-9]*)$/.exec(
|
|
PlantValue
|
|
);
|
|
|
|
if (ListMatch !== null) {
|
|
return CreateAdminTransferTextCell(
|
|
"List " + ListMatch[1],
|
|
"MutationRecipeMatcher"
|
|
);
|
|
}
|
|
|
|
return CreateAdminTransferPlantCell(
|
|
PlantValue,
|
|
PlantCatalogue,
|
|
CyclePlantStages
|
|
);
|
|
}
|
|
|
|
if (
|
|
Value === null ||
|
|
Value === "Any"
|
|
) {
|
|
return CreateAdminTransferTextCell(
|
|
"Any",
|
|
"MutationRecipeAny"
|
|
);
|
|
}
|
|
|
|
if (Value === "Empty") {
|
|
return CreateAdminTransferTextCell(
|
|
"Empty",
|
|
"GuideRecipeEmpty"
|
|
);
|
|
}
|
|
|
|
if (typeof Value === "string") {
|
|
const ListMatch =
|
|
/^List:([1-9][0-9]*)$/.exec(
|
|
Value
|
|
);
|
|
|
|
if (ListMatch !== null) {
|
|
return CreateAdminTransferTextCell(
|
|
"List " + ListMatch[1],
|
|
"MutationRecipeMatcher"
|
|
);
|
|
}
|
|
|
|
return CreateAdminTransferPlantCell(
|
|
Value,
|
|
PlantCatalogue,
|
|
CyclePlantStages
|
|
);
|
|
}
|
|
|
|
if (!IsAdminPlainObject(Value)) {
|
|
return CreateAdminTransferTextCell(
|
|
"Any",
|
|
"MutationRecipeAny"
|
|
);
|
|
}
|
|
|
|
if (typeof Value.Plant === "string") {
|
|
return CreateAdminTransferPlantCell(
|
|
Value.Plant,
|
|
PlantCatalogue,
|
|
CyclePlantStages
|
|
);
|
|
}
|
|
|
|
const Requirements = [];
|
|
|
|
if (
|
|
Array.isArray(Value.Tags) &&
|
|
Value.Tags.length > 0
|
|
) {
|
|
Requirements.push(
|
|
Value.Tags.join(" + ")
|
|
);
|
|
}
|
|
|
|
if (
|
|
Array.isArray(Value.TagsAny) &&
|
|
Value.TagsAny.length > 0
|
|
) {
|
|
Requirements.push(
|
|
"Any: " +
|
|
Value.TagsAny.join(" / ")
|
|
);
|
|
}
|
|
|
|
if (
|
|
Array.isArray(Value.TagsNot) &&
|
|
Value.TagsNot.length > 0
|
|
) {
|
|
Requirements.push(
|
|
"Not: " +
|
|
Value.TagsNot.join(", ")
|
|
);
|
|
}
|
|
|
|
if (Requirements.length === 0) {
|
|
return CreateAdminTransferTextCell(
|
|
"Any",
|
|
"MutationRecipeAny"
|
|
);
|
|
}
|
|
|
|
return CreateAdminTransferTextCell(
|
|
Requirements.join("\n"),
|
|
"MutationRecipeMatcher"
|
|
);
|
|
}
|
|
|
|
|
|
function CreateAdminTransferPlantCell(
|
|
PlantKey,
|
|
PlantCatalogue,
|
|
CyclePlantStages = false
|
|
) {
|
|
const Plant =
|
|
PlantCatalogue[PlantKey];
|
|
|
|
if (Plant === undefined) {
|
|
return CreateAdminTransferTextCell(
|
|
PlantKey,
|
|
"MutationRecipeMatcher"
|
|
);
|
|
}
|
|
|
|
const Element =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Element.className =
|
|
"PlantTile GuideRecipeCell GuideRecipePlant";
|
|
|
|
const MatureImage =
|
|
GetPlantMatureImageSource(
|
|
PlantKey
|
|
);
|
|
|
|
if (MatureImage !== null) {
|
|
const Image =
|
|
document.createElement(
|
|
"img"
|
|
);
|
|
|
|
Image.className =
|
|
"PlantSprite GuideRecipeImage";
|
|
Image.src = MatureImage;
|
|
Image.alt = "";
|
|
Element.appendChild(Image);
|
|
}
|
|
|
|
const Label =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
Label.className =
|
|
"GuideRecipeLabel";
|
|
Label.textContent =
|
|
Plant.Name ??
|
|
PlantKey;
|
|
|
|
Element.appendChild(Label);
|
|
|
|
if (CyclePlantStages === true) {
|
|
RegisterMutationRecipePreviewPlantStageCell(
|
|
Element,
|
|
Plant
|
|
);
|
|
}
|
|
|
|
return Element;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferTextCell(
|
|
Text,
|
|
ClassName
|
|
) {
|
|
const Element =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Element.className =
|
|
"PlantTile GuideRecipeCell " +
|
|
ClassName;
|
|
|
|
const Label =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
Label.className =
|
|
"MutationRecipeText";
|
|
Label.textContent = Text;
|
|
|
|
Element.appendChild(Label);
|
|
return Element;
|
|
}
|
|
|
|
|
|
function NormalizeAdminTransferMatrix(
|
|
Matrix,
|
|
Width = null,
|
|
Height = null,
|
|
FillValue = "Any"
|
|
) {
|
|
if (!Array.isArray(Matrix)) {
|
|
return [];
|
|
}
|
|
|
|
const TargetHeight =
|
|
Height ?? Matrix.length;
|
|
|
|
const TargetWidth =
|
|
Width ?? Math.max(
|
|
0,
|
|
...Matrix.map(
|
|
Row =>
|
|
Array.isArray(Row)
|
|
? Row.length
|
|
: 0
|
|
)
|
|
);
|
|
|
|
if (
|
|
TargetWidth <= 0 ||
|
|
TargetHeight <= 0
|
|
) {
|
|
return [];
|
|
}
|
|
|
|
const Result = [];
|
|
|
|
for (
|
|
let Y = 0;
|
|
Y < TargetHeight;
|
|
Y++
|
|
) {
|
|
const SourceRow =
|
|
Array.isArray(Matrix[Y])
|
|
? Matrix[Y]
|
|
: [];
|
|
|
|
const Row = [];
|
|
|
|
for (
|
|
let X = 0;
|
|
X < TargetWidth;
|
|
X++
|
|
) {
|
|
Row.push(
|
|
X < SourceRow.length
|
|
? SourceRow[X]
|
|
: FillValue
|
|
);
|
|
}
|
|
|
|
Result.push(Row);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferMutationRelations(
|
|
Mutation,
|
|
PlantCatalogue
|
|
) {
|
|
const Relations =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Relations.className =
|
|
"MutationEncyclopediaRelations";
|
|
|
|
Relations.append(
|
|
CreateAdminTransferMutationRelationGroup(
|
|
"Plants used",
|
|
Mutation.Relations
|
|
?.PlantsUsed ?? [],
|
|
PlantCatalogue
|
|
),
|
|
CreateAdminTransferMutationRelationGroup(
|
|
"Plants created",
|
|
Mutation.Relations
|
|
?.PlantsCreated ?? [],
|
|
PlantCatalogue
|
|
)
|
|
);
|
|
|
|
return Relations;
|
|
}
|
|
|
|
|
|
function CreateAdminTransferMutationRelationGroup(
|
|
Heading,
|
|
PlantIds,
|
|
PlantCatalogue
|
|
) {
|
|
const Group =
|
|
document.createElement(
|
|
"section"
|
|
);
|
|
|
|
Group.className =
|
|
"MutationEncyclopediaRelationGroup";
|
|
|
|
const Title =
|
|
document.createElement(
|
|
"h3"
|
|
);
|
|
|
|
Title.textContent = Heading;
|
|
|
|
const List =
|
|
document.createElement(
|
|
"ul"
|
|
);
|
|
|
|
List.className =
|
|
"MutationEncyclopediaRelationList";
|
|
|
|
const Ids = [
|
|
...new Set(
|
|
PlantIds.map(
|
|
Value => Number(Value)
|
|
)
|
|
)
|
|
].sort(
|
|
(A, B) => A - B
|
|
);
|
|
|
|
if (Ids.length === 0) {
|
|
const Empty =
|
|
document.createElement(
|
|
"li"
|
|
);
|
|
|
|
Empty.className =
|
|
"MutationEncyclopediaRelationEmpty";
|
|
Empty.textContent = "None";
|
|
List.appendChild(Empty);
|
|
} else {
|
|
for (const PlantId of Ids) {
|
|
const PlantKey =
|
|
GetAdminTransferPlantKeyById(
|
|
PlantId,
|
|
PlantCatalogue
|
|
);
|
|
|
|
const Item =
|
|
document.createElement(
|
|
"li"
|
|
);
|
|
|
|
Item.textContent =
|
|
PlantKey === null
|
|
? "Plant " +
|
|
PlantId
|
|
: PlantCatalogue[
|
|
PlantKey
|
|
]?.Name ??
|
|
PlantKey;
|
|
|
|
List.appendChild(Item);
|
|
}
|
|
}
|
|
|
|
Group.append(
|
|
Title,
|
|
List
|
|
);
|
|
|
|
return Group;
|
|
}
|
|
|
|
|
|
function AddAdminTransferCardStatus(
|
|
Card,
|
|
StatusText,
|
|
FileName,
|
|
ErrorText = null
|
|
) {
|
|
const Header =
|
|
Card.querySelector(
|
|
".PanelHeader"
|
|
);
|
|
|
|
const Meta =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
Meta.className =
|
|
"AdminTransferCardMeta";
|
|
|
|
const Status =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
Status.className =
|
|
"AdminTransferStatus";
|
|
Status.textContent =
|
|
StatusText;
|
|
|
|
const File =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
File.className =
|
|
"AdminTransferFileName";
|
|
File.textContent =
|
|
FileName;
|
|
|
|
Meta.append(
|
|
Status,
|
|
File
|
|
);
|
|
|
|
Header.appendChild(Meta);
|
|
|
|
if (ErrorText !== null) {
|
|
const Error =
|
|
document.createElement(
|
|
"p"
|
|
);
|
|
|
|
Error.className =
|
|
"AdminTransferInlineError";
|
|
Error.textContent =
|
|
ErrorText;
|
|
|
|
Card.appendChild(Error);
|
|
}
|
|
}
|
|
|
|
|
|
function FormatAdminTransferChance(
|
|
Chance
|
|
) {
|
|
const Percent =
|
|
Math.max(
|
|
0,
|
|
Math.min(
|
|
Number(Chance ?? 1),
|
|
1
|
|
)
|
|
) * 100;
|
|
|
|
return Percent.toLocaleString(
|
|
undefined,
|
|
{
|
|
maximumFractionDigits: 2
|
|
}
|
|
) + "%";
|
|
}
|
|
|
|
|
|
function FormatAdminTransferDuration(
|
|
Milliseconds,
|
|
AllowNone = false
|
|
) {
|
|
let TotalSeconds =
|
|
Math.max(
|
|
0,
|
|
Math.ceil(
|
|
Number(
|
|
Milliseconds ?? 0
|
|
) /
|
|
1000
|
|
)
|
|
);
|
|
|
|
if (
|
|
TotalSeconds === 0 &&
|
|
AllowNone
|
|
) {
|
|
return "None";
|
|
}
|
|
|
|
const Hours =
|
|
Math.floor(
|
|
TotalSeconds / 3600
|
|
);
|
|
|
|
TotalSeconds %= 3600;
|
|
|
|
const Minutes =
|
|
Math.floor(
|
|
TotalSeconds / 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 CloneAdminTransferValue(
|
|
Value
|
|
) {
|
|
if (Value === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
return JSON.parse(
|
|
JSON.stringify(Value)
|
|
);
|
|
}
|
|
|
|
|
|
function SetAdminTransferImportMessage(
|
|
Message
|
|
) {
|
|
const Element =
|
|
document.getElementById(
|
|
"AdminMassImportMessage"
|
|
);
|
|
|
|
if (Element !== null) {
|
|
Element.textContent = Message;
|
|
}
|
|
}
|
|
|
|
|
|
function SetAdminTransferExportMessage(
|
|
Message
|
|
) {
|
|
const Element =
|
|
document.getElementById(
|
|
"AdminMassExportMessage"
|
|
);
|
|
|
|
if (Element !== null) {
|
|
Element.textContent = Message;
|
|
}
|
|
}
|
|
|
|
|
|
function DownloadAdminTransferZip(
|
|
FileName,
|
|
Files
|
|
) {
|
|
const Encoder =
|
|
new TextEncoder();
|
|
|
|
const LocalParts = [];
|
|
const CentralParts = [];
|
|
let Offset = 0;
|
|
|
|
const Now = new Date();
|
|
const DosTime =
|
|
(
|
|
Now.getHours() << 11
|
|
) |
|
|
(
|
|
Now.getMinutes() << 5
|
|
) |
|
|
Math.floor(
|
|
Now.getSeconds() / 2
|
|
);
|
|
|
|
const DosDate =
|
|
(
|
|
Math.max(
|
|
0,
|
|
Now.getFullYear() - 1980
|
|
) << 9
|
|
) |
|
|
(
|
|
(Now.getMonth() + 1) << 5
|
|
) |
|
|
Now.getDate();
|
|
|
|
for (const File of Files) {
|
|
const NameBytes =
|
|
Encoder.encode(
|
|
File.Name
|
|
);
|
|
|
|
const DataBytes =
|
|
Encoder.encode(
|
|
File.Text
|
|
);
|
|
|
|
const Crc =
|
|
GetAdminTransferCrc32(
|
|
DataBytes
|
|
);
|
|
|
|
const LocalHeader =
|
|
new Uint8Array(
|
|
30 + NameBytes.length
|
|
);
|
|
|
|
const LocalView =
|
|
new DataView(
|
|
LocalHeader.buffer
|
|
);
|
|
|
|
LocalView.setUint32(
|
|
0,
|
|
0x04034b50,
|
|
true
|
|
);
|
|
LocalView.setUint16(
|
|
4,
|
|
20,
|
|
true
|
|
);
|
|
LocalView.setUint16(
|
|
6,
|
|
0x0800,
|
|
true
|
|
);
|
|
LocalView.setUint16(
|
|
8,
|
|
0,
|
|
true
|
|
);
|
|
LocalView.setUint16(
|
|
10,
|
|
DosTime,
|
|
true
|
|
);
|
|
LocalView.setUint16(
|
|
12,
|
|
DosDate,
|
|
true
|
|
);
|
|
LocalView.setUint32(
|
|
14,
|
|
Crc,
|
|
true
|
|
);
|
|
LocalView.setUint32(
|
|
18,
|
|
DataBytes.length,
|
|
true
|
|
);
|
|
LocalView.setUint32(
|
|
22,
|
|
DataBytes.length,
|
|
true
|
|
);
|
|
LocalView.setUint16(
|
|
26,
|
|
NameBytes.length,
|
|
true
|
|
);
|
|
LocalHeader.set(
|
|
NameBytes,
|
|
30
|
|
);
|
|
|
|
LocalParts.push(
|
|
LocalHeader,
|
|
DataBytes
|
|
);
|
|
|
|
const CentralHeader =
|
|
new Uint8Array(
|
|
46 + NameBytes.length
|
|
);
|
|
|
|
const CentralView =
|
|
new DataView(
|
|
CentralHeader.buffer
|
|
);
|
|
|
|
CentralView.setUint32(
|
|
0,
|
|
0x02014b50,
|
|
true
|
|
);
|
|
CentralView.setUint16(
|
|
4,
|
|
20,
|
|
true
|
|
);
|
|
CentralView.setUint16(
|
|
6,
|
|
20,
|
|
true
|
|
);
|
|
CentralView.setUint16(
|
|
8,
|
|
0x0800,
|
|
true
|
|
);
|
|
CentralView.setUint16(
|
|
10,
|
|
0,
|
|
true
|
|
);
|
|
CentralView.setUint16(
|
|
12,
|
|
DosTime,
|
|
true
|
|
);
|
|
CentralView.setUint16(
|
|
14,
|
|
DosDate,
|
|
true
|
|
);
|
|
CentralView.setUint32(
|
|
16,
|
|
Crc,
|
|
true
|
|
);
|
|
CentralView.setUint32(
|
|
20,
|
|
DataBytes.length,
|
|
true
|
|
);
|
|
CentralView.setUint32(
|
|
24,
|
|
DataBytes.length,
|
|
true
|
|
);
|
|
CentralView.setUint16(
|
|
28,
|
|
NameBytes.length,
|
|
true
|
|
);
|
|
CentralView.setUint32(
|
|
42,
|
|
Offset,
|
|
true
|
|
);
|
|
CentralHeader.set(
|
|
NameBytes,
|
|
46
|
|
);
|
|
|
|
CentralParts.push(
|
|
CentralHeader
|
|
);
|
|
|
|
Offset +=
|
|
LocalHeader.length +
|
|
DataBytes.length;
|
|
}
|
|
|
|
const CentralOffset = Offset;
|
|
const CentralSize =
|
|
CentralParts.reduce(
|
|
(Total, Part) =>
|
|
Total + Part.length,
|
|
0
|
|
);
|
|
|
|
const End =
|
|
new Uint8Array(22);
|
|
|
|
const EndView =
|
|
new DataView(
|
|
End.buffer
|
|
);
|
|
|
|
EndView.setUint32(
|
|
0,
|
|
0x06054b50,
|
|
true
|
|
);
|
|
EndView.setUint16(
|
|
8,
|
|
Files.length,
|
|
true
|
|
);
|
|
EndView.setUint16(
|
|
10,
|
|
Files.length,
|
|
true
|
|
);
|
|
EndView.setUint32(
|
|
12,
|
|
CentralSize,
|
|
true
|
|
);
|
|
EndView.setUint32(
|
|
16,
|
|
CentralOffset,
|
|
true
|
|
);
|
|
|
|
const BlobData =
|
|
new Blob(
|
|
[
|
|
...LocalParts,
|
|
...CentralParts,
|
|
End
|
|
],
|
|
{
|
|
type: "application/zip"
|
|
}
|
|
);
|
|
|
|
const Url =
|
|
URL.createObjectURL(
|
|
BlobData
|
|
);
|
|
|
|
const Link =
|
|
document.createElement(
|
|
"a"
|
|
);
|
|
|
|
Link.href = Url;
|
|
Link.download = FileName;
|
|
Link.click();
|
|
|
|
URL.revokeObjectURL(Url);
|
|
}
|
|
|
|
|
|
function GetAdminTransferCrc32(
|
|
Data
|
|
) {
|
|
let Crc = 0xffffffff;
|
|
|
|
for (const Byte of Data) {
|
|
Crc ^= Byte;
|
|
|
|
for (
|
|
let Bit = 0;
|
|
Bit < 8;
|
|
Bit++
|
|
) {
|
|
Crc =
|
|
(
|
|
Crc >>> 1
|
|
) ^
|
|
(
|
|
-(Crc & 1) &
|
|
0xedb88320
|
|
);
|
|
}
|
|
}
|
|
|
|
return (
|
|
Crc ^ 0xffffffff
|
|
) >>> 0;
|
|
}
|
|
|
|
|
|
document.addEventListener(
|
|
"DOMContentLoaded",
|
|
StartAdminContentTransfer
|
|
);
|