mirror of
https://git.srhtnin.garden/sarah/srhtnin.github.io.git
synced 2026-10-13 18:38:11 +02:00
Added profile page to edit your profile and see stats.
This commit is contained in:
1 parent
39a8f18fa3
commit
11b26d972e
6 files changed
+1105
-6
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -3,7 +3,8 @@
|
|||||||
$NavbarPages = [
|
$NavbarPages = [
|
||||||
"Home" => "/",
|
"Home" => "/",
|
||||||
"Sarahtonin" => "/Pages/Sarahtonin.html",
|
"Sarahtonin" => "/Pages/Sarahtonin.html",
|
||||||
"Garden" => "/Pages/Garden.html"
|
"Garden" => "/Pages/Garden.html",
|
||||||
|
"Profile" => "/Pages/Profile.html"
|
||||||
];
|
];
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$PageTitle = "Profile";
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!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>Profile</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Manage your Garden profile, view your
|
||||||
|
statistics, and move your account between
|
||||||
|
devices.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<section class="ProfileSection">
|
||||||
|
<h2>Player</h2>
|
||||||
|
|
||||||
|
<form id="ProfileUsernameForm">
|
||||||
|
<label
|
||||||
|
for="ProfileUsernameInput"
|
||||||
|
>
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="ProfileFormRow">
|
||||||
|
<input
|
||||||
|
id="ProfileUsernameInput"
|
||||||
|
type="text"
|
||||||
|
minlength="3"
|
||||||
|
maxlength="24"
|
||||||
|
autocomplete="username"
|
||||||
|
placeholder="Username"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
|
||||||
|
<button type="submit">
|
||||||
|
Save username
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
id="ProfileUsernameMessage"
|
||||||
|
class="ProfileMessage"
|
||||||
|
></p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<section class="ProfileSection">
|
||||||
|
<h2>Statistics</h2>
|
||||||
|
|
||||||
|
<div class="ProfileStatistics">
|
||||||
|
<div class="ProfileStatistic">
|
||||||
|
<span class="ProfileStatisticName">
|
||||||
|
Current Dew
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<strong id="ProfileCurrentDew">
|
||||||
|
...
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ProfileStatistic">
|
||||||
|
<span class="ProfileStatisticName">
|
||||||
|
Lifetime Dew
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<strong id="ProfileLifetimeDew">
|
||||||
|
...
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ProfileStatistic">
|
||||||
|
<span class="ProfileStatisticName">
|
||||||
|
Plants discovered
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<strong id="ProfilePlantsDiscovered">
|
||||||
|
...
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ProfileStatistic">
|
||||||
|
<span class="ProfileStatisticName">
|
||||||
|
Mutations discovered
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<strong id="ProfileMutationsDiscovered">
|
||||||
|
...
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ProfileStatistic">
|
||||||
|
<span class="ProfileStatisticName">
|
||||||
|
Garden size
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<strong id="ProfileGardenSize">
|
||||||
|
...
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<section class="ProfileSection">
|
||||||
|
<h2>Account</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Your Account Key is the secret key used
|
||||||
|
to identify your Garden account.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You can use the same Account Key on
|
||||||
|
another device to access the same
|
||||||
|
username, save, and leaderboard entry.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
Anyone with your Account Key can
|
||||||
|
access your Garden account.
|
||||||
|
</strong>
|
||||||
|
Keep it private and treat it like a
|
||||||
|
password.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<details class="AccountKeySpoiler">
|
||||||
|
<summary>
|
||||||
|
Show Account Key
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<div class="AccountKeyContent">
|
||||||
|
<p>
|
||||||
|
Do not share this key with
|
||||||
|
anyone you do not trust.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<code id="AccountKeyDisplay">
|
||||||
|
Loading...
|
||||||
|
</code>
|
||||||
|
|
||||||
|
<div class="ProfileButtonRow">
|
||||||
|
<button
|
||||||
|
id="CopyAccountKeyButton"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Copy Account Key
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="ExportAccountKeyButton"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Export Account Key
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
id="AccountKeyMessage"
|
||||||
|
class="ProfileMessage"
|
||||||
|
></p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<section class="ProfileSection">
|
||||||
|
<h2>Import Account</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To use an existing Garden account on
|
||||||
|
this device, paste its Account Key
|
||||||
|
below.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Importing an Account Key will replace
|
||||||
|
the account currently used by this
|
||||||
|
browser. It will not delete the old
|
||||||
|
account or its server save.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form id="ImportAccountForm">
|
||||||
|
<label
|
||||||
|
for="ImportAccountKeyInput"
|
||||||
|
>
|
||||||
|
Account Key
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
id="ImportAccountKeyInput"
|
||||||
|
rows="3"
|
||||||
|
spellcheck="false"
|
||||||
|
autocomplete="off"
|
||||||
|
placeholder="Paste Account Key here"
|
||||||
|
required
|
||||||
|
></textarea>
|
||||||
|
|
||||||
|
<div class="ProfileButtonRow">
|
||||||
|
<button type="submit">
|
||||||
|
Import Account
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="ImportAccountKeyFileButton"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Import From File
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
id="ImportAccountKeyFile"
|
||||||
|
type="file"
|
||||||
|
accept=".txt,text/plain"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
|
||||||
|
<p
|
||||||
|
id="ImportAccountMessage"
|
||||||
|
class="ProfileMessage"
|
||||||
|
></p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="/Scripts/Garden/Save.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Users.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Profile.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Account.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,581 @@
|
|||||||
|
function StartAccount() {
|
||||||
|
const AccountKeySpoiler =
|
||||||
|
document.querySelector(
|
||||||
|
".AccountKeySpoiler"
|
||||||
|
);
|
||||||
|
|
||||||
|
const CopyButton =
|
||||||
|
document.getElementById(
|
||||||
|
"CopyAccountKeyButton"
|
||||||
|
);
|
||||||
|
|
||||||
|
const ExportButton =
|
||||||
|
document.getElementById(
|
||||||
|
"ExportAccountKeyButton"
|
||||||
|
);
|
||||||
|
|
||||||
|
const ImportForm =
|
||||||
|
document.getElementById(
|
||||||
|
"ImportAccountForm"
|
||||||
|
);
|
||||||
|
|
||||||
|
const ImportFileButton =
|
||||||
|
document.getElementById(
|
||||||
|
"ImportAccountKeyFileButton"
|
||||||
|
);
|
||||||
|
|
||||||
|
const ImportFileInput =
|
||||||
|
document.getElementById(
|
||||||
|
"ImportAccountKeyFile"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
AccountKeySpoiler.addEventListener(
|
||||||
|
"toggle",
|
||||||
|
RenderAccountKeySpoiler
|
||||||
|
);
|
||||||
|
|
||||||
|
CopyButton.addEventListener(
|
||||||
|
"click",
|
||||||
|
CopyAccountKey
|
||||||
|
);
|
||||||
|
|
||||||
|
ExportButton.addEventListener(
|
||||||
|
"click",
|
||||||
|
ExportAccountKey
|
||||||
|
);
|
||||||
|
|
||||||
|
ImportForm.addEventListener(
|
||||||
|
"submit",
|
||||||
|
SubmitAccountImport
|
||||||
|
);
|
||||||
|
|
||||||
|
ImportFileButton.addEventListener(
|
||||||
|
"click",
|
||||||
|
() => {
|
||||||
|
ImportFileInput.click();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ImportFileInput.addEventListener(
|
||||||
|
"change",
|
||||||
|
ImportAccountKeyFile
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RenderAccountKeySpoiler() {
|
||||||
|
const Spoiler =
|
||||||
|
document.querySelector(
|
||||||
|
".AccountKeySpoiler"
|
||||||
|
);
|
||||||
|
|
||||||
|
const Summary =
|
||||||
|
Spoiler.querySelector(
|
||||||
|
"summary"
|
||||||
|
);
|
||||||
|
|
||||||
|
const Display =
|
||||||
|
document.getElementById(
|
||||||
|
"AccountKeyDisplay"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (Spoiler.open) {
|
||||||
|
Display.textContent =
|
||||||
|
GetSaveKey();
|
||||||
|
|
||||||
|
Summary.textContent =
|
||||||
|
"Hide Account Key";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove the key from the page when
|
||||||
|
* the spoiler is closed again.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Display.textContent =
|
||||||
|
"Hidden";
|
||||||
|
|
||||||
|
Summary.textContent =
|
||||||
|
"Show Account Key";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function CopyAccountKey() {
|
||||||
|
const AccountKey =
|
||||||
|
GetSaveKey();
|
||||||
|
|
||||||
|
const Message =
|
||||||
|
document.getElementById(
|
||||||
|
"AccountKeyMessage"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
navigator.clipboard !==
|
||||||
|
undefined
|
||||||
|
) {
|
||||||
|
await navigator.clipboard.writeText(
|
||||||
|
AccountKey
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
CopyTextFallback(
|
||||||
|
AccountKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Account Key copied.";
|
||||||
|
} catch (Error) {
|
||||||
|
console.error(
|
||||||
|
"Couldn't copy Account Key:",
|
||||||
|
Error
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
CopyTextFallback(
|
||||||
|
AccountKey
|
||||||
|
);
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Account Key copied.";
|
||||||
|
} catch (FallbackError) {
|
||||||
|
console.error(
|
||||||
|
"Couldn't copy Account Key:",
|
||||||
|
FallbackError
|
||||||
|
);
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Couldn't copy Account Key.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function CopyTextFallback(
|
||||||
|
Text
|
||||||
|
) {
|
||||||
|
const TextArea =
|
||||||
|
document.createElement(
|
||||||
|
"textarea"
|
||||||
|
);
|
||||||
|
|
||||||
|
TextArea.value =
|
||||||
|
Text;
|
||||||
|
|
||||||
|
TextArea.setAttribute(
|
||||||
|
"readonly",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
TextArea.style.position =
|
||||||
|
"fixed";
|
||||||
|
|
||||||
|
TextArea.style.opacity =
|
||||||
|
"0";
|
||||||
|
|
||||||
|
|
||||||
|
document.body.appendChild(
|
||||||
|
TextArea
|
||||||
|
);
|
||||||
|
|
||||||
|
TextArea.select();
|
||||||
|
|
||||||
|
|
||||||
|
const Success =
|
||||||
|
document.execCommand(
|
||||||
|
"copy"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
TextArea.remove();
|
||||||
|
|
||||||
|
|
||||||
|
if (!Success) {
|
||||||
|
throw new Error(
|
||||||
|
"Copy command failed."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ExportAccountKey() {
|
||||||
|
const AccountKey =
|
||||||
|
GetSaveKey();
|
||||||
|
|
||||||
|
const Message =
|
||||||
|
document.getElementById(
|
||||||
|
"AccountKeyMessage"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const File =
|
||||||
|
new Blob(
|
||||||
|
[
|
||||||
|
AccountKey +
|
||||||
|
"\n"
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"text/plain;charset=utf-8"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const FileUrl =
|
||||||
|
URL.createObjectURL(
|
||||||
|
File
|
||||||
|
);
|
||||||
|
|
||||||
|
const Link =
|
||||||
|
document.createElement(
|
||||||
|
"a"
|
||||||
|
);
|
||||||
|
|
||||||
|
Link.href =
|
||||||
|
FileUrl;
|
||||||
|
|
||||||
|
Link.download =
|
||||||
|
"SarahtoninGardenAccountKey.txt";
|
||||||
|
|
||||||
|
|
||||||
|
document.body.appendChild(
|
||||||
|
Link
|
||||||
|
);
|
||||||
|
|
||||||
|
Link.click();
|
||||||
|
|
||||||
|
Link.remove();
|
||||||
|
|
||||||
|
|
||||||
|
URL.revokeObjectURL(
|
||||||
|
FileUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Account Key exported.";
|
||||||
|
} catch (Error) {
|
||||||
|
console.error(
|
||||||
|
"Couldn't export Account Key:",
|
||||||
|
Error
|
||||||
|
);
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Couldn't export Account Key.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function SubmitAccountImport(
|
||||||
|
Event
|
||||||
|
) {
|
||||||
|
Event.preventDefault();
|
||||||
|
|
||||||
|
const Input =
|
||||||
|
document.getElementById(
|
||||||
|
"ImportAccountKeyInput"
|
||||||
|
);
|
||||||
|
|
||||||
|
await ImportAccountKey(
|
||||||
|
Input.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function ImportAccountKeyFile(
|
||||||
|
Event
|
||||||
|
) {
|
||||||
|
const File =
|
||||||
|
Event.target.files[0];
|
||||||
|
|
||||||
|
if (File === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Message =
|
||||||
|
document.getElementById(
|
||||||
|
"ImportAccountMessage"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const AccountKey =
|
||||||
|
await File.text();
|
||||||
|
|
||||||
|
const Input =
|
||||||
|
document.getElementById(
|
||||||
|
"ImportAccountKeyInput"
|
||||||
|
);
|
||||||
|
|
||||||
|
Input.value =
|
||||||
|
AccountKey.trim();
|
||||||
|
|
||||||
|
await ImportAccountKey(
|
||||||
|
AccountKey
|
||||||
|
);
|
||||||
|
} catch (Error) {
|
||||||
|
console.error(
|
||||||
|
"Couldn't read Account Key file:",
|
||||||
|
Error
|
||||||
|
);
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Couldn't read that Account Key file.";
|
||||||
|
} finally {
|
||||||
|
/*
|
||||||
|
* Allows the same file to be
|
||||||
|
* selected again later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Event.target.value =
|
||||||
|
"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function ImportAccountKey(
|
||||||
|
RawAccountKey
|
||||||
|
) {
|
||||||
|
const Message =
|
||||||
|
document.getElementById(
|
||||||
|
"ImportAccountMessage"
|
||||||
|
);
|
||||||
|
|
||||||
|
const AccountKey =
|
||||||
|
NormalizeAccountKey(
|
||||||
|
RawAccountKey
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
!IsValidAccountKey(
|
||||||
|
AccountKey
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Message.textContent =
|
||||||
|
"That isn't a valid Account Key.";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const CurrentAccountKey =
|
||||||
|
GetSaveKey();
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
AccountKey ===
|
||||||
|
CurrentAccountKey
|
||||||
|
) {
|
||||||
|
Message.textContent =
|
||||||
|
"You're already using that account.";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Checking account...";
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check that the key actually belongs
|
||||||
|
* to an existing Garden account before
|
||||||
|
* replacing this browser's key.
|
||||||
|
*
|
||||||
|
* This prevents a typo from silently
|
||||||
|
* creating a completely new account.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let AccountExists;
|
||||||
|
|
||||||
|
try {
|
||||||
|
AccountExists =
|
||||||
|
await CheckAccountKeyExists(
|
||||||
|
AccountKey
|
||||||
|
);
|
||||||
|
} catch (Error) {
|
||||||
|
console.error(
|
||||||
|
"Couldn't verify Account Key:",
|
||||||
|
Error
|
||||||
|
);
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Couldn't verify that account right now.";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!AccountExists) {
|
||||||
|
Message.textContent =
|
||||||
|
"No Garden account exists for that key.";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Confirmed =
|
||||||
|
window.confirm(
|
||||||
|
"Import this Garden account?\n\n" +
|
||||||
|
"This browser will stop using its current " +
|
||||||
|
"account and switch to the imported one.\n\n" +
|
||||||
|
"The current account and its server save " +
|
||||||
|
"will not be deleted."
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (!Confirmed) {
|
||||||
|
Message.textContent =
|
||||||
|
"Import cancelled.";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to synchronize the current
|
||||||
|
* browser save one last time before
|
||||||
|
* switching away from it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const CurrentSave =
|
||||||
|
ReadLocalSave();
|
||||||
|
|
||||||
|
|
||||||
|
if (CurrentSave !== null) {
|
||||||
|
Message.textContent =
|
||||||
|
"Saving current account...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
await WriteRemoteSave(
|
||||||
|
CurrentSave
|
||||||
|
);
|
||||||
|
} catch (Error) {
|
||||||
|
console.error(
|
||||||
|
"Couldn't synchronize current account:",
|
||||||
|
Error
|
||||||
|
);
|
||||||
|
|
||||||
|
const SwitchAnyway =
|
||||||
|
window.confirm(
|
||||||
|
"The current account couldn't be " +
|
||||||
|
"synchronized with the server.\n\n" +
|
||||||
|
"Switch accounts anyway?"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (!SwitchAnyway) {
|
||||||
|
Message.textContent =
|
||||||
|
"Import cancelled.";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change identity first, then remove
|
||||||
|
* the cached save.
|
||||||
|
*
|
||||||
|
* On reload, LoadGame() sees no local
|
||||||
|
* save and downloads the imported
|
||||||
|
* account's save from the server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
localStorage.setItem(
|
||||||
|
SaveKeyName,
|
||||||
|
AccountKey
|
||||||
|
);
|
||||||
|
|
||||||
|
localStorage.removeItem(
|
||||||
|
LocalSaveName
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Account imported. Reloading...";
|
||||||
|
|
||||||
|
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function NormalizeAccountKey(
|
||||||
|
AccountKey
|
||||||
|
) {
|
||||||
|
return AccountKey
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function IsValidAccountKey(
|
||||||
|
AccountKey
|
||||||
|
) {
|
||||||
|
return /^[a-f0-9]{64}$/.test(
|
||||||
|
AccountKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function CheckAccountKeyExists(
|
||||||
|
AccountKey
|
||||||
|
) {
|
||||||
|
const Response =
|
||||||
|
await fetch(
|
||||||
|
ApiUrl + "/Load.php",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
|
||||||
|
headers: {
|
||||||
|
"Content-Type":
|
||||||
|
"application/json"
|
||||||
|
},
|
||||||
|
|
||||||
|
body: JSON.stringify({
|
||||||
|
SaveKey:
|
||||||
|
AccountKey
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (!Response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
"Load API returned HTTP " +
|
||||||
|
Response.status
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Result =
|
||||||
|
await Response.json();
|
||||||
|
|
||||||
|
|
||||||
|
if (!Result.Success) {
|
||||||
|
throw new Error(
|
||||||
|
Result.Error ??
|
||||||
|
"Account lookup failed."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
Result.Exists === true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
"DOMContentLoaded",
|
||||||
|
StartAccount
|
||||||
|
);
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
let ProfileSave = null;
|
||||||
|
|
||||||
|
|
||||||
|
async function StartProfile() {
|
||||||
|
await RenderProfileUsername();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ProfileSave =
|
||||||
|
await LoadGame();
|
||||||
|
|
||||||
|
RenderProfileStatistics();
|
||||||
|
} catch (Error) {
|
||||||
|
console.error(
|
||||||
|
"Couldn't load profile statistics:",
|
||||||
|
Error
|
||||||
|
);
|
||||||
|
|
||||||
|
RenderProfileStatisticsError();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const UsernameForm =
|
||||||
|
document.getElementById(
|
||||||
|
"ProfileUsernameForm"
|
||||||
|
);
|
||||||
|
|
||||||
|
UsernameForm.addEventListener(
|
||||||
|
"submit",
|
||||||
|
SubmitProfileUsername
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function RenderProfileUsername() {
|
||||||
|
const Input =
|
||||||
|
document.getElementById(
|
||||||
|
"ProfileUsernameInput"
|
||||||
|
);
|
||||||
|
|
||||||
|
const Message =
|
||||||
|
document.getElementById(
|
||||||
|
"ProfileUsernameMessage"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const Profile =
|
||||||
|
await GetProfile();
|
||||||
|
|
||||||
|
if (!Profile.Success) {
|
||||||
|
throw new Error(
|
||||||
|
Profile.Error ??
|
||||||
|
"Couldn't load profile."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Profile.Username !== null
|
||||||
|
) {
|
||||||
|
Input.value =
|
||||||
|
Profile.Username;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message.textContent = "";
|
||||||
|
} catch (Error) {
|
||||||
|
console.error(
|
||||||
|
"Couldn't load username:",
|
||||||
|
Error
|
||||||
|
);
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Couldn't load username.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function SubmitProfileUsername(
|
||||||
|
Event
|
||||||
|
) {
|
||||||
|
Event.preventDefault();
|
||||||
|
|
||||||
|
const Input =
|
||||||
|
document.getElementById(
|
||||||
|
"ProfileUsernameInput"
|
||||||
|
);
|
||||||
|
|
||||||
|
const Message =
|
||||||
|
document.getElementById(
|
||||||
|
"ProfileUsernameMessage"
|
||||||
|
);
|
||||||
|
|
||||||
|
const Username =
|
||||||
|
Input.value.trim();
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
Username.length < 3 ||
|
||||||
|
Username.length > 24
|
||||||
|
) {
|
||||||
|
Message.textContent =
|
||||||
|
"Username must be between 3 and 24 characters.";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Saving...";
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const Result =
|
||||||
|
await SetUsername(
|
||||||
|
Username
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!Result.Success) {
|
||||||
|
Message.textContent =
|
||||||
|
Result.Error ??
|
||||||
|
"Couldn't change username.";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Input.value =
|
||||||
|
Username;
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Username saved.";
|
||||||
|
} catch (Error) {
|
||||||
|
console.error(
|
||||||
|
"Couldn't change username:",
|
||||||
|
Error
|
||||||
|
);
|
||||||
|
|
||||||
|
Message.textContent =
|
||||||
|
"Couldn't change username.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RenderProfileStatistics() {
|
||||||
|
const CurrentDew =
|
||||||
|
ProfileSave
|
||||||
|
?.Currency
|
||||||
|
?.Dew ??
|
||||||
|
0;
|
||||||
|
|
||||||
|
const LifetimeDew =
|
||||||
|
ProfileSave
|
||||||
|
?.Statistics
|
||||||
|
?.CurrencyEarned
|
||||||
|
?.Dew ??
|
||||||
|
CurrentDew;
|
||||||
|
|
||||||
|
const PlantsDiscovered =
|
||||||
|
ProfileSave
|
||||||
|
?.Discoveries
|
||||||
|
?.Plants
|
||||||
|
?.length ??
|
||||||
|
0;
|
||||||
|
|
||||||
|
const MutationsDiscovered =
|
||||||
|
ProfileSave
|
||||||
|
?.Discoveries
|
||||||
|
?.Mutations
|
||||||
|
?.length ??
|
||||||
|
0;
|
||||||
|
|
||||||
|
const GardenWidth =
|
||||||
|
ProfileSave
|
||||||
|
?.Garden
|
||||||
|
?.Width ??
|
||||||
|
0;
|
||||||
|
|
||||||
|
const GardenHeight =
|
||||||
|
ProfileSave
|
||||||
|
?.Garden
|
||||||
|
?.Height ??
|
||||||
|
0;
|
||||||
|
|
||||||
|
|
||||||
|
SetProfileStatistic(
|
||||||
|
"ProfileCurrentDew",
|
||||||
|
FormatProfileNumber(
|
||||||
|
CurrentDew
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
SetProfileStatistic(
|
||||||
|
"ProfileLifetimeDew",
|
||||||
|
FormatProfileNumber(
|
||||||
|
LifetimeDew
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
SetProfileStatistic(
|
||||||
|
"ProfilePlantsDiscovered",
|
||||||
|
FormatProfileNumber(
|
||||||
|
PlantsDiscovered
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
SetProfileStatistic(
|
||||||
|
"ProfileMutationsDiscovered",
|
||||||
|
FormatProfileNumber(
|
||||||
|
MutationsDiscovered
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
SetProfileStatistic(
|
||||||
|
"ProfileGardenSize",
|
||||||
|
GardenWidth +
|
||||||
|
"×" +
|
||||||
|
GardenHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RenderProfileStatisticsError() {
|
||||||
|
const StatisticIds = [
|
||||||
|
"ProfileCurrentDew",
|
||||||
|
"ProfileLifetimeDew",
|
||||||
|
"ProfilePlantsDiscovered",
|
||||||
|
"ProfileMutationsDiscovered",
|
||||||
|
"ProfileGardenSize"
|
||||||
|
];
|
||||||
|
|
||||||
|
for (
|
||||||
|
const StatisticId
|
||||||
|
of StatisticIds
|
||||||
|
) {
|
||||||
|
SetProfileStatistic(
|
||||||
|
StatisticId,
|
||||||
|
"?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function SetProfileStatistic(
|
||||||
|
ElementId,
|
||||||
|
Value
|
||||||
|
) {
|
||||||
|
const Element =
|
||||||
|
document.getElementById(
|
||||||
|
ElementId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Element === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Element.textContent =
|
||||||
|
Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FormatProfileNumber(
|
||||||
|
Number
|
||||||
|
) {
|
||||||
|
return Number.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
"DOMContentLoaded",
|
||||||
|
StartProfile
|
||||||
|
);
|
||||||
@@ -145,18 +145,21 @@ async function WriteRemoteSave(SaveData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function ReadRemoteSave() {
|
async function ReadRemoteSave(
|
||||||
|
SaveKey = GetSaveKey()
|
||||||
|
) {
|
||||||
const Response = await fetch(
|
const Response = await fetch(
|
||||||
ApiUrl + "/Load.php",
|
ApiUrl + "/Load.php",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type":
|
||||||
|
"application/json"
|
||||||
},
|
},
|
||||||
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
SaveKey: GetSaveKey()
|
SaveKey: SaveKey
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -168,7 +171,8 @@ async function ReadRemoteSave() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Result = await Response.json();
|
const Result =
|
||||||
|
await Response.json();
|
||||||
|
|
||||||
if (!Result.Success) {
|
if (!Result.Success) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -184,7 +188,6 @@ async function ReadRemoteSave() {
|
|||||||
return Result.SaveData;
|
return Result.SaveData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function SaveGame(SaveData) {
|
async function SaveGame(SaveData) {
|
||||||
SaveData.Revision++;
|
SaveData.Revision++;
|
||||||
SaveData.LastSavedAt = Date.now();
|
SaveData.LastSavedAt = Date.now();
|
||||||
|
|||||||
Reference in new issue
Block a user