Refactoring, database structure changed

This commit is contained in:
2025-03-03 00:56:32 +01:00
Unverified
parent d3805ef3db
commit c603c41c0b
913 changed files with 21764 additions and 32775 deletions

View File

@@ -0,0 +1,20 @@
@inherits Component
<div class="panel">
<div class="vstack">
<div class="d-flex justify-content-center">
<div class="text-danger icon-size">⚠&#xFE0E;</div>
</div>
<div class="d-flex justify-content-center">
<h3 class="text-danger">An error occured while loading a page</h3>
</div>
@if (!string.IsNullOrWhiteSpace(ErrorMessage))
{
<div class="d-flex justify-content-center">
<p>@ErrorMessage</p>
</div>
}
</div>
</div>

View File

@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Components;
namespace WatchIt.Website.Components.Panels.Common;
public partial class ErrorPanel : Component
{
#region PARAMETERS
[Parameter] public string? ErrorMessage { get; set; }
#endregion
}

View File

@@ -0,0 +1,5 @@
/* CLASSES */
.icon-size {
font-size: 80px;
}

View File

@@ -0,0 +1,49 @@
@using WatchIt.Website.Components.Subcomponents.Common
@inherits Component
@typeparam TItem
<div class="panel">
<div class="vstack gap-3">
<span class="panel-text-title">@(Title)</span>
@if (_loaded)
{
<div class="container-grid">
<div class="row">
@if (_items.Count() > 0)
{
for (int i = 0; i < Count; i++)
{
<div class="col">
@if (_items.Count() > i)
{
<a class="text-reset text-decoration-none" href="@(string.Format(ItemUrlFormatString, IdSource(_items.ElementAt(i))))">
@{ int iCopy = i; }
<HorizontalListItem Place="@(HidePlace ? null : i + 1)"
Name="@(NameSource(_items.ElementAt(iCopy)))"
PosterPlaceholder="@(PosterPlaceholder)"
GetPosterAction="@(() => GetPictureAction(_items.ElementAt(iCopy)))"/>
</a>
}
</div>
}
}
else if (!string.IsNullOrWhiteSpace(EmptyListMessage))
{
<div class="col">
<div class="d-flex justify-content-center">
@(EmptyListMessage)
</div>
</div>
}
</div>
</div>
}
else
{
<Loading Color="@(Loading.Colors.Light)"/>
}
</div>
</div>

View File

@@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Components;
using Refit;
using WatchIt.DTO.Models.Generics.Image;
namespace WatchIt.Website.Components.Panels.Common;
public partial class HorizontalListPanel<TItem> : Component
{
#region PARAMETERS
[Parameter] public int Count { get; set; } = 5;
[Parameter] public required string Title {get; set; }
[Parameter] public required Func<Task<IApiResponse<IEnumerable<TItem>>>> GetItemsAction { get; set; }
[Parameter] public required string ItemUrlFormatString { get; set; }
[Parameter] public required Func<TItem, long> IdSource { get; set; }
[Parameter] public required Func<TItem, string> NameSource { get; set; }
[Parameter] public required string PosterPlaceholder { get; set; }
[Parameter] public required Func<TItem, Task<ImageResponse?>> GetPictureAction { get; set; }
[Parameter] public bool HidePlace { get; set; }
[Parameter] public string? EmptyListMessage { get; set; }
#endregion
#region FIELDS
private bool _loaded;
private IEnumerable<TItem> _items = default!;
#endregion
#region PRIVATE METHODS
protected override async Task OnFirstRenderAsync()
{
IApiResponse<IEnumerable<TItem>> response = await GetItemsAction();
if (response.IsSuccessful)
{
_items = response.Content;
}
_loaded = true;
StateHasChanged();
}
#endregion
}

View File

@@ -0,0 +1,72 @@
@using WatchIt.Website.Components.Subcomponents.Common
@inherits Component
<div class="panel @(Class)">
@if (_loaded)
{
<div class="vstack gap-3">
<Image Content="@(_imageSelected)" Placeholder="@(ImagePlaceholder)" AlternativeText="loaded_image" Circle="@(Circle)" Width="@(ContentWidth)"/>
<Blazorise.FileEdit Width="@(Blazorise.Width.Px(ContentWidth - 100))" Changed="@(LoadImageFromFile)" Filter="image/jpeg, image/png, image/webp" Disabled="@(Disabled)"/>
@if (_imageChanged || _imageSaved is not null)
{
<div class="content-width">
@if (_imageChanged)
{
<div class="container-grid">
<div class="row gx-1">
<div class="col">
<button type="button" class="btn btn-secondary btn-block btn-stretch-x" @onclick="@(SaveImage)" disabled=@(Disabled || _imageSaving || _imageDeleting) autocomplete="off">
@if (!_imageSaving)
{
<span>Save</span>
}
else
{
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span>Saving...</span>
}
</button>
</div>
<div class="col">
<button type="button" class="btn btn-danger btn-block btn-stretch-x" @onclick="@(CancelImage)" disabled=@(Disabled || _imageSaving || _imageDeleting) autocomplete="off">Drop changes</button>
</div>
</div>
</div>
}
else if (_imageSaved is not null)
{
<button type="button" class="btn btn-danger btn-block btn-stretch-x" @onclick="@(DeleteImage)" disabled=@(Disabled || _imageSaving || _imageDeleting) autocomplete="off">
@if (!_imageSaving)
{
<span>Delete</span>
}
else
{
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span>Deleting...</span>
}
</button>
}
</div>
}
</div>
}
else
{
<div class="d-flex align-items-center justify-content-center h-100 content-width">
<Loading Color="@(Loading.Colors.Light)"/>
</div>
}
</div>
<style>
/* CLASSES */
.content-width {
width: @(ContentWidth)px;
}
</style>

View File

@@ -0,0 +1,138 @@
using System.Reflection.Metadata;
using Blazorise;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using WatchIt.DTO.Models.Generics.Image;
namespace WatchIt.Website.Components.Panels.Common;
public partial class ImageEditorPanel : Component
{
#region PARAMETERS
[Parameter] public int ContentWidth { get; set; } = 300;
[Parameter] public required string ImagePlaceholder { get; set; }
[Parameter] public bool Circle { get; set; }
[Parameter] public bool Disabled { get; set; }
[Parameter] public string Class { get; set; } = string.Empty;
[Parameter]
public required ImageBase? Image
{
get => _imageSaved;
set
{
_imageSet = true;
_imageSaved = value;
_imageSelected = ImageToRequest(value);
}
}
[Parameter] public required Func<Task<ImageResponse?>> ImageGetMethod { get; set; }
[Parameter] public required Func<ImageRequest, Task<ImageResponse?>> ImagePutMethod { get; set; }
[Parameter] public required Func<Task<bool>> ImageDeleteMethod { get; set; }
[Parameter] public Action<ImageResponse?>? OnImageChanged { get; set; }
#endregion
#region FIELDS
private bool _loaded;
private ImageBase? _imageSaved;
private bool _imageSet;
private ImageRequest? _imageSelected;
private bool _imageChanged;
private bool _imageSaving;
private bool _imageDeleting;
#endregion
#region PRIVATE METHODS
protected override async Task OnFirstRenderAsync()
{
await base.OnFirstRenderAsync();
if (!_imageSet)
{
_imageSaved = await ImageGetMethod();
_imageSelected = ImageToRequest(_imageSaved);
}
_loaded = true;
StateHasChanged();
}
private async Task LoadImageFromFile(FileChangedEventArgs args)
{
IFileEntry file = args.Files.First();
Stream stream = file.OpenReadStream(5242880);
byte[] array;
using (MemoryStream ms = new MemoryStream())
{
await stream.CopyToAsync(ms);
array = ms.ToArray();
}
_imageSelected = new ImageRequest
{
Image = array,
MimeType = file.Type,
};
_imageChanged = true;
}
private async Task SaveImage()
{
_imageSaving = true;
if (_imageSelected is not null)
{
ImageResponse? response = await ImagePutMethod(_imageSelected);
if (response is not null)
{
_imageSaved = response;
_imageSelected = ImageToRequest(_imageSaved);
_imageChanged = false;
OnImageChanged?.Invoke(response);
}
}
_imageSaving = false;
}
private void CancelImage()
{
_imageSelected = ImageToRequest(_imageSaved);
_imageChanged = false;
}
private async Task DeleteImage()
{
_imageDeleting = true;
if (_imageSaved is not null)
{
bool response = await ImageDeleteMethod();
if (response)
{
_imageSaved = null;
_imageSelected = null;
_imageChanged = false;
OnImageChanged?.Invoke(null);
}
}
_imageDeleting = false;
}
public static ImageRequest? ImageToRequest(ImageBase? image) => image is null ? null : new ImageRequest
{
Image = image.Image,
MimeType = image.MimeType,
};
#endregion
}

View File

@@ -0,0 +1,27 @@
@using WatchIt.Website.Components.Subcomponents.Common
@inherits Component
<div class="container-grid mt-header">
<div class="row">
<div class="col-auto">
<Image Content="@(Poster)" Placeholder="@(PosterPlaceholder)" AlternativeText="poster" Height="350"/>
</div>
<div class="col">
<div class="d-flex flex-column justify-content-end h-100">
<h1 class="fw-bold title-shadow">@(Name)</h1>
<div class="d-flex flex-column gap-3">
@if (!string.IsNullOrWhiteSpace(Subname))
{
<span class="fst-italic description-shadow">@(Subname)</span>
}
@if (!string.IsNullOrWhiteSpace(Description))
{
<span class="description-shadow">@(Description)</span>
}
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Components;
using WatchIt.DTO.Models.Generics.Image;
namespace WatchIt.Website.Components.Panels.Common;
public partial class ItemPageHeaderPanel : Component
{
#region PARAMETERS
[Parameter] public required string Name { get; set; }
[Parameter] public string? Subname { get; set; }
[Parameter] public string? Description { get; set; }
[Parameter] public ImageResponse? Poster { get; set; }
[Parameter] public required string PosterPlaceholder { get; set; }
#endregion
}

View File

@@ -0,0 +1,9 @@
/* CLASSES */
.title-shadow {
text-shadow: 2px 2px 2px #000;
}
.description-shadow {
text-shadow: 1px 1px 1px #000;
}

View File

@@ -0,0 +1,124 @@
@using System.Net
@using WatchIt.Website.Components.Subcomponents.Common
@using Blazorise
@using Blazorise.Snackbar
@using Refit
@using WatchIt.DTO.Models.Controllers.Roles
@using WatchIt.DTO.Models.Generics.Rating
@inherits Component
@typeparam TRole where TRole : WatchIt.DTO.Models.Controllers.Roles.Role.Response.RoleResponse
@typeparam TRoleParent
<div class="panel @(Class)">
@if (_loaded)
{
<div class="vstack gap-3">
<span class="panel-text-title">@(Title)</span>
@if (RoleParents is null)
{
<span class="text-center">An error occured. @(ParentName) could not be obtained.</span>
}
else if (_roles is null)
{
<span class="text-center">An error occured. Roles could not be obtained.</span>
}
else if (!_roles.Any())
{
<span class="text-center">No items found.</span>
}
else if (_roleTypes is null)
{
<span class="text-center">An error occured. Role types could not be obtained.</span>
}
else
{
<div class="d-flex justify-content-center">
<RadioGroup TValue="short" Color="Color.Default" Buttons Size="Size.Small" @bind-CheckedValue="@(_checkedType)">
@foreach (IRoleTypeResponse roleType in _roleTypes)
{
<Radio Value="@(roleType.Id)">@roleType.Name</Radio>
}
</RadioGroup>
</div>
<div class="vstack">
@{
IEnumerable<TRole> roles = _roles.Where(x => x.TypeId == _checkedType);
}
@for (int i = 0; i < roles.Count(); i++)
{
TRole role = roles.ElementAt(i);
TRoleParent parent = ParentFunc(role, RoleParents);
if (i > 0)
{
<hr/>
}
<VerticalListItem @key="@(role)"
Name="@(NameFunc(role, parent))"
AdditionalInfo="@(AdditionalInfoFunc is not null ? AdditionalInfoFunc(role, parent) : null)"
PicturePlaceholder="@(PicturePlaceholder)"
PictureFunc="@(() => PictureFunc(role, parent))"
GetGlobalRatingMethod="@(async () =>
{
IApiResponse<RatingOverallResponse> response = await RolesClient.GetRoleRating(role.Id);
if (!response.IsSuccessful)
{
await Base.SnackbarStack.PushAsync($"An error occured. Rating for role with id {role.Id} could not be obtained.", SnackbarColor.Danger);
}
return response.Content;
})"
GetYourRatingMethod="@(async userId =>
{
IApiResponse<RatingUserResponse> response = await RolesClient.GetRoleUserRating(role.Id, userId);
if (!response.IsSuccessful)
{
if (response.StatusCode != HttpStatusCode.NotFound)
{
await Base.SnackbarStack.PushAsync($"An error occured. User rating for role with id {role.Id} could not be obtained.", SnackbarColor.Danger);
}
return null;
}
else
{
return Convert.ToInt32(response.Content.Rating);
}
})"
PutYourRatingMethod="@(async request =>
{
string token = await AuthenticationService.GetRawAccessTokenAsync() ?? string.Empty;
IApiResponse response = await RolesClient.PutRoleRating(token, role.Id, request);
if (!response.IsSuccessful)
{
await Base.SnackbarStack.PushAsync($"An error occured. Role could not be rated.", SnackbarColor.Danger);
}
})"
DeleteYourRatingMethod="@(async () =>
{
string token = await AuthenticationService.GetRawAccessTokenAsync() ?? string.Empty;
IApiResponse response = await RolesClient.DeleteRoleRating(token, role.Id);
if (!response.IsSuccessful)
{
await Base.SnackbarStack.PushAsync($"An error occured. Role could not be rated.", SnackbarColor.Danger);
}
})"
ItemUrl="@(UrlFunc(role, parent))"
PictureHeight="110"
NameSize="20"
OnRatingChanged="@(OnRatingChanged)"/>
}
</div>
}
</div>
}
else
{
<Loading Color="@(Loading.Colors.Light)"/>
}
</div>

View File

@@ -0,0 +1,107 @@
using Microsoft.AspNetCore.Components;
using WatchIt.Database.Model.Media;
using WatchIt.Database.Model.People;
using WatchIt.DTO.Models.Controllers.Media.Medium.Response;
using WatchIt.DTO.Models.Controllers.People.Person;
using WatchIt.DTO.Models.Controllers.Roles;
using WatchIt.DTO.Models.Controllers.Roles.Role.Query;
using WatchIt.DTO.Models.Controllers.Roles.Role.Response;
using WatchIt.DTO.Models.Generics.Image;
using WatchIt.DTO.Models.Generics.Rating;
using WatchIt.Website.Clients;
using WatchIt.Website.Components.Layout;
using WatchIt.Website.Services.Authentication;
namespace WatchIt.Website.Components.Panels.Common;
public partial class RolesPanel<TRole, TRoleParent> : Component where TRole : RoleResponse
{
#region SERVICES
[Inject] protected IRolesClient RolesClient { get; set; } = null!;
[Inject] protected IAuthenticationService AuthenticationService { get; set; } = null!;
#endregion
#region PARAMETERS
[Parameter] public required string Title { get; set; }
[Parameter] public IEnumerable<TRoleParent>? RoleParents { get; set; }
[Parameter] public required string ParentName { get; set; }
[Parameter] public required Func<TRole, TRoleParent, string> NameFunc { get; set; }
[Parameter] public required Func<TRole, TRoleParent, string>? AdditionalInfoFunc { get; set; }
[Parameter] public required string PicturePlaceholder { get; set; }
[Parameter] public required Func<TRole, TRoleParent, Task<ImageResponse?>> PictureFunc { get; set; }
[Parameter] public required Func<TRole, TRoleParent, string> UrlFunc { get; set; }
[Parameter] public required Func<TRole, TRoleParent, RatingOverallResponse> GlobalRatingFunc { get; set; }
[Parameter] public required Func<Task<IEnumerable<IRoleTypeResponse>?>> GetRoleTypesMethod { get; set; }
[Parameter] public required Func<Task<IEnumerable<TRole>?>> GetRolesMethod { get; set; }
[Parameter] public required Func<TRole, IEnumerable<TRoleParent>, TRoleParent> ParentFunc { get; set; }
[Parameter] public Action? OnRatingChanged { get; set; }
[Parameter] public string Class { get; set; } = string.Empty;
#endregion
#region FIELDS
private bool _loaded;
private IEnumerable<IRoleTypeResponse>? _roleTypes;
private IEnumerable<TRole>? _roles;
private short _checkedType;
private IEnumerable<TRole> _rolesVisible = [];
#endregion
#region PRIVATE METHODS
protected override async Task OnFirstRenderAsync()
{
await base.OnFirstRenderAsync();
await LoadRoles();
if (_roles is not null && _roles.Any())
{
await LoadRoleTypes();
}
if (_roleTypes is not null && _roleTypes.Any())
{
_checkedType = _roleTypes.First().Id;
//_rolesVisible = _roles.Where(x => x.TypeId == _checkedType);
}
_loaded = true;
StateHasChanged();
}
private async Task LoadRoles()
{
_roles = await GetRolesMethod();
}
private async Task LoadRoleTypes()
{
IEnumerable<IRoleTypeResponse>? roleTypesOriginal = await GetRoleTypesMethod();
IEnumerable<short>? roleTypesId = _roles!.Select(x => x.TypeId).Distinct();
_roleTypes = roleTypesOriginal?.Where(x => roleTypesId.Contains(x.Id));
}
private void CheckedTypeChanged(short value)
{
_checkedType = value;
_rolesVisible = _roles.Where(x => x.TypeId == value);
}
#endregion
}