Merge branch 'main' into mod-panel

This commit is contained in:
jvyden 2022-07-27 16:55:16 -04:00
commit cc7022cf48
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
93 changed files with 2379 additions and 1426 deletions

View file

@ -28,214 +28,10 @@
@foreach (GriefReport report in Model.Reports)
{
<div class="ui segment">
<div>
<canvas class="hide-subjects" id="canvas-subjects-@report.ReportId" width="1920" height="1080"
style="position: absolute; transform: rotate(180deg)">
</canvas>
<img class="hover-region" id="game-image-@report.ReportId" src="/gameAssets/@report.JpegHash" alt="Grief report picture" style="width: 100%; height: auto; border-radius: .28571429rem;">
</div>
<p>
<i>
Report submitted by
<b>
<a href="/user/@report.ReportingPlayerId">@report.ReportingPlayer.Username</a>
</b>
</i>
</p>
<b class="hover-players" id="hover-subjects-2-@report.ReportId">Report contains @report.XmlPlayers.Length @(report.XmlPlayers.Length == 1 ? "player" : "players")</b>
@foreach (ReportPlayer player in report.XmlPlayers)
{
<div id="hover-subjects-@report.ReportId" class="hover-players">
<a href="/">@player.Name</a>
</div>
}
<br>
<div>
<b>Report time: </b>@(DateTimeOffset.FromUnixTimeMilliseconds(report.Timestamp).ToString("R"))
</div>
<div>
<b>Report reason: </b>@report.Type
</div>
<div>
<b>Level ID:</b> @report.LevelId
</div>
<div>
<b>Level type:</b> @report.LevelType
</div>
<div>
<b>Level owner:</b> @report.LevelOwner
</div>
<br>
<a class="ui green small button" href="/moderation/report/@report.ReportId/dismiss">
<i class="checkmark icon"></i>
<span>Dismiss</span>
</a>
<a class="ui red small button" href="/moderation/report/@report.ReportId/remove">
<i class="trash icon"></i>
<span>Remove all related assets</span>
</a>
</div>
<script>
subjects[@report.ReportId] = @Html.Raw(report.Players)
bounds[@report.ReportId] = @Html.Raw(report.Bounds)
images[@report.ReportId] = document.getElementById("game-image-@report.ReportId")
canvases[@report.ReportId] = document.getElementById("canvas-subjects-@report.ReportId")
canvases[@report.ReportId].width = images[@report.ReportId].offsetWidth;
canvases[@report.ReportId].height = images[@report.ReportId].clientHeight;
ctx[@report.ReportId] = canvases[@report.ReportId].getContext('2d');
</script>
@await Html.PartialAsync("Partials/ReportPartial", report)
}
<script>
function getReportId(name){
let split = name.split("-");
return split[split.length-1];
}
const colours = ["#96dd3c", "#ceb424", "#cc0a1d", "#c800cc"];
let displayType;
window.addEventListener("load", function () {
document.querySelectorAll(".hover-players").forEach(item => {
item.addEventListener('mouseenter', function () {
let reportId = getReportId(item.id);
const canvas = canvases[reportId];
displayType = 1;
canvas.className = "photo-subjects";
redraw(reportId);
});
});
document.querySelectorAll(".hover-region").forEach(item => {
item.addEventListener('mouseenter', function () {
let reportId = getReportId(item.id);
const canvas = canvases[reportId];
const image = document.getElementById("game-image-" + reportId.toString());
displayType = 0;
canvas.className = "photo-subjects";
canvas.width = image.offsetWidth;
canvas.height = image.clientHeight; // space for names to hang off
redraw(reportId);
});
});
document.querySelectorAll(".hover-region, .hover-players").forEach(item => {
item.addEventListener('mouseleave', function () {
canvases[getReportId(item.id)].className = "photo-subjects hide-subjects";
});
});
}, false);
function redraw(reportId){
let context = ctx[reportId];
let canvas = canvases[reportId];
let image = images[reportId];
context.clearRect(0, 0, canvas.width, canvas.height);
let w = canvas.width;
let h = canvas.height;
// halfwidth, halfheight
const hw = w / 2;
const hh = h / 2;
switch (displayType){
case 0: {
let imageBounds = bounds[reportId];
const x1 = imageBounds.Left;
const x2 = imageBounds.Right;
const y1 = imageBounds.Top;
const y2 = imageBounds.Bottom;
const scaleX = image.naturalWidth / canvas.width;
const scaleY = image.naturalHeight / canvas.height;
const bx = canvas.width-(x2/scaleX);
const by = canvas.height-(y2/scaleY);
const bw = (x2 - x1) / scaleX;
const bh = (y2 - y1) / scaleY;
context.beginPath();
context.globalAlpha = 0.6;
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
context.clearRect(bx, by, bw, bh);
context.beginPath();
context.lineWidth = 2;
context.strokeStyle = "#957a24";
context.rect(bx, by, bw, bh);
context.stroke();
context.globalAlpha = 1.0;
break;
}
case 1: {
let subject = subjects[reportId];
subject.forEach((s, si) => {
const colour = colours[si % 4];
// Bounding box
const x1 = s.Location.Left;
const x2 = s.Location.Right;
const y1 = s.Location.Top;
const y2 = s.Location.Bottom;
const scaleX = image.naturalWidth / canvas.width;
const scaleY = image.naturalHeight / canvas.height;
const bx = canvas.width-(x2/scaleX);
const by = canvas.height-(y2/scaleY);
const bw = (x2 - x1) / scaleX;
const bh = (y2 - y1) / scaleY;
context.beginPath();
context.lineWidth = 3;
context.strokeStyle = colour;
context.rect(bx, by, bw, bh);
context.stroke();
// Move into relative coordinates from bounding box
context.translate(bx, by);
// Username label
context.font = "16px Lato";
context.fillStyle = colour;
// Text width/height for the label background
const tw = context.measureText(s.Name).width;
const th = 24;
// Check if the label will flow off the bottom of the frame
const overflowBottom = (y2+tw - 24) > (canvas.width);
// Check if the label will flow off the left of the frame
const overflowLeft = (x2) < (24);
// Set alignment
context.textAlign = overflowLeft ? "start" : "end";
// Text x / y
const lx = overflowLeft ? -bw + 6 : -6;
const ly = overflowBottom ? -bh - 6 : 16;
// Label background x / y
const lbx = overflowLeft ? bw - tw - 12 : 0;
const lby = overflowBottom ? bh : -24;
// Draw background
context.fillRect(lbx, lby, tw+8, th);
// Draw text, rotated back upright (canvas draws rotated 180deg)
context.fillStyle = "white";
context.rotate(Math.PI);
context.fillText(s.Name, lx, ly);
// reset transform
context.setTransform(1, 0, 0, 1, 0, 0);
});
break;
}
}
}
</script>
@await Html.PartialAsync("Partials/RenderReportBoundsPartial")
@if (Model.PageNumber != 0)
{

View file

@ -46,16 +46,19 @@ public class BaseLayout : PageModel
set => this.user = value;
}
private string getLanguage()
private string? language;
public string GetLanguage()
{
if (ServerStatics.IsUnitTesting) return "en-US";
if (ServerStatics.IsUnitTesting) return LocalizationManager.DefaultLang;
if (this.language != null) return this.language;
IRequestCultureFeature? requestCulture = Request.HttpContext.Features.Get<IRequestCultureFeature>();
if (requestCulture == null) return this.language = LocalizationManager.DefaultLang;
if (requestCulture == null) return LocalizationManager.DefaultLang;
return requestCulture.RequestCulture.UICulture.Name;
return this.language = requestCulture.RequestCulture.UICulture.Name;
}
public string Translate(TranslatableString translatableString) => translatableString.Translate(this.getLanguage());
public string Translate(TranslatableString translatableString, params object?[] format) => translatableString.Translate(this.getLanguage(), format);
public string Translate(TranslatableString translatableString) => translatableString.Translate(this.GetLanguage());
public string Translate(TranslatableString translatableString, params object?[] format) => translatableString.Translate(this.GetLanguage(), format);
}

View file

@ -0,0 +1,147 @@
<script>
function getReportId(name){
let split = name.split("-");
return split[split.length-1];
}
const colours = ["#96dd3c", "#ceb424", "#cc0a1d", "#c800cc"];
let displayType;
window.addEventListener("load", function () {
document.querySelectorAll(".hover-players").forEach(item => {
item.addEventListener('mouseenter', function () {
let reportId = getReportId(item.id);
const canvas = canvases[reportId];
displayType = 1;
canvas.className = "photo-subjects";
redraw(reportId);
});
});
document.querySelectorAll(".hover-region").forEach(item => {
item.addEventListener('mouseenter', function () {
let reportId = getReportId(item.id);
const canvas = canvases[reportId];
const image = document.getElementById("game-image-" + reportId.toString());
displayType = 0;
canvas.className = "photo-subjects";
canvas.width = image.offsetWidth;
canvas.height = image.clientHeight; // space for names to hang off
redraw(reportId);
});
});
document.querySelectorAll(".hover-region, .hover-players").forEach(item => {
item.addEventListener('mouseleave', function () {
canvases[getReportId(item.id)].className = "photo-subjects hide-subjects";
});
});
}, false);
function redraw(reportId){
let context = ctx[reportId];
let canvas = canvases[reportId];
let image = images[reportId];
context.clearRect(0, 0, canvas.width, canvas.height);
let w = canvas.width;
let h = canvas.height;
// halfwidth, halfheight
const hw = w / 2;
const hh = h / 2;
switch (displayType){
case 0: {
let imageBounds = bounds[reportId];
const x1 = imageBounds.Left;
const x2 = imageBounds.Right;
const y1 = imageBounds.Top;
const y2 = imageBounds.Bottom;
const scaleX = image.naturalWidth / canvas.width;
const scaleY = image.naturalHeight / canvas.height;
const bx = canvas.width-(x2/scaleX);
const by = canvas.height-(y2/scaleY);
const bw = (x2 - x1) / scaleX;
const bh = (y2 - y1) / scaleY;
context.beginPath();
context.globalAlpha = 0.6;
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
context.clearRect(bx, by, bw, bh);
context.beginPath();
context.lineWidth = 2;
context.strokeStyle = "#957a24";
context.rect(bx, by, bw, bh);
context.stroke();
context.globalAlpha = 1.0;
break;
}
case 1: {
let subject = subjects[reportId];
subject.forEach((s, si) => {
const colour = colours[si % 4];
// Bounding box
const x1 = s.Location.Left;
const x2 = s.Location.Right;
const y1 = s.Location.Top;
const y2 = s.Location.Bottom;
const scaleX = image.naturalWidth / canvas.width;
const scaleY = image.naturalHeight / canvas.height;
const bx = canvas.width-(x2/scaleX);
const by = canvas.height-(y2/scaleY);
const bw = (x2 - x1) / scaleX;
const bh = (y2 - y1) / scaleY;
context.beginPath();
context.lineWidth = 3;
context.strokeStyle = colour;
context.rect(bx, by, bw, bh);
context.stroke();
// Move into relative coordinates from bounding box
context.translate(bx, by);
// Username label
context.font = "16px Lato";
context.fillStyle = colour;
// Text width/height for the label background
const tw = context.measureText(s.Name).width;
const th = 24;
// Check if the label will flow off the bottom of the frame
const overflowBottom = (y2+tw - 24) > (canvas.width);
// Check if the label will flow off the left of the frame
const overflowLeft = (x2) < (24);
// Set alignment
context.textAlign = overflowLeft ? "start" : "end";
// Text x / y
const lx = overflowLeft ? -bw + 6 : -6;
const ly = overflowBottom ? -bh - 6 : 16;
// Label background x / y
const lbx = overflowLeft ? bw - tw - 12 : 0;
const lby = overflowBottom ? bh : -24;
// Draw background
context.fillRect(lbx, lby, tw+8, th);
// Draw text, rotated back upright (canvas draws rotated 180deg)
context.fillStyle = "white";
context.rotate(Math.PI);
context.fillText(s.Name, lx, ly);
// reset transform
context.setTransform(1, 0, 0, 1, 0, 0);
});
break;
}
}
}
</script>

View file

@ -0,0 +1,63 @@
@using LBPUnion.ProjectLighthouse.Administration.Reports
@model LBPUnion.ProjectLighthouse.Administration.Reports.GriefReport
<div class="ui segment">
<div>
<canvas class="hide-subjects" id="canvas-subjects-@Model.ReportId" width="1920" height="1080"
style="position: absolute; transform: rotate(180deg)">
</canvas>
<img class="hover-region" id="game-image-@Model.ReportId" src="/gameAssets/@Model.JpegHash" alt="Grief report picture" style="width: 100%; height: auto; border-radius: .28571429rem;">
</div>
<p>
<i>
Report submitted by
<b>
<a href="/user/@Model.ReportingPlayerId">@Model.ReportingPlayer.Username</a>
</b>
</i>
</p>
<b class="hover-players" id="hover-subjects-2-@Model.ReportId">Report contains @Model.XmlPlayers.Length @(Model.XmlPlayers.Length == 1 ? "player" : "players")</b>
@foreach (ReportPlayer player in Model.XmlPlayers)
{
<div id="hover-subjects-@Model.ReportId" class="hover-players">
<a href="/">@player.Name</a>
</div>
}
<br>
<div>
<b>Report time: </b>@(DateTimeOffset.FromUnixTimeMilliseconds(Model.Timestamp).ToString("R"))
</div>
<div>
<b>Report reason: </b>@Model.Type
</div>
<div>
<b>Level ID:</b> @Model.LevelId
</div>
<div>
<b>Level type:</b> @Model.LevelType
</div>
<div>
<b>Level owner:</b> @Model.LevelOwner
</div>
<br>
<a class="ui green small button" href="/admin/report/@Model.ReportId/dismiss">
<i class="checkmark icon"></i>
<span>Dismiss</span>
</a>
<a class="ui red small button" href="/admin/report/@Model.ReportId/remove">
<i class="trash icon"></i>
<span>Remove all related assets</span>
</a>
</div>
<script>
subjects[@Model.ReportId] = @Html.Raw(Model.Players)
bounds[@Model.ReportId] = @Html.Raw(Model.Bounds)
images[@Model.ReportId] = document.getElementById("game-image-@Model.ReportId")
canvases[@Model.ReportId] = document.getElementById("canvas-subjects-@Model.ReportId")
canvases[@Model.ReportId].width = images[@Model.ReportId].offsetWidth;
canvases[@Model.ReportId].height = images[@Model.ReportId].clientHeight;
ctx[@Model.ReportId] = canvases[@Model.ReportId].getContext('2d');
</script>

View file

@ -1,15 +1,18 @@
@using LBPUnion.ProjectLighthouse.Helpers
@using LBPUnion.ProjectLighthouse.Localization
@model LBPUnion.ProjectLighthouse.PlayerData.Profiles.User
@{
bool showLink = (bool?)ViewData["ShowLink"] ?? false;
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang;
}
<div class="card">
@{
int size = isMobile ? 50 : 100;
}
<div class="cardIcon userCardIcon" style="background-image: url('/gameAssets/@Model.WebsiteAvatarHash'); min-width: @(size)px; width: @(size)px; height: @(size)px">
<div class="cardIcon userCardIcon" style="background-image: url('/gameAssets/@Model.WebsiteAvatarHash'); min-width: @(size)px; width: @(size)px; height: @(size)px; background-position: center center; background-size: auto @(size)px;">
</div>
<div class="cardStats">
@if (showLink)
@ -25,7 +28,7 @@
</h1>
}
<p>
<i>@Model.Status</i>
<i>@Model.Status.ToTranslatedString(language)</i>
</p>
<div class="cardStatsUnderTitle">
<i class="pink heart icon" title="Hearts"></i> <span>@Model.Hearts</span>

View file

@ -1,11 +1,12 @@
@page "/photos/{pageNumber:int}"
@using LBPUnion.ProjectLighthouse.Localization.StringLists
@using LBPUnion.ProjectLighthouse.PlayerData
@using LBPUnion.ProjectLighthouse.Types
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.PhotosPage
@{
Layout = "Layouts/BaseLayout";
Model.Title = "Photos";
Model.Title = Model.Translate(BaseLayoutStrings.HeaderPhotos);
}
<p>There are @Model.PhotoCount total photos!</p>

View file

@ -0,0 +1,18 @@
@page "/admin/report/{reportId:int}"
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.ReportPage
@{
Layout = "Layouts/BaseLayout";
Model.Title = $"Report {Model.Report.ReportId}";
}
<script>
let subjects = [];
let bounds = [];
let canvases = [];
let ctx = [];
let images = [];
</script>
@await Html.PartialAsync("Partials/ReportPartial", Model.Report)
@await Html.PartialAsync("Partials/RenderReportBoundsPartial")

View file

@ -0,0 +1,42 @@
using System.Text.Json;
using LBPUnion.ProjectLighthouse.Administration.Reports;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
public class ReportPage : BaseLayout
{
public ReportPage(Database database) : base(database)
{}
public GriefReport Report;
public async Task<IActionResult> OnGet([FromRoute] int reportId)
{
User? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
if (!user.IsAdmin) return this.NotFound();
GriefReport? report = await this.Database.Reports
.Include(r => r.ReportingPlayer)
.FirstOrDefaultAsync(r => r.ReportId == reportId);
if (report == null) return this.NotFound();
report.XmlPlayers = (ReportPlayer[])JsonSerializer.Deserialize(report.Players,
typeof(ReportPlayer[]))!;
report.XmlBounds = new Marqee
{
Rect = (Rectangle)JsonSerializer.Deserialize(report.Bounds,
typeof(Rectangle))!,
};
this.Report = report;
return this.Page();
}
}

View file

@ -1,11 +1,12 @@
@page "/slots/{pageNumber:int}"
@using LBPUnion.ProjectLighthouse.Extensions
@using LBPUnion.ProjectLighthouse.Levels
@using LBPUnion.ProjectLighthouse.Localization.StringLists
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SlotsPage
@{
Layout = "Layouts/BaseLayout";
Model.Title = "Levels";
Model.Title = Model.Translate(BaseLayoutStrings.HeaderSlots);
}
<p>There are @Model.SlotCount total levels!</p>

View file

@ -46,6 +46,9 @@
{
"IsMobile", Model.Request.IsMobile()
},
{
"Language", Model.GetLanguage()
}
})
</div>
<div class="eight wide right aligned column">

View file

@ -1,12 +1,13 @@
@page "/users/{pageNumber:int}"
@using LBPUnion.ProjectLighthouse.Extensions
@using LBPUnion.ProjectLighthouse.Localization.StringLists
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
@using LBPUnion.ProjectLighthouse.Types
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.UsersPage
@{
Layout = "Layouts/BaseLayout";
Model.Title = "Users";
Model.Title = Model.Translate(BaseLayoutStrings.HeaderUsers);
}
<p>There are @Model.UserCount total users.</p>
@ -31,6 +32,9 @@
{
"IsMobile", isMobile
},
{
"Language", Model.GetLanguage()
},
})
</div>
}