mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-05-16 14:42:28 +00:00
Website UI redesign and QOL changes (#601)
* Initial support for leaderboards and some refactoring * Start of UI redesign * Finish slot and user redesign, added deletion of comments, reviews, scores, and photos * Remove leftover debug print * Fix bug in permission check * Simplify sidebar code and add hearted and queued levels * Fix navbar scrolling on mobile and refactor SlotCardPartial
This commit is contained in:
parent
37b0925cba
commit
f4cad21061
40 changed files with 779 additions and 255 deletions
|
@ -3,7 +3,7 @@ using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Controllers.Admin;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Controllers.Moderator;
|
||||
|
||||
[ApiController]
|
||||
[Route("moderation/case/{id:int}")]
|
|
@ -0,0 +1,122 @@
|
|||
using LBPUnion.ProjectLighthouse.Administration;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Reviews;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Controllers.Moderator;
|
||||
|
||||
[ApiController]
|
||||
[Route("moderation")]
|
||||
public class ModerationRemovalController : ControllerBase
|
||||
{
|
||||
|
||||
private readonly Database database;
|
||||
|
||||
public ModerationRemovalController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
private async Task<IActionResult> Delete<T>(DbSet<T> dbSet, int id, string? callbackUrl, Func<User, int, Task<T?>> getHandler) where T: class
|
||||
{
|
||||
User? user = this.database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("~/login");
|
||||
|
||||
T? item = await getHandler(user, id);
|
||||
if (item == null) return this.Redirect("~/404");
|
||||
|
||||
dbSet.Remove(item);
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Redirect(callbackUrl ?? "~/");
|
||||
}
|
||||
|
||||
[HttpGet("deleteScore/{scoreId:int}")]
|
||||
public async Task<IActionResult> DeleteScore(int scoreId, [FromQuery] string? callbackUrl)
|
||||
{
|
||||
return await this.Delete<Score>(this.database.Scores, scoreId, callbackUrl, async (user, id) =>
|
||||
{
|
||||
Score? score = await this.database.Scores.Include(s => s.Slot).FirstOrDefaultAsync(s => s.ScoreId == id);
|
||||
if (score == null) return null;
|
||||
|
||||
if (!user.IsModerator && score.Slot.CreatorId != user.UserId) return null;
|
||||
|
||||
return score;
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("deleteComment/{commentId:int}")]
|
||||
public async Task<IActionResult> DeleteComment(int commentId, [FromQuery] string? callbackUrl)
|
||||
{
|
||||
User? user = this.database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("~/login");
|
||||
|
||||
Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
|
||||
if (comment == null) return this.Redirect("~/404");
|
||||
|
||||
if (comment.Deleted) return this.Redirect(callbackUrl ?? "~/");
|
||||
|
||||
bool canDelete;
|
||||
switch (comment.Type)
|
||||
{
|
||||
case CommentType.Level:
|
||||
int slotCreatorId = await this.database.Slots.Where(s => s.SlotId == comment.TargetId)
|
||||
.Select(s => s.CreatorId)
|
||||
.FirstOrDefaultAsync();
|
||||
canDelete = user.UserId == comment.PosterUserId || user.UserId == slotCreatorId;
|
||||
break;
|
||||
case CommentType.Profile:
|
||||
canDelete = user.UserId == comment.PosterUserId || user.UserId == comment.TargetId;
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (!canDelete && !user.IsModerator) return this.Redirect(callbackUrl ?? "~/");
|
||||
|
||||
comment.Deleted = true;
|
||||
comment.DeletedBy = user.Username;
|
||||
comment.DeletedType = !canDelete && user.IsModerator ? "moderator" : "user";
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Redirect(callbackUrl ?? "~/");
|
||||
}
|
||||
|
||||
[HttpGet("deleteReview/{reviewId:int}")]
|
||||
public async Task<IActionResult> DeleteReview(int reviewId, [FromQuery] string? callbackUrl)
|
||||
{
|
||||
User? user = this.database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("~/login");
|
||||
|
||||
Review? review = await this.database.Reviews.Include(r => r.Slot).FirstOrDefaultAsync(c => c.ReviewId == reviewId);
|
||||
if (review == null) return this.Redirect("~/404");
|
||||
|
||||
if (review.Deleted) return this.Redirect(callbackUrl ?? "~/");
|
||||
|
||||
bool canDelete = review.Slot?.CreatorId == user.UserId;
|
||||
if (!canDelete && !user.IsModerator) return this.Redirect(callbackUrl ?? "~/");
|
||||
|
||||
review.Deleted = true;
|
||||
review.DeletedBy = !canDelete && user.IsModerator ? DeletedBy.Moderator : DeletedBy.LevelAuthor;
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Redirect(callbackUrl ?? "~/");
|
||||
}
|
||||
|
||||
[HttpGet("deletePhoto/{photoId:int}")]
|
||||
public async Task<IActionResult> DeletePhoto(int photoId, [FromQuery] string? callbackUrl)
|
||||
{
|
||||
return await this.Delete<Photo>(this.database.Photos, photoId, callbackUrl, async (user, id) =>
|
||||
{
|
||||
Photo? photo = await this.database.Photos.Include(p => p.Slot).FirstOrDefaultAsync(p => p.PhotoId == id);
|
||||
if (photo == null) return null;
|
||||
|
||||
if (!user.IsModerator && photo.Slot?.CreatorId != user.UserId) return null;
|
||||
|
||||
return photo;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Levels;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Controllers.Admin;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Controllers.Moderator;
|
||||
|
||||
[ApiController]
|
||||
[Route("moderation/slot/{id:int}")]
|
|
@ -1,3 +1,4 @@
|
|||
using LBPUnion.ProjectLighthouse.Levels;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
|
@ -13,7 +14,9 @@ public static class PartialExtensions
|
|||
|
||||
public static ViewDataDictionary<T> WithTime<T>(this ViewDataDictionary<T> viewData, string timeZone) => WithKeyValue(viewData, "TimeZone", timeZone);
|
||||
|
||||
private static ViewDataDictionary<T> WithKeyValue<T>(this ViewDataDictionary<T> viewData, string key, object value)
|
||||
public static ViewDataDictionary<T> CanDelete<T>(this ViewDataDictionary<T> viewData, bool canDelete) => WithKeyValue(viewData, "CanDelete", canDelete);
|
||||
|
||||
private static ViewDataDictionary<T> WithKeyValue<T>(this ViewDataDictionary<T> viewData, string key, object? value)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -33,6 +36,25 @@ public static class PartialExtensions
|
|||
public static Task<IHtmlContent> ToLink<T>(this User user, IHtmlHelper<T> helper, ViewDataDictionary<T> viewData, string language, string timeZone = "", bool includeStatus = false)
|
||||
=> helper.PartialAsync("Partials/Links/UserLinkPartial", user, viewData.WithLang(language).WithTime(timeZone).WithKeyValue("IncludeStatus", includeStatus));
|
||||
|
||||
public static Task<IHtmlContent> ToHtml<T>(this Photo photo, IHtmlHelper<T> helper, ViewDataDictionary<T> viewData, string language, string timeZone)
|
||||
=> helper.PartialAsync("Partials/PhotoPartial", photo, viewData.WithLang(language).WithTime(timeZone));
|
||||
public static Task<IHtmlContent> ToHtml<T>
|
||||
(
|
||||
this Slot slot,
|
||||
IHtmlHelper<T> helper,
|
||||
ViewDataDictionary<T> viewData,
|
||||
User? user,
|
||||
string callbackUrl,
|
||||
string language = "",
|
||||
string timeZone = "",
|
||||
bool isMobile = false,
|
||||
bool showLink = false,
|
||||
bool isMini = false
|
||||
) =>
|
||||
helper.PartialAsync("Partials/SlotCardPartial", slot, viewData.WithLang(language).WithTime(timeZone)
|
||||
.WithKeyValue("User", user)
|
||||
.WithKeyValue("CallbackUrl", callbackUrl)
|
||||
.WithKeyValue("ShowLink", showLink)
|
||||
.WithKeyValue("IsMobile", isMobile));
|
||||
|
||||
public static Task<IHtmlContent> ToHtml<T>(this Photo photo, IHtmlHelper<T> helper, ViewDataDictionary<T> viewData, string language, string timeZone, bool canDelete = false)
|
||||
=> helper.PartialAsync("Partials/PhotoPartial", photo, viewData.WithLang(language).WithTime(timeZone).CanDelete(canDelete));
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
@page "/verifyEmail"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.CompleteEmailVerificationPage
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Email.CompleteEmailVerificationPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
|
@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Email;
|
||||
|
||||
public class CompleteEmailVerificationPage : BaseLayout
|
||||
{
|
|
@ -1,5 +1,5 @@
|
|||
@page "/login/sendVerificationEmail"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SendVerificationEmailPage
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Email.SendVerificationEmailPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
|
@ -9,7 +9,7 @@ using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Email;
|
||||
|
||||
public class SendVerificationEmailPage : BaseLayout
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
@page "/login/setEmail"
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@using LBPUnion.ProjectLighthouse.Localization.StringLists
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SetEmailForm
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Email.SetEmailForm
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
|
@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Email;
|
||||
|
||||
public class SetEmailForm : BaseLayout
|
||||
{
|
|
@ -63,7 +63,7 @@ else
|
|||
<div class="ui left aligned segment">
|
||||
@foreach (Slot slot in Model.LatestTeamPicks!) @* Can't reach a point where this is null *@
|
||||
{
|
||||
@await Html.PartialAsync("Partials/SlotCardPartial", slot, Model.GetSlotViewData(slot.SlotId, isMobile))
|
||||
@await slot.ToHtml(Html, ViewData, Model.User, $"~/slot/{slot.SlotId}", language, timeZone, isMobile, true, true)
|
||||
<br>
|
||||
}
|
||||
</div>
|
||||
|
@ -80,7 +80,7 @@ else
|
|||
<div class="ui left aligned segment">
|
||||
@foreach (Slot slot in Model.NewestLevels!) @* Can't reach a point where this is null *@
|
||||
{
|
||||
@await Html.PartialAsync("Partials/SlotCardPartial", slot, Model.GetSlotViewData(slot.SlotId, isMobile))
|
||||
@await slot.ToHtml(Html, ViewData, Model.User, $"~/slot/{slot.SlotId}", language, timeZone, isMobile, true, true)
|
||||
<br>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,6 @@ using LBPUnion.ProjectLighthouse.Levels;
|
|||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
@ -53,24 +52,4 @@ public class LandingPage : BaseLayout
|
|||
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
public ViewDataDictionary GetSlotViewData(int slotId, bool isMobile = false)
|
||||
=> new(ViewData)
|
||||
{
|
||||
{
|
||||
"User", this.User
|
||||
},
|
||||
{
|
||||
"CallbackUrl", $"~/slot/{slotId}"
|
||||
},
|
||||
{
|
||||
"ShowLink", true
|
||||
},
|
||||
{
|
||||
"IsMini", true
|
||||
},
|
||||
{
|
||||
"IsMobile", isMobile
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
@page "/login"
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@using LBPUnion.ProjectLighthouse.Localization.StringLists
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.LoginForm
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login.LoginForm
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
|
@ -13,7 +13,7 @@ using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login;
|
||||
|
||||
public class LoginForm : BaseLayout
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
@page "/logout"
|
||||
@using LBPUnion.ProjectLighthouse.Localization.StringLists
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.LogoutPage
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login.LogoutPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
|
@ -1,10 +1,9 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login;
|
||||
|
||||
public class LogoutPage : BaseLayout
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
@page "/passwordReset"
|
||||
@using LBPUnion.ProjectLighthouse.Localization.StringLists
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.PasswordResetPage
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login.PasswordResetPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
|
@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
|||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login;
|
||||
|
||||
public class PasswordResetPage : BaseLayout
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
@page "/passwordResetRequest"
|
||||
@using LBPUnion.ProjectLighthouse.Localization.StringLists
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.PasswordResetRequestForm
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login.PasswordResetRequestForm
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
|
@ -8,7 +8,7 @@ using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login;
|
||||
|
||||
public class PasswordResetRequestForm : BaseLayout
|
||||
{
|
|
@ -1,5 +1,5 @@
|
|||
@page "/passwordResetRequired"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.PasswordResetRequiredPage
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login.PasswordResetRequiredPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
|
@ -1,10 +1,9 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login;
|
||||
|
||||
public class PasswordResetRequiredPage : BaseLayout
|
||||
{
|
|
@ -1,5 +1,5 @@
|
|||
@page "/pirate"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.PirateSignupPage
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login.PirateSignupPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
|
@ -2,7 +2,7 @@ using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
|||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login;
|
||||
|
||||
public class PirateSignupPage : BaseLayout
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
@page "/register"
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@using LBPUnion.ProjectLighthouse.Localization.StringLists
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.RegisterForm
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login.RegisterForm
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
|
@ -10,7 +10,7 @@ using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login;
|
||||
|
||||
public class RegisterForm : BaseLayout
|
||||
{
|
|
@ -2,12 +2,15 @@
|
|||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Levels
|
||||
@using LBPUnion.ProjectLighthouse.Localization.StringLists
|
||||
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Moderation.HiddenLevelsPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = Model.Translate(ModPanelStrings.HiddenLevels);
|
||||
bool isMobile = Model.Request.IsMobile();
|
||||
string language = Model.GetLanguage();
|
||||
string timeZone = Model.GetTimeZone();
|
||||
}
|
||||
|
||||
<p>There are @Model.SlotCount hidden levels.</p>
|
||||
|
@ -15,21 +18,7 @@
|
|||
@foreach (Slot slot in Model.Slots)
|
||||
{
|
||||
<div class="ui segment">
|
||||
@await Html.PartialAsync("Partials/SlotCardPartial", slot, new ViewDataDictionary(ViewData)
|
||||
{
|
||||
{
|
||||
"User", Model.User
|
||||
},
|
||||
{
|
||||
"CallbackUrl", $"~/moderation/hiddenLevels/{Model.PageNumber}"
|
||||
},
|
||||
{
|
||||
"ShowLink", true
|
||||
},
|
||||
{
|
||||
"IsMobile", isMobile
|
||||
},
|
||||
})
|
||||
@await slot.ToHtml(Html, ViewData, Model.User, $"~/moderation/hiddenLevels/{Model.PageNumber}", language, timeZone, isMobile, true)
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang;
|
||||
string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id;
|
||||
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
|
||||
int pageOwnerId = (int?)ViewData["PageOwner"] ?? 0;
|
||||
}
|
||||
|
||||
<div class="ui yellow segment" id="comments">
|
||||
<h2>Comments</h2>
|
||||
@if (Model.Comments.Count == 0 && Model.CommentsEnabled)
|
||||
{
|
||||
<p>There are no comments.</p>
|
||||
|
@ -86,6 +86,12 @@
|
|||
{
|
||||
<span>@decodedMessage</span>
|
||||
}
|
||||
@if (((Model.User?.IsModerator ?? false) || Model.User?.UserId == comment.PosterUserId || Model.User?.UserId == pageOwnerId) && !comment.Deleted)
|
||||
{
|
||||
<button class="ui red icon button" style="display:inline-flex; float: right" onclick="deleteComment(@comment.CommentId)">
|
||||
<i class="trash icon"></i>
|
||||
</button>
|
||||
}
|
||||
<p>
|
||||
<i>@TimeZoneInfo.ConvertTime(timestamp, timeZoneInfo).ToString("M/d/yyyy @ h:mm:ss tt")</i>
|
||||
</p>
|
||||
|
@ -96,4 +102,12 @@
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
<script>
|
||||
function deleteComment(commentId){
|
||||
if (window.confirm("Are you sure you want to delete this?\nThis action cannot be undone.")){
|
||||
window.location.hash = "comments";
|
||||
window.location.href = "/moderation/deleteComment/" + commentId + "?callbackUrl=" + this.window.location;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
|
@ -0,0 +1,70 @@
|
|||
@using LBPUnion.ProjectLighthouse
|
||||
@using LBPUnion.ProjectLighthouse.Localization
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
|
||||
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@{
|
||||
string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang;
|
||||
string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id;
|
||||
bool canDelete = (bool?)ViewData["CanDelete"] ?? false;
|
||||
}
|
||||
<div class="ui blue segment" id="scores">
|
||||
@if (Model.Scores.Count == 0)
|
||||
{
|
||||
<p>There are no scores.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = Model.Scores.Count;
|
||||
<p>There @(count == 1 ? "is" : "are") @count score@(count == 1 ? "" : "s").</p>
|
||||
<div class="ui divider"></div>
|
||||
}
|
||||
<div class="ui list">
|
||||
@for(int i = 0; i < Model.Scores.Count; i++)
|
||||
{
|
||||
Score score = Model.Scores[i];
|
||||
string[] playerIds = score.PlayerIds;
|
||||
Database database = Model.Database;
|
||||
<div class="item">
|
||||
<span class="ui large text">
|
||||
@if(canDelete)
|
||||
{
|
||||
<button class="ui red icon button" style="display: inline-block; position: absolute; right: 1em" onclick="deleteScore(@score.ScoreId)">
|
||||
<i class="trash icon"></i>
|
||||
</button>
|
||||
}
|
||||
<b>@(i+1):</b>
|
||||
<span class="ui text">@score.Points points</span>
|
||||
</span>
|
||||
<div class="content">
|
||||
<div class="list" style="padding-top: 0">
|
||||
@for (int j = 0; j < playerIds.Length; j++)
|
||||
{
|
||||
User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == playerIds[j]);
|
||||
if (user == null) continue;
|
||||
<div class="item">
|
||||
<i class="minus icon" style="padding-top: 9px"></i>
|
||||
<div class="content" style="padding-left: 0">
|
||||
@await user.ToLink(Html, ViewData, language, timeZone)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (i != Model.Scores.Count - 1)
|
||||
{
|
||||
<div class="ui divider"></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
function deleteScore(scoreId){
|
||||
if (window.confirm("Are you sure you want to delete this?\nThis action cannot be undone.")){
|
||||
window.location.hash = "scores";
|
||||
window.location.href = "/moderation/deleteScore/" + scoreId + "?callbackUrl=" + this.window.location;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
|
@ -1,4 +1,3 @@
|
|||
@using System.Globalization
|
||||
@using System.Web
|
||||
@using LBPUnion.ProjectLighthouse.Levels
|
||||
@using LBPUnion.ProjectLighthouse.Localization
|
||||
|
@ -9,15 +8,30 @@
|
|||
@{
|
||||
string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang;
|
||||
string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id;
|
||||
bool canDelete = (bool?)ViewData["CanDelete"] ?? false;
|
||||
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
|
||||
}
|
||||
|
||||
<div style="position: relative">
|
||||
@if (canDelete)
|
||||
{
|
||||
<button class="ui red icon button" style="position: absolute; right: 0.5em; top: 0.5em" onclick="deletePhoto(@Model.PhotoId)">
|
||||
<i class="trash icon"></i>
|
||||
</button>
|
||||
<script>
|
||||
function deletePhoto(photoId){
|
||||
if (window.confirm("Are you sure you want to delete this?\nThis action cannot be undone.")){
|
||||
window.location.hash = "photos";
|
||||
window.location.href = "/moderation/deletePhoto/" + photoId + "?callbackUrl=" + this.window.location;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
<canvas class="hide-subjects" id="canvas-subjects-@Model.PhotoId" width="1920" height="1080"
|
||||
style="position: absolute; transform: rotate(180deg)">
|
||||
</canvas>
|
||||
<img id="game-image-@Model.PhotoId" src="/gameAssets/@Model.LargeHash"
|
||||
style="width: 100%; height: auto; border-radius: .28571429rem;">
|
||||
style="width: 100%; height: auto; border-radius: .28571429rem;" alt="Photo">
|
||||
</div>
|
||||
<br>
|
||||
|
||||
|
@ -50,6 +64,12 @@
|
|||
case SlotType.Local:
|
||||
<span>in a level on the moon</span>
|
||||
break;
|
||||
case SlotType.Moon:
|
||||
case SlotType.Unknown:
|
||||
case SlotType.Unknown2:
|
||||
case SlotType.DLC:
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
at @TimeZoneInfo.ConvertTime(DateTime.UnixEpoch.AddSeconds(Model.Timestamp), timeZoneInfo).ToString("M/d/yyyy h:mm:ss tt")
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
@using System.Web
|
||||
@using LBPUnion.ProjectLighthouse.Administration
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@using LBPUnion.ProjectLighthouse.Files
|
||||
@using LBPUnion.ProjectLighthouse.Helpers
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData.Reviews
|
||||
|
||||
@{
|
||||
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
|
||||
bool canDelete = (bool?)ViewData["CanDelete"] ?? false;
|
||||
}
|
||||
|
||||
<div class="eight wide column" id="reviews">
|
||||
<div class="ui purple segment">
|
||||
@if (Model.Reviews.Count == 0 && Model.ReviewsEnabled)
|
||||
{
|
||||
<p>There are no reviews.</p>
|
||||
}
|
||||
else if (!Model.ReviewsEnabled)
|
||||
{
|
||||
<b>
|
||||
<i>Reviews are disabled on this level.</i>
|
||||
</b>
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = Model.Reviews.Count;
|
||||
<p>There @(count == 1 ? "is" : "are") @count review@(count == 1 ? "" : "s").</p>
|
||||
<div class="ui divider"></div>
|
||||
}
|
||||
|
||||
@for(int i = 0; i < Model.Reviews.Count; i++)
|
||||
{
|
||||
Review review = Model.Reviews[i];
|
||||
string faceHash = (review.Thumb switch {
|
||||
-1 => review.Reviewer?.BooHash,
|
||||
0 => review.Reviewer?.MehHash,
|
||||
1 => review.Reviewer?.YayHash,
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
}) ?? "";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(faceHash) || !FileHelper.ResourceExists(faceHash))
|
||||
{
|
||||
faceHash = ServerConfiguration.Instance.WebsiteConfiguration.MissingIconHash;
|
||||
}
|
||||
|
||||
string faceAlt = review.Thumb switch {
|
||||
-1 => "Boo!",
|
||||
0 => "Meh.",
|
||||
1 => "Yay!",
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
|
||||
int size = isMobile ? 50 : 100;
|
||||
|
||||
<div class="card">
|
||||
<div>
|
||||
<img class="cardIcon slotCardIcon" src="@ServerConfiguration.Instance.ExternalUrl/gameAssets/@faceHash" alt="@faceAlt" title="@faceAlt" style="min-width: @(size)px; width: @(size)px; height: @(size)px">
|
||||
</div>
|
||||
<div class="cardStats">
|
||||
<h3 style="margin-bottom: 5px;">@review.Reviewer?.Username</h3>
|
||||
@if (review.Deleted)
|
||||
{
|
||||
if (review.DeletedBy == DeletedBy.LevelAuthor)
|
||||
{
|
||||
<p>
|
||||
<i>This review has been deleted by the level author.</i>
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>
|
||||
<i>This review has been deleted by a moderator.</i>
|
||||
</p>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (review.Labels.Length > 1)
|
||||
{
|
||||
@foreach (string reviewLabel in review.Labels)
|
||||
{
|
||||
<div class="ui blue label">@LabelHelper.TranslateTag(reviewLabel)</div>
|
||||
}
|
||||
}
|
||||
@if (string.IsNullOrWhiteSpace(review.Text))
|
||||
{
|
||||
<p>
|
||||
<i>This review contains no text.</i>
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
<p>@HttpUtility.HtmlDecode(review.Text)</p>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (canDelete && !review.Deleted)
|
||||
{
|
||||
<div style="display: inline-block; right: 1em; position: absolute;">
|
||||
<button class="ui red icon button" onclick="deleteReview(@review.ReviewId)">
|
||||
<i class="trash icon"></i>
|
||||
</button>
|
||||
<script>
|
||||
function deleteReview(reviewId){
|
||||
if (window.confirm("Are you sure you want to delete this?\nThis action cannot be undone.")){
|
||||
window.location.hash = "reviews";
|
||||
window.location.href = "/moderation/deleteReview/" + reviewId + "?callbackUrl=" + this.window.location;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (i != Model.Reviews.Count - 1)
|
||||
{
|
||||
<div class="ui divider"></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (isMobile)
|
||||
{
|
||||
<br/>
|
||||
}
|
||||
</div>
|
|
@ -24,8 +24,9 @@
|
|||
|
||||
@foreach (Photo photo in Model.Photos)
|
||||
{
|
||||
bool canDelete = Model.User != null && (Model.User.IsModerator || Model.User.UserId == photo.CreatorId);
|
||||
<div class="ui segment">
|
||||
@await photo.ToHtml(Html, ViewData, language, timeZone)
|
||||
@await photo.ToHtml(Html, ViewData, language, timeZone, canDelete)
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
@page "/slot/{id:int}"
|
||||
@using System.Web
|
||||
@using LBPUnion.ProjectLighthouse.Administration
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Files
|
||||
@using LBPUnion.ProjectLighthouse.Helpers
|
||||
@using LBPUnion.ProjectLighthouse.Localization.StringLists
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData.Reviews
|
||||
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SlotPage
|
||||
|
||||
|
@ -17,7 +15,7 @@
|
|||
Model.Title = HttpUtility.HtmlDecode(Model.Slot?.Name ?? "");
|
||||
Model.Description = HttpUtility.HtmlDecode(Model.Slot?.Description ?? "");
|
||||
|
||||
bool isMobile = this.Request.IsMobile();
|
||||
bool isMobile = Request.IsMobile();
|
||||
string language = Model.GetLanguage();
|
||||
string timeZone = Model.GetTimeZone();
|
||||
}
|
||||
|
@ -34,21 +32,7 @@
|
|||
</div>
|
||||
}
|
||||
|
||||
@await Html.PartialAsync("Partials/SlotCardPartial", Model.Slot, new ViewDataDictionary(ViewData)
|
||||
{
|
||||
{
|
||||
"User", Model.User
|
||||
},
|
||||
{
|
||||
"CallbackUrl", $"~/slot/{Model.Slot?.SlotId}"
|
||||
},
|
||||
{
|
||||
"ShowLink", false
|
||||
},
|
||||
{
|
||||
"IsMobile", Model.Request.IsMobile()
|
||||
},
|
||||
})
|
||||
@await Model.Slot.ToHtml(Html, ViewData, Model.User, $"~/slot/{Model.Slot?.SlotId}", language, timeZone, isMobile)
|
||||
<br>
|
||||
|
||||
<div class="@(isMobile ? "" : "ui grid")">
|
||||
|
@ -66,7 +50,6 @@
|
|||
<div class="ui red segment">
|
||||
<h2>Tags</h2>
|
||||
@{
|
||||
|
||||
string[] authorLabels;
|
||||
if (Model.Slot?.GameVersion == GameVersion.LittleBigPlanet1)
|
||||
{
|
||||
|
@ -96,137 +79,91 @@
|
|||
{
|
||||
<br/>
|
||||
}
|
||||
<div class="eight wide column">
|
||||
</div>
|
||||
|
||||
<div class="ui grid">
|
||||
@{
|
||||
string outerDiv = isMobile ?
|
||||
"horizontal-scroll" :
|
||||
"three wide column";
|
||||
string innerDiv = isMobile ?
|
||||
"ui top attached tabular menu horizontal-scroll" :
|
||||
"ui vertical fluid tabular menu";
|
||||
}
|
||||
<div class="@outerDiv">
|
||||
<div class="@innerDiv">
|
||||
<a class="item active lh-sidebar" target="lh-comments">
|
||||
Comments
|
||||
</a>
|
||||
<a class="item lh-sidebar" target="lh-photos">
|
||||
@Model.Translate(BaseLayoutStrings.HeaderPhotos)
|
||||
</a>
|
||||
<a class="item lh-sidebar" target="lh-reviews">
|
||||
Reviews
|
||||
</a>
|
||||
<a class="item lh-sidebar" target="lh-scores">
|
||||
Scores
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@{
|
||||
string divLength = isMobile ? "sixteen" : "thirteen";
|
||||
}
|
||||
<div class="@divLength wide stretched column">
|
||||
<div class="lh-content" id="lh-comments">
|
||||
@await Html.PartialAsync("Partials/CommentsPartial", ViewData.WithLang(language).WithTime(timeZone))
|
||||
</div>
|
||||
@if (isMobile)
|
||||
{
|
||||
<br/>
|
||||
}
|
||||
<div class="eight wide column">
|
||||
<div class="ui purple segment">
|
||||
<h2>Reviews</h2>
|
||||
@if (Model.Reviews.Count == 0 && Model.ReviewsEnabled)
|
||||
{
|
||||
<p>There are no reviews.</p>
|
||||
}
|
||||
else if (!Model.ReviewsEnabled)
|
||||
{
|
||||
<b>
|
||||
<i>Reviews are disabled on this level.</i>
|
||||
</b>
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = Model.Reviews.Count;
|
||||
<p>There @(count == 1 ? "is" : "are") @count review@(count == 1 ? "" : "s").</p>
|
||||
<div class="ui divider"></div>
|
||||
}
|
||||
|
||||
|
||||
@for(int i = 0; i < Model.Reviews.Count; i++)
|
||||
{
|
||||
Review review = Model.Reviews[i];
|
||||
string faceHash = (review.Thumb switch {
|
||||
-1 => review.Reviewer?.BooHash,
|
||||
0 => review.Reviewer?.MehHash,
|
||||
1 => review.Reviewer?.YayHash,
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
}) ?? "";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(faceHash) || !FileHelper.ResourceExists(faceHash))
|
||||
{
|
||||
faceHash = ServerConfiguration.Instance.WebsiteConfiguration.MissingIconHash;
|
||||
}
|
||||
|
||||
string faceAlt = review.Thumb switch {
|
||||
-1 => "Boo!",
|
||||
0 => "Meh.",
|
||||
1 => "Yay!",
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
|
||||
int size = isMobile ? 50 : 100;
|
||||
|
||||
<div class="card">
|
||||
<div>
|
||||
<img class="cardIcon slotCardIcon" src="@ServerConfiguration.Instance.ExternalUrl/gameAssets/@faceHash" alt="@faceAlt" title="@faceAlt" style="min-width: @(size)px; width: @(size)px; height: @(size)px">
|
||||
</div>
|
||||
<div class="cardStats">
|
||||
<h3 style="margin-bottom: 5px;">@review.Reviewer?.Username</h3>
|
||||
@if (review.Deleted)
|
||||
{
|
||||
if (review.DeletedBy == DeletedBy.LevelAuthor)
|
||||
{
|
||||
<p>
|
||||
<i>This review has been deleted by the level author.</i>
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>
|
||||
<i>This review has been deleted by a moderator.</i>
|
||||
</p>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (review.Labels.Length > 1)
|
||||
{
|
||||
@foreach (string reviewLabel in review.Labels)
|
||||
{
|
||||
<div class="ui blue label">@LabelHelper.TranslateTag(reviewLabel)</div>
|
||||
}
|
||||
}
|
||||
@if (string.IsNullOrWhiteSpace(review.Text))
|
||||
{
|
||||
<p>
|
||||
<i>This review contains no text.</i>
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
<p>@HttpUtility.HtmlDecode(review.Text)</p>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (i != Model.Reviews.Count - 1)
|
||||
{
|
||||
<div class="ui divider"></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (isMobile)
|
||||
{
|
||||
<br/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lh-content" id="lh-photos">
|
||||
<div class="ui purple segment" id="photos">
|
||||
@if (Model.Photos.Count != 0)
|
||||
{
|
||||
<div class="ui purple segment">
|
||||
<h2>Most recent photos</h2>
|
||||
|
||||
<div class="ui center aligned grid">
|
||||
@foreach (Photo photo in Model.Photos)
|
||||
{
|
||||
<div class="eight wide column">
|
||||
@await photo.ToHtml(Html, ViewData, language, timeZone)
|
||||
string width = isMobile ? "sixteen" : "eight";
|
||||
bool canDelete = Model.User != null && (Model.User.IsModerator || Model.User.UserId == photo.CreatorId || Model.User.UserId == Model.Slot?.SlotId);
|
||||
<div class="@width wide column">
|
||||
@await photo.ToHtml(Html, ViewData, language, timeZone, canDelete)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (isMobile)
|
||||
{
|
||||
<br/>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>This level has no photos yet.</p>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="lh-content" id="lh-reviews">
|
||||
@await Html.PartialAsync("Partials/ReviewPartial", new ViewDataDictionary(ViewData)
|
||||
{
|
||||
{
|
||||
"isMobile", isMobile
|
||||
},
|
||||
{
|
||||
"CanDelete", (Model.User?.IsModerator ?? false) || Model.Slot?.CreatorId == Model.User?.UserId
|
||||
},
|
||||
})
|
||||
</div>
|
||||
<div class="lh-content" id="lh-scores">
|
||||
<div class="eight wide column">
|
||||
@await Html.PartialAsync("Partials/LeaderboardPartial",
|
||||
ViewData.WithLang(language).WithTime(timeZone).CanDelete(Model.User?.IsModerator ?? false))
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (isMobile)
|
||||
{
|
||||
<br/>
|
||||
}
|
||||
|
||||
@if (Model.User != null && Model.User.IsModerator)
|
||||
{
|
||||
|
@ -282,3 +219,50 @@
|
|||
<br/>
|
||||
}
|
||||
}
|
||||
|
||||
<script>
|
||||
const sidebarElements = Array.from(document.querySelectorAll(".lh-sidebar"));
|
||||
const contentElements = Array.from(document.querySelectorAll(".lh-content"));
|
||||
let selectedId = window.location.hash;
|
||||
if (selectedId.startsWith("#"))
|
||||
selectedId = selectedId.substring(1);
|
||||
let selectedElement = document.getElementById(selectedId);
|
||||
// id = lh-sidebar element
|
||||
function setVisible(e){
|
||||
let eTarget = document.getElementById(e.target);
|
||||
if (!e || !eTarget) return;
|
||||
|
||||
// make all active elements not active
|
||||
for (let active of document.getElementsByClassName("active")) {
|
||||
active.classList.remove("active");
|
||||
}
|
||||
// hide all content divs
|
||||
for (let i = 0; i < contentElements.length; i++){
|
||||
contentElements[i].style.display = "none";
|
||||
}
|
||||
// unhide content
|
||||
eTarget.style.display = "";
|
||||
|
||||
e.classList.add("active");
|
||||
}
|
||||
|
||||
sidebarElements.forEach(el => {
|
||||
if (el.classList.contains("active")){
|
||||
setVisible(el);
|
||||
}
|
||||
el.addEventListener('click', event => {
|
||||
if (!event.target.target) return;
|
||||
|
||||
setVisible(event.target)
|
||||
})
|
||||
})
|
||||
// set the active content window based on url
|
||||
if (selectedElement != null) {
|
||||
while (selectedElement != null && !selectedElement.classList.contains("lh-content")){
|
||||
selectedElement = selectedElement.parentElement;
|
||||
}
|
||||
|
||||
let sidebarEle = document.querySelector("[target=" + selectedElement.id + "]")
|
||||
setVisible(sidebarEle);
|
||||
}
|
||||
</script>
|
|
@ -16,6 +16,7 @@ public class SlotPage : BaseLayout
|
|||
public List<Comment> Comments = new();
|
||||
public List<Review> Reviews = new();
|
||||
public List<Photo> Photos = new();
|
||||
public List<Score> Scores = new();
|
||||
|
||||
public bool CommentsEnabled;
|
||||
public readonly bool ReviewsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelReviewsEnabled;
|
||||
|
@ -93,6 +94,12 @@ public class SlotPage : BaseLayout
|
|||
.Take(10)
|
||||
.ToListAsync();
|
||||
|
||||
this.Scores = await this.Database.Scores.OrderByDescending(s => s.Points)
|
||||
.ThenByDescending(s => s.ScoreId)
|
||||
.Where(s => s.SlotId == id)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
|
||||
if (this.User == null) return this.Page();
|
||||
|
||||
foreach (Comment c in this.Comments)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Levels
|
||||
@using LBPUnion.ProjectLighthouse.Localization.StringLists
|
||||
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SlotsPage
|
||||
|
||||
@{
|
||||
|
@ -9,6 +10,8 @@
|
|||
Model.Title = Model.Translate(BaseLayoutStrings.HeaderSlots);
|
||||
|
||||
bool isMobile = Model.Request.IsMobile();
|
||||
string language = Model.GetLanguage();
|
||||
string timeZone = Model.GetTimeZone();
|
||||
}
|
||||
|
||||
<p>There are @Model.SlotCount total levels!</p>
|
||||
|
@ -24,21 +27,7 @@
|
|||
@foreach (Slot slot in Model.Slots)
|
||||
{
|
||||
<div class="ui segment">
|
||||
@await Html.PartialAsync("Partials/SlotCardPartial", slot, new ViewDataDictionary(ViewData)
|
||||
{
|
||||
{
|
||||
"User", Model.User
|
||||
},
|
||||
{
|
||||
"CallbackUrl", $"~/slots/{Model.PageNumber}"
|
||||
},
|
||||
{
|
||||
"ShowLink", true
|
||||
},
|
||||
{
|
||||
"IsMobile", isMobile
|
||||
},
|
||||
})
|
||||
@await slot.ToHtml(Html, ViewData, Model.User, $"~/slots/{Model.PageNumber}", language, timeZone, isMobile, true)
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@using System.Web
|
||||
@using LBPUnion.ProjectLighthouse.Administration
|
||||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Levels
|
||||
@using LBPUnion.ProjectLighthouse.Localization.StringLists
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData
|
||||
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
|
||||
|
@ -123,29 +124,133 @@
|
|||
}
|
||||
</div>
|
||||
|
||||
<div class="ui grid">
|
||||
@{
|
||||
string outerDiv = isMobile ?
|
||||
"horizontal-scroll" :
|
||||
"three wide column";
|
||||
string innerDiv = isMobile ?
|
||||
"ui top attached tabular menu horizontal-scroll" :
|
||||
"ui vertical fluid tabular menu";
|
||||
}
|
||||
<div class="@outerDiv">
|
||||
<div class="@innerDiv">
|
||||
<a class="item active lh-sidebar" target="lh-comments">
|
||||
Comments
|
||||
</a>
|
||||
<a class="item lh-sidebar" target="lh-photos">
|
||||
@Model.Translate(BaseLayoutStrings.HeaderPhotos)
|
||||
</a>
|
||||
|
||||
<a class="item lh-sidebar" target="lh-levels">
|
||||
@Model.Translate(BaseLayoutStrings.HeaderSlots)
|
||||
</a>
|
||||
<a class="item lh-sidebar" target="lh-playlists">
|
||||
Playlists
|
||||
</a>
|
||||
@if (Model.User == Model.ProfileUser)
|
||||
{
|
||||
<a class="item lh-sidebar" target="lh-hearted">
|
||||
Hearted Levels
|
||||
</a>
|
||||
<a class="item lh-sidebar" target="lh-queued">
|
||||
Queued Levels
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@{
|
||||
string divLength = isMobile ? "sixteen" : "thirteen";
|
||||
}
|
||||
<div class="@divLength wide stretched column">
|
||||
<div class="lh-content" id="lh-comments">
|
||||
@await Html.PartialAsync("Partials/CommentsPartial", ViewData.WithLang(language).WithTime(timeZone))
|
||||
</div>
|
||||
<div class="lh-content" id="lh-photos">
|
||||
<div class="ui purple segment" id="photos">
|
||||
@if (Model.Photos != null && Model.Photos.Count != 0)
|
||||
{
|
||||
<div class="ui purple segment">
|
||||
<h2>@Model.Translate(GeneralStrings.RecentPhotos)</h2>
|
||||
|
||||
<div class="ui center aligned grid">
|
||||
@foreach (Photo photo in Model.Photos)
|
||||
{
|
||||
string width = isMobile ? "sixteen" : "eight";
|
||||
bool canDelete = Model.User != null && (Model.User.IsModerator || Model.User.UserId == photo.CreatorId);
|
||||
<div class="@width wide column">
|
||||
@await photo.ToHtml(Html, ViewData, language, timeZone)
|
||||
@await photo.ToHtml(Html, ViewData, language, timeZone, canDelete)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (isMobile)
|
||||
{
|
||||
<br/>
|
||||
}
|
||||
}
|
||||
|
||||
@await Html.PartialAsync("Partials/CommentsPartial", ViewData.WithLang(language).WithTime(timeZone))
|
||||
else
|
||||
{
|
||||
<p>This user hasn't uploaded any photos</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="lh-content" id="lh-levels">
|
||||
<div class="ui green segment" id="levels">
|
||||
@if (Model.HeartedSlots?.Count == 0)
|
||||
{
|
||||
<p>This user hasn't published any levels</p>
|
||||
}
|
||||
@foreach (Slot slot in Model.Slots ?? new List<Slot>())
|
||||
{
|
||||
<div class="ui segment">
|
||||
@await slot.ToHtml(Html, ViewData, Model.User, $"~/user/{Model.ProfileUser.UserId}#levels", language, timeZone, isMobile, true)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="lh-content" id="lh-playlists">
|
||||
<div class="ui purple segment">
|
||||
<p>@Model.Translate(GeneralStrings.Soon)</p>
|
||||
</div>
|
||||
</div>
|
||||
@if (Model.User == Model.ProfileUser)
|
||||
{
|
||||
<div class="lh-content" id="lh-hearted">
|
||||
<div class="ui pink segment" id="hearted">
|
||||
@if (Model.HeartedSlots?.Count == 0)
|
||||
{
|
||||
<p>You haven't hearted any levels</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>You have hearted @(Model.HeartedSlots?.Count) levels</p>
|
||||
}
|
||||
@foreach (Slot slot in Model.HeartedSlots ?? new List<Slot>())
|
||||
{
|
||||
<div class="ui segment">
|
||||
@await slot.ToHtml(Html, ViewData, Model.User, $"~/user/{Model.ProfileUser.UserId}#hearted", language, timeZone, isMobile, true)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="lh-content" id="lh-queued">
|
||||
<div class="ui yellow segment" id="queued">
|
||||
@if (Model.QueuedSlots?.Count == 0)
|
||||
{
|
||||
<p>You haven't queued any levels</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>There are @(Model.QueuedSlots?.Count) levels in your queue</p>
|
||||
}
|
||||
@foreach (Slot slot in Model.QueuedSlots ?? new List<Slot>())
|
||||
{
|
||||
<div class="ui segment">
|
||||
@await slot.ToHtml(Html, ViewData, Model.User, $"~/user/{Model.ProfileUser.UserId}#queued", language, timeZone, isMobile, true)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.User != null && Model.User.IsModerator)
|
||||
{
|
||||
|
@ -192,3 +297,50 @@
|
|||
<br/>
|
||||
}
|
||||
}
|
||||
|
||||
<script>
|
||||
const sidebarElements = Array.from(document.querySelectorAll(".lh-sidebar"));
|
||||
const contentElements = Array.from(document.querySelectorAll(".lh-content"));
|
||||
let selectedId = window.location.hash;
|
||||
if (selectedId.startsWith("#"))
|
||||
selectedId = selectedId.substring(1);
|
||||
let selectedElement = document.getElementById(selectedId);
|
||||
// id = lh-sidebar element
|
||||
function setVisible(e){
|
||||
let eTarget = document.getElementById(e.target);
|
||||
if (!e || !eTarget) return;
|
||||
|
||||
// make all active elements not active
|
||||
for (let active of document.getElementsByClassName("active")) {
|
||||
active.classList.remove("active");
|
||||
}
|
||||
// hide all content divs
|
||||
for (let i = 0; i < contentElements.length; i++){
|
||||
contentElements[i].style.display = "none";
|
||||
}
|
||||
// unhide content
|
||||
eTarget.style.display = "";
|
||||
|
||||
e.classList.add("active");
|
||||
}
|
||||
|
||||
sidebarElements.forEach(el => {
|
||||
if (el.classList.contains("active")){
|
||||
setVisible(el);
|
||||
}
|
||||
el.addEventListener('click', event => {
|
||||
if (!event.target.target) return;
|
||||
|
||||
setVisible(event.target)
|
||||
})
|
||||
})
|
||||
// set the active content window based on url
|
||||
if (selectedElement != null) {
|
||||
while (selectedElement != null && !selectedElement.classList.contains("lh-content")){
|
||||
selectedElement = selectedElement.parentElement;
|
||||
}
|
||||
|
||||
let sidebarEle = document.querySelector("[target=" + selectedElement.id + "]")
|
||||
setVisible(sidebarEle);
|
||||
}
|
||||
</script>
|
|
@ -1,5 +1,6 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Levels;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
|
@ -18,6 +19,10 @@ public class UserPage : BaseLayout
|
|||
public bool IsProfileUserHearted;
|
||||
|
||||
public List<Photo>? Photos;
|
||||
public List<Slot>? Slots;
|
||||
|
||||
public List<Slot>? HeartedSlots;
|
||||
public List<Slot>? QueuedSlots;
|
||||
|
||||
public User? ProfileUser;
|
||||
public UserPage(Database database) : base(database)
|
||||
|
@ -50,7 +55,33 @@ public class UserPage : BaseLayout
|
|||
}
|
||||
}
|
||||
|
||||
this.Photos = await this.Database.Photos.Include(p => p.Slot).OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(6).ToListAsync();
|
||||
this.Photos = await this.Database.Photos.Include(p => p.Slot)
|
||||
.OrderByDescending(p => p.Timestamp)
|
||||
.Where(p => p.CreatorId == userId)
|
||||
.Take(6)
|
||||
.ToListAsync();
|
||||
|
||||
this.Slots = await this.Database.Slots.Include(p => p.Creator)
|
||||
.OrderByDescending(s => s.LastUpdated)
|
||||
.Where(p => p.CreatorId == userId)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
|
||||
if (this.User == this.ProfileUser)
|
||||
{
|
||||
this.QueuedSlots = await this.Database.QueuedLevels.Include(h => h.Slot)
|
||||
.Where(h => this.User != null && h.UserId == this.User.UserId)
|
||||
.Select(h => h.Slot)
|
||||
.Where(s => s.Type == SlotType.User)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
this.HeartedSlots = await this.Database.HeartedLevels.Include(h => h.Slot)
|
||||
.Where(h => this.User != null && h.UserId == this.User.UserId)
|
||||
.Select(h => h.Slot)
|
||||
.Where(s => s.Type == SlotType.User)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
this.CommentsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled && this.ProfileUser.CommentsEnabled;
|
||||
if (this.CommentsEnabled)
|
||||
|
@ -70,12 +101,16 @@ public class UserPage : BaseLayout
|
|||
|
||||
foreach (Comment c in this.Comments)
|
||||
{
|
||||
Reaction? reaction = await this.Database.Reactions.FirstOrDefaultAsync(r => r.UserId == this.User.UserId && r.TargetId == c.CommentId);
|
||||
Reaction? reaction = await this.Database.Reactions.Where(r => r.TargetId == c.TargetId)
|
||||
.Where(r => r.UserId == this.User.UserId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (reaction != null) c.YourThumb = reaction.Rating;
|
||||
}
|
||||
this.IsProfileUserHearted = await this.Database.HeartedProfiles.FirstOrDefaultAsync
|
||||
(u => u.UserId == this.User.UserId && u.HeartedUserId == this.ProfileUser.UserId) !=
|
||||
null;
|
||||
|
||||
this.IsProfileUserHearted = await this.Database.HeartedProfiles
|
||||
.Where(h => h.HeartedUserId == this.ProfileUser.UserId)
|
||||
.Where(h => h.UserId == this.User.UserId)
|
||||
.AnyAsync();
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
|
|
|
@ -68,7 +68,6 @@ public class CleanupBrokenPhotosMaintenanceJob : IMaintenanceJob
|
|||
}
|
||||
|
||||
LbpFile? file = LbpFile.FromHash(photo.LargeHash);
|
||||
// Console.WriteLine(file.FileType, );
|
||||
if (file == null || file.FileType != LbpFileType.Jpeg && file.FileType != LbpFileType.Png)
|
||||
{
|
||||
largeHashIsInvalidFile = true;
|
||||
|
|
|
@ -49,10 +49,12 @@ public static class RequestExtensions
|
|||
private static readonly HttpClient client;
|
||||
|
||||
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
|
||||
private static async Task<bool> verifyCaptcha(string token)
|
||||
private static async Task<bool> verifyCaptcha(string? token)
|
||||
{
|
||||
if (!ServerConfiguration.Instance.Captcha.CaptchaEnabled) return true;
|
||||
|
||||
if (token == null) return false;
|
||||
|
||||
List<KeyValuePair<string, string>> payload = new()
|
||||
{
|
||||
new("secret", ServerConfiguration.Instance.Captcha.Secret),
|
||||
|
@ -84,7 +86,7 @@ public static class RequestExtensions
|
|||
bool gotCaptcha = request.Form.TryGetValue(keyName, out StringValues values);
|
||||
if (!gotCaptcha) return false;
|
||||
|
||||
if (!await verifyCaptcha(values[0] ?? string.Empty)) return false;
|
||||
if (!await verifyCaptcha(values[0])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -72,6 +72,18 @@ canvas.hide-subjects {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.horizontal-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.horizontal-scroll {
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*#region Cards*/
|
||||
|
||||
.card {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue