Added social stuff to more areas

This commit is contained in:
sarah committed 2026-09-07 19:11:45 +02:00
1 parent 56d70e8c0c
commit bafb53e4d5
7 files changed
+540 -181

No files matched your search

+28 -6
View File
@@ -256,12 +256,12 @@ $PageSection = "Garden";
> >
<h2 class="PanelHeader PanelHeaderInset">Player</h2> <h2 class="PanelHeader PanelHeaderInset">Player</h2>
<p> <div
Username: id="PlayerIdentity"
<strong id="UsernameDisplay"> class="GardenPlayerIdentity"
Loading... >
</strong> Loading...
</p> </div>
<form <form
id="UsernameForm" id="UsernameForm"
@@ -283,6 +283,28 @@ $PageSection = "Garden";
<p id="UsernameMessage"></p> <p id="UsernameMessage"></p>
</form> </form>
<h3>Comments</h3>
<p
id="PlayerCommentMessage"
class="ProfileMessage"
aria-live="polite"
></p>
<div
id="PlayerCommentList"
class="GardenerCommentList"
></div>
<button
id="LoadMorePlayerCommentsButton"
class="ActionButton GardenerLoadMoreButton"
type="button"
hidden
>
Load more comments
</button>
</section> </section>
</main> </main>
+5 -2
View File
@@ -336,7 +336,8 @@ $PageSection = "Guide";
The <a href="/Pages/Social.html">Social</a> page lets The <a href="/Pages/Social.html">Social</a> page lets
you search for other gardeners by username and open their you search for other gardeners by username and open their
public Gardener page. Gardener profile pictures use a plant public Gardener page. Gardener profile pictures use a plant
and growth stage the player has discovered. and growth stage the player has discovered. Gardeners who
have not chosen one yet use a mature Red Rose by default.
</p> </p>
<p> <p>
@@ -350,7 +351,9 @@ $PageSection = "Guide";
<p> <p>
Comments are simple messages left on a Gardener page. A Comments are simple messages left on a Gardener page. A
comment can be deleted by its author or by the owner of the comment can be deleted by its author or by the owner of the
Gardener page. Dew donations transfer spendable Dew between Gardener page. Your own received comments are also shown in
the Player panel on your Garden, even while public comments
are disabled. Dew donations transfer spendable Dew between
accounts, but donated Dew does not increase the recipient's accounts, but donated Dew does not increase the recipient's
Lifetime Dew. Lifetime Dew.
</p> </p>
+11 -134
View File
@@ -1084,29 +1084,12 @@ async function LoadGardenerComments(
} }
try { try {
const Response = await fetch(
ApiUrl + "/Comments.php",
{
method: "POST",
headers: {
"Content-Type":
"application/json"
},
body: JSON.stringify({
Action: "List",
SaveKey: GetSaveKey(),
Username:
GardenerData.Username,
Offset:
GardenerCommentOffset,
Limit:
GardenerCommentPageSize
})
}
);
const Result = const Result =
await Response.json(); await GetGardenerComments(
GardenerData.Username,
GardenerCommentOffset,
GardenerCommentPageSize
);
if (!Result.Success) { if (!Result.Success) {
throw new Error( throw new Error(
@@ -1186,102 +1169,10 @@ async function LoadGardenerComments(
function CreateGardenerComment( function CreateGardenerComment(
Comment Comment
) { ) {
const Article = return CreateGardenerCommentCard(
document.createElement( Comment,
"article" DeleteGardenerComment
);
Article.className =
"PlantTile GardenerComment";
Article.dataset.commentId =
String(Comment.Id);
const Header =
document.createElement(
"div"
);
Header.className =
"GardenerCommentHeader";
const Identity =
CreateGardenerIdentity(
Comment.Username,
Comment.Colour,
Comment.ProfilePicture
);
const Time =
document.createElement(
"time"
);
Time.className =
"GardenerCommentTime";
const CreatedAt = Number(
Comment.CreatedAt
); );
if (Number.isFinite(CreatedAt)) {
const DateValue =
new Date(CreatedAt);
Time.dateTime =
DateValue.toISOString();
Time.textContent =
DateValue.toLocaleString();
}
Header.append(
Identity,
Time
);
const Text =
document.createElement(
"p"
);
Text.className =
"GardenerCommentText";
Text.textContent =
Comment.Text ?? "";
Article.append(
Header,
Text
);
if (Comment.CanDelete === true) {
const DeleteButton =
document.createElement(
"button"
);
DeleteButton.type = "button";
DeleteButton.className =
"ActionButton GardenerCommentDelete";
DeleteButton.textContent =
"Delete";
DeleteButton.addEventListener(
"click",
() => DeleteGardenerComment(
Number(Comment.Id),
DeleteButton
)
);
Article.appendChild(
DeleteButton
);
}
return Article;
} }
@@ -1402,24 +1293,10 @@ async function DeleteGardenerComment(
); );
try { try {
const Response = await fetch(
ApiUrl + "/Comments.php",
{
method: "POST",
headers: {
"Content-Type":
"application/json"
},
body: JSON.stringify({
Action: "Delete",
SaveKey: GetSaveKey(),
CommentId: CommentId
})
}
);
const Result = const Result =
await Response.json(); await DeleteGardenerCommentRequest(
CommentId
);
if (!Result.Success) { if (!Result.Success) {
throw new Error( throw new Error(
+15 -3
View File
@@ -1194,6 +1194,10 @@ function InitialiseProfilePictureChooser() {
SavedProfilePicture.GrowthStage SavedProfilePicture.GrowthStage
); );
} else { } else {
const DefaultPlantKey =
DefaultGardenerProfilePicture
.PlantKey;
const FirstEntry = const FirstEntry =
GetProfilePicturePlantEntries() GetProfilePicturePlantEntries()
.sort( .sort(
@@ -1203,11 +1207,19 @@ function InitialiseProfilePictureChooser() {
)[0] ?? null; )[0] ?? null;
ProfilePictureSelectedPlantKey = ProfilePictureSelectedPlantKey =
FirstEntry?.PlantKey ?? IsProfilePicturePlantAvailable(
null; DefaultPlantKey
)
? DefaultPlantKey
: FirstEntry?.PlantKey ??
null;
ProfilePictureSelectedGrowthStage = ProfilePictureSelectedGrowthStage =
null; ProfilePictureSelectedPlantKey ===
DefaultPlantKey
? DefaultGardenerProfilePicture
.GrowthStage
: null;
} }
RenderProfilePictureChooser(); RenderProfilePictureChooser();
+1 -1
View File
@@ -263,7 +263,7 @@ function CreateGardenerSearchResult(
CreateGardenerAvatar( CreateGardenerAvatar(
Gardener.ProfilePicture, Gardener.ProfilePicture,
Gardener.Username, Gardener.Username,
"GardenerAvatarSearch" "GardenerAvatarLarge"
); );
const Details = const Details =
+468 -31
View File
@@ -1,5 +1,14 @@
const DefaultColourPickerValue = "#000000"; const DefaultColourPickerValue = "#000000";
const DefaultGardenerProfilePicture = Object.freeze({
PlantKey: "RedRose",
GrowthStage: 3
});
let PlayerCommentUsername = null;
let PlayerCommentOffset = 0;
const PlayerCommentPageSize = 20;
function RenderGardenOwnerHeading( function RenderGardenOwnerHeading(
Username Username
@@ -194,20 +203,19 @@ function ApplyPlayerColour(
function GetGardenerProfilePictureSource( function GetGardenerProfilePictureSource(
ProfilePicture ProfilePicture
) { ) {
if ( const Candidate =
ProfilePicture === null || ProfilePicture !== null &&
typeof ProfilePicture !== "object" || typeof ProfilePicture === "object" &&
Array.isArray(ProfilePicture) || !Array.isArray(ProfilePicture) &&
typeof ProfilePicture.PlantKey !== typeof ProfilePicture.PlantKey ===
"string" || "string" &&
!Number.isInteger( Number.isInteger(
Number( Number(
ProfilePicture.GrowthStage ProfilePicture.GrowthStage
) )
) )
) { ? ProfilePicture
return null; : DefaultGardenerProfilePicture;
}
if ( if (
typeof Plants === "undefined" || typeof Plants === "undefined" ||
@@ -217,11 +225,24 @@ function GetGardenerProfilePictureSource(
return null; return null;
} }
const Plant = let Plant =
Plants[ Plants[Candidate.PlantKey];
ProfilePicture.PlantKey
let GrowthStage = Number(
Candidate.GrowthStage
);
if (Plant === undefined) {
Plant = Plants[
DefaultGardenerProfilePicture
.PlantKey
]; ];
GrowthStage =
DefaultGardenerProfilePicture
.GrowthStage;
}
if (Plant === undefined) { if (Plant === undefined) {
return null; return null;
} }
@@ -231,10 +252,6 @@ function GetGardenerProfilePictureSource(
Plant Plant
); );
const GrowthStage = Number(
ProfilePicture.GrowthStage
);
const RequestedSource = const RequestedSource =
Images[GrowthStage - 1]; Images[GrowthStage - 1];
@@ -260,6 +277,15 @@ function GetGardenerProfilePictureSource(
} }
} }
if (
Candidate !==
DefaultGardenerProfilePicture
) {
return GetGardenerProfilePictureSource(
DefaultGardenerProfilePicture
);
}
return null; return null;
} }
@@ -404,6 +430,163 @@ function CreateGardenerIdentity(
} }
function CreateGardenerCommentCard(
Comment,
DeleteHandler = null
) {
const Article =
document.createElement(
"article"
);
Article.className =
"PlantTile GardenerComment";
Article.dataset.commentId =
String(Comment.Id);
const Header =
document.createElement(
"div"
);
Header.className =
"GardenerCommentHeader";
const Identity =
CreateGardenerIdentity(
Comment.Username,
Comment.Colour,
Comment.ProfilePicture
);
const Time =
document.createElement(
"time"
);
Time.className =
"GardenerCommentTime";
const CreatedAt = Number(
Comment.CreatedAt
);
if (Number.isFinite(CreatedAt)) {
const DateValue =
new Date(CreatedAt);
Time.dateTime =
DateValue.toISOString();
Time.textContent =
DateValue.toLocaleString();
}
Header.append(
Identity,
Time
);
const Text =
document.createElement(
"p"
);
Text.className =
"GardenerCommentText";
Text.textContent =
Comment.Text ?? "";
Article.append(
Header,
Text
);
if (
Comment.CanDelete === true &&
typeof DeleteHandler ===
"function"
) {
const DeleteButton =
document.createElement(
"button"
);
DeleteButton.type = "button";
DeleteButton.className =
"ActionButton GardenerCommentDelete";
DeleteButton.textContent =
"Delete";
DeleteButton.addEventListener(
"click",
() => DeleteHandler(
Number(Comment.Id),
DeleteButton
)
);
Article.appendChild(
DeleteButton
);
}
return Article;
}
async function GetGardenerComments(
Username,
Offset = 0,
Limit = 20
) {
const Response = await fetch(
ApiUrl + "/Comments.php",
{
method: "POST",
headers: {
"Content-Type":
"application/json"
},
body: JSON.stringify({
Action: "List",
SaveKey: GetSaveKey(),
Username: Username,
Offset: Offset,
Limit: Limit
})
}
);
return await Response.json();
}
async function DeleteGardenerCommentRequest(
CommentId
) {
const Response = await fetch(
ApiUrl + "/Comments.php",
{
method: "POST",
headers: {
"Content-Type":
"application/json"
},
body: JSON.stringify({
Action: "Delete",
SaveKey: GetSaveKey(),
CommentId: CommentId
})
}
);
return await Response.json();
}
async function GetProfile() { async function GetProfile() {
const Response = await fetch( const Response = await fetch(
ApiUrl + "/Profile.php", ApiUrl + "/Profile.php",
@@ -501,9 +684,9 @@ async function RenderUser() {
const Profile = const Profile =
await GetProfile(); await GetProfile();
const UsernameDisplay = const PlayerIdentity =
document.getElementById( document.getElementById(
"UsernameDisplay" "PlayerIdentity"
); );
const UsernameForm = const UsernameForm =
@@ -515,13 +698,29 @@ async function RenderUser() {
Profile.Success && Profile.Success &&
Profile.Username !== null Profile.Username !== null
) { ) {
UsernameDisplay.textContent = if (PlayerIdentity !== null) {
Profile.Username; const Identity =
CreateGardenerIdentity(
Profile.Username,
Profile.Colour,
Profile.ProfilePicture,
false
);
ApplyPlayerColour( Identity.classList.add(
UsernameDisplay, "GardenPlayerIdentityContent"
Profile.Colour );
);
Identity.querySelector(
".GardenerAvatar"
)?.classList.add(
"GardenerAvatarLarge"
);
PlayerIdentity.replaceChildren(
Identity
);
}
UsernameForm.hidden = true; UsernameForm.hidden = true;
@@ -529,22 +728,250 @@ async function RenderUser() {
Profile.Username Profile.Username
); );
await LoadPlayerComments(
Profile.Username,
true
);
return; return;
} }
UsernameDisplay.textContent = if (PlayerIdentity !== null) {
"Unnamed"; const Identity =
CreateGardenerIdentity(
"Unnamed",
null,
null,
false
);
ApplyPlayerColour( Identity.classList.add(
UsernameDisplay, "GardenPlayerIdentityContent"
null );
);
Identity.querySelector(
".GardenerAvatar"
)?.classList.add(
"GardenerAvatarLarge"
);
PlayerIdentity.replaceChildren(
Identity
);
}
UsernameForm.hidden = false; UsernameForm.hidden = false;
RenderGardenOwnerHeading( RenderGardenOwnerHeading(
null null
); );
PlayerCommentUsername = null;
PlayerCommentOffset = 0;
const CommentList =
document.getElementById(
"PlayerCommentList"
);
const CommentMessage =
document.getElementById(
"PlayerCommentMessage"
);
const LoadMoreButton =
document.getElementById(
"LoadMorePlayerCommentsButton"
);
CommentList?.replaceChildren();
if (CommentMessage !== null) {
CommentMessage.textContent =
"Set a username to receive comments.";
}
if (LoadMoreButton !== null) {
LoadMoreButton.hidden = true;
}
}
async function LoadPlayerComments(
Username,
Reset
) {
const List =
document.getElementById(
"PlayerCommentList"
);
const Message =
document.getElementById(
"PlayerCommentMessage"
);
const LoadMoreButton =
document.getElementById(
"LoadMorePlayerCommentsButton"
);
if (List === null) {
return;
}
if (Reset) {
PlayerCommentUsername = Username;
PlayerCommentOffset = 0;
List.replaceChildren();
}
if (
typeof PlayerCommentUsername !==
"string" ||
PlayerCommentUsername.length === 0
) {
if (Message !== null) {
Message.textContent =
"Set a username to receive comments.";
}
if (LoadMoreButton !== null) {
LoadMoreButton.hidden = true;
}
return;
}
if (LoadMoreButton !== null) {
LoadMoreButton.disabled = true;
}
try {
const Result =
await GetGardenerComments(
PlayerCommentUsername,
PlayerCommentOffset,
PlayerCommentPageSize
);
if (!Result.Success) {
throw new Error(
Result.Error ??
"Couldn't load comments."
);
}
const Comments =
Array.isArray(Result.Comments)
? Result.Comments
: [];
for (const Comment of Comments) {
List.appendChild(
CreateGardenerCommentCard(
Comment,
DeletePlayerComment
)
);
}
PlayerCommentOffset +=
Comments.length;
if (
Reset &&
Comments.length === 0
) {
const Empty =
document.createElement(
"p"
);
Empty.className =
"GardenerCommentsEmpty";
Empty.textContent =
"No comments yet.";
List.appendChild(
Empty
);
}
if (Message !== null) {
Message.textContent = "";
}
if (LoadMoreButton !== null) {
LoadMoreButton.hidden =
Result.HasMore !== true;
}
} catch (Error) {
console.error(
"Couldn't load player comments:",
Error
);
if (Message !== null) {
Message.textContent =
"Couldn't load comments.";
}
} finally {
if (LoadMoreButton !== null) {
LoadMoreButton.disabled = false;
}
}
}
async function DeletePlayerComment(
CommentId,
Button
) {
Button.disabled = true;
const Message =
document.getElementById(
"PlayerCommentMessage"
);
try {
const Result =
await DeleteGardenerCommentRequest(
CommentId
);
if (!Result.Success) {
throw new Error(
Result.Error ??
"Couldn't delete comment."
);
}
await LoadPlayerComments(
PlayerCommentUsername,
true
);
if (Message !== null) {
Message.textContent =
"Comment deleted.";
}
} catch (Error) {
console.error(
"Couldn't delete player comment:",
Error
);
if (Message !== null) {
Message.textContent =
Error.message ??
"Couldn't delete comment.";
}
Button.disabled = false;
}
} }
@@ -596,5 +1023,15 @@ document.addEventListener(
SubmitUsername SubmitUsername
); );
} }
document.getElementById(
"LoadMorePlayerCommentsButton"
)?.addEventListener(
"click",
() => LoadPlayerComments(
PlayerCommentUsername,
false
)
);
} }
); );
+12 -4
View File
@@ -1025,7 +1025,7 @@ input, button, textarea, select {
padding: 0.75rem; padding: 0.75rem;
} }
.ProfileSection h3:not(.PanelHeader) { .GardenUser h3:not(.PanelHeader), .ProfileSection h3:not(.PanelHeader) {
margin-top: 1.5rem; margin-top: 1.5rem;
} }
@@ -1643,10 +1643,18 @@ input, button, textarea, select {
white-space: nowrap; white-space: nowrap;
} }
.GardenerIdentity:hover .GardenerIdentityName { a.GardenerIdentity:hover .GardenerIdentityName {
text-decoration: underline; text-decoration: underline;
} }
.GardenPlayerIdentity {
min-height: 4.25rem;
}
.GardenPlayerIdentityContent {
font-size: 1.35rem;
}
.GardenerSearchResults { .GardenerSearchResults {
display: grid; display: grid;
gap: 0.75rem; gap: 0.75rem;
@@ -1671,7 +1679,7 @@ input, button, textarea, select {
box-shadow: 2px 2px 0 var(--Crust); box-shadow: 2px 2px 0 var(--Crust);
} }
.GardenerAvatarSearch { .GardenerAvatarLarge {
width: 4.25rem; width: 4.25rem;
height: 4.25rem; height: 4.25rem;
flex-basis: 4.25rem; flex-basis: 4.25rem;
@@ -1813,7 +1821,7 @@ input, button, textarea, select {
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
.InteractivePanel .GardenerCommentDelete { .GardenerCommentDelete {
width: auto; width: auto;
margin-top: 0.75rem; margin-top: 0.75rem;
} }