Merge pull request #116 from mateuszskoczek/features/media_page_roles_panels

Features/media page roles panels
This commit is contained in:
2024-10-19 01:19:15 +02:00
committed by GitHub
Unverified
28 changed files with 545 additions and 14 deletions

View File

@@ -4,7 +4,7 @@ using WatchIt.Common.Query;
namespace WatchIt.Common.Model.Roles; namespace WatchIt.Common.Model.Roles;
public class ActorRoleResponse : ActorRole, IQueryOrderable<ActorRoleResponse> public class ActorRoleResponse : ActorRole, IQueryOrderable<ActorRoleResponse>, IRoleResponse
{ {
#region PROPERTIES #region PROPERTIES

View File

@@ -4,7 +4,7 @@ using WatchIt.Common.Query;
namespace WatchIt.Common.Model.Roles; namespace WatchIt.Common.Model.Roles;
public class CreatorRoleResponse : CreatorRole, IQueryOrderable<CreatorRoleResponse> public class CreatorRoleResponse : CreatorRole, IQueryOrderable<CreatorRoleResponse>, IRoleResponse
{ {
#region PROPERTIES #region PROPERTIES

View File

@@ -0,0 +1,9 @@
namespace WatchIt.Common.Model.Roles;
public interface IRoleResponse
{
Guid Id { get; set; }
long MediaId { get; set; }
long PersonId { get; set; }
short TypeId { get; set; }
}

View File

@@ -42,7 +42,14 @@ public static class Program
using (IServiceScope scope = app.Services.CreateScope()) using (IServiceScope scope = app.Services.CreateScope())
{ {
scope.ServiceProvider.GetService<DatabaseContext>().Database.Migrate(); DatabaseContext dbContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
while (!dbContext.Database.CanConnect())
{
Thread.Sleep(1000);
}
dbContext.Database.Migrate();
} }
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())

View File

@@ -5,5 +5,6 @@ public class ConfigurationData
public Logging Logging { get; set; } public Logging Logging { get; set; }
public string AllowedHosts { get; set; } public string AllowedHosts { get; set; }
public StorageKeys StorageKeys { get; set; } public StorageKeys StorageKeys { get; set; }
public Style Style { get; set; }
public Endpoints Endpoints { get; set; } public Endpoints Endpoints { get; set; }
} }

View File

@@ -0,0 +1,6 @@
namespace WatchIt.Website.Services.Utility.Configuration.Model;
public class Style
{
public int DefaultPanelPadding { get; set; }
}

View File

@@ -9,8 +9,10 @@
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png"/>
<!-- CSS --> <!-- CSS -->
<link rel="stylesheet" href="css/general.css?version=0.3.0.1"/> <link rel="stylesheet" href="css/general.css?version=0.3.0.2"/>
<link rel="stylesheet" href="css/panel.css?version=0.3.0.2"/>
<link rel="stylesheet" href="css/main_button.css?version=0.3.0.0"/> <link rel="stylesheet" href="css/main_button.css?version=0.3.0.0"/>
<link rel="stylesheet" href="css/gaps.css?version=0.3.0.0"/>
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.2.0.12"/> <link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.2.0.12"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

View File

@@ -0,0 +1,16 @@
@if (IsLoading)
{
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span>@LoadingContent</span>
}
else
{
if (ChildContent is null)
{
<span>@Content</span>
}
else
{
@(ChildContent)
}
}

View File

@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Components;
namespace WatchIt.Website.Components;
public partial class LoadingButtonContentComponent : ComponentBase
{
#region PARAMETERS
[Parameter] public bool IsLoading { get; set; }
[Parameter] public string? LoadingContent { get; set; }
[Parameter] public string Content { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
#endregion
}

View File

@@ -0,0 +1,10 @@
<div class="panel panel-padding-regular panel-radius-regular panel-background-regular @(Class)">
<div class="vstack gap-3">
<span class="panel-text-title">Actors</span>
<RoleListComponent Id="@(Id)"
TRole="WatchIt.Common.Model.Roles.ActorRoleResponse"
TQuery="WatchIt.Common.Model.Roles.ActorRoleMediaQueryParameters"
GetRolesAction="MediaWebAPIService.GetMediaAllActorRoles"
AdditionalTextSource="@(data => data.Name)"/>
</div>
</div>

View File

@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Components;
using WatchIt.Website.Services.WebAPI.Media;
namespace WatchIt.Website.Components.MediaPage;
public partial class ActorRolesPanelComponent : ComponentBase
{
#region SERVICES
[Inject] private IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
#endregion
#region PARAMETERS
[Parameter] public string Class { get; set; } = string.Empty;
[Parameter] public required long Id { get; set; }
#endregion
}

View File

@@ -0,0 +1,30 @@
@using WatchIt.Common.Model.Roles
<div class="panel panel-padding-regular panel-radius-regular panel-background-regular @(Class)">
@if (_loaded)
{
<div class="vstack gap-3">
<span class="panel-text-title">Creators</span>
<div class="d-flex justify-content-center">
<RadioGroup TValue="short" Color="Color.Default" Buttons Size="Size.Small" CheckedValue="@(_query.TypeId!.Value)" CheckedValueChanged="CheckedTypeChanged">
@foreach (RoleTypeResponse roleType in _roleTypes)
{
<Radio Value="@(roleType.Id)">@roleType.Name</Radio>
}
</RadioGroup>
</div>
<RoleListComponent @ref=@(_roleListComponent)
Id="@(Id)"
TRole="CreatorRoleResponse"
TQuery="CreatorRoleMediaQueryParameters"
GetRolesAction="MediaWebAPIService.GetMediaAllCreatorRoles"
Query="@(_query)"/>
</div>
}
else
{
<LoadingComponent Color="white"/>
}
</div>

View File

@@ -0,0 +1,71 @@
using Microsoft.AspNetCore.Components;
using WatchIt.Common.Model.Roles;
using WatchIt.Website.Services.WebAPI.Media;
using WatchIt.Website.Services.WebAPI.Roles;
namespace WatchIt.Website.Components.MediaPage;
public partial class CreatorRolesPanelComponent : ComponentBase
{
#region SERVICES
[Inject] private IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
[Inject] private IRolesWebAPIService RolesWebAPIService { get; set; } = default!;
#endregion
#region PARAMETERS
[Parameter] public string Class { get; set; } = string.Empty;
[Parameter] public required long Id { get; set; }
#endregion
#region FIELDS
private RoleListComponent<CreatorRoleResponse, CreatorRoleMediaQueryParameters> _roleListComponent;
private bool _loaded;
private IEnumerable<RoleTypeResponse> _roleTypes;
private CreatorRoleMediaQueryParameters _query;
#endregion
#region PRIVATE METHODS
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
List<Task> endTasks = new List<Task>();
// STEP 0
endTasks.AddRange(
[
RolesWebAPIService.GetAllCreatorRoleTypes(successAction: data => _roleTypes = data)
]);
// END
await Task.WhenAll(endTasks);
_query = new CreatorRoleMediaQueryParameters { TypeId = _roleTypes.First().Id };
_loaded = true;
StateHasChanged();
}
}
private async Task CheckedTypeChanged(short value)
{
_query.TypeId = value;
await _roleListComponent.Refresh();
}
#endregion
}

View File

@@ -0,0 +1,26 @@
@using WatchIt.Common.Model.Roles
@typeparam TRole where TRole : WatchIt.Common.Model.Roles.IRoleResponse
<div class="container-grid">
<div class="row">
<div class="col-auto">
<img id="picture" class="rounded-2 shadow object-fit-cover picture-aspect-ratio" src="@(_picture is not null ? _picture.ToString() : "assets/person_picture.png")" alt="picture" height="100"/>
</div>
<div class="col">
<div class="d-flex align-items-start flex-column h-100">
<div class="mb-auto">
<span id="nameText">
@if (_person is null)
{
<span>Loading...</span>
}
else
{
<strong>@(_person.Name)</strong>@(Role is ActorRoleResponse actor ? $" as {actor.Name}" : string.Empty)
}
</span>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,65 @@
using Microsoft.AspNetCore.Components;
using WatchIt.Common.Model;
using WatchIt.Common.Model.Persons;
using WatchIt.Common.Model.Roles;
using WatchIt.Website.Services.WebAPI.Persons;
namespace WatchIt.Website.Components.MediaPage;
public partial class RoleComponent<TRole> : ComponentBase where TRole : IRoleResponse
{
#region SERVICES
[Inject] private IPersonsWebAPIService PersonsWebAPIService { get; set; } = default!;
#endregion
#region PARAMETERS
[Parameter] public required TRole Role { get; set; }
#endregion
#region FIELDS
private PersonResponse? _person;
private Picture? _picture;
#endregion
#region PRIVATE METHODS
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
List<Task> endTasks = new List<Task>();
// STEP 0
endTasks.AddRange(
[
PersonsWebAPIService.GetPersonPhoto(Role.PersonId, data =>
{
_picture = data;
StateHasChanged();
}),
PersonsWebAPIService.GetPerson(Role.PersonId, data =>
{
_person = data;
StateHasChanged();
})
]);
// END
await Task.WhenAll(endTasks);
}
}
#endregion
}

View File

@@ -0,0 +1,40 @@
@typeparam TRole where TRole : WatchIt.Common.Model.Roles.IRoleResponse, WatchIt.Common.Query.IQueryOrderable<TRole>
@typeparam TQuery where TQuery : WatchIt.Common.Query.QueryParameters<TRole>
@if (_loaded)
{
if (_roles.Count > 0)
{
<div class="vstack">
@for (int i = 0; i < _roles.Count; i++)
{
if (i > 0)
{
<hr/>
}
<a class="text-reset text-decoration-none" href="/person/@(_roles[i].PersonId)">
<RoleComponent TRole="TRole"
Role="@(_roles[i])"/>
</a>
}
@if (!_allItemsLoaded)
{
<div class="d-flex justify-content-center">
<button class="btn btn-secondary" @onclick="@(async () => await GetRoles())">
<LoadingButtonContentComponent Content="Load more"
LoadingContent="Loading..."
IsLoading="@(_rolesFetching)"/>
</button>
</div>
}
</div>
}
else
{
<span class="text-center">No roles found</span>
}
}
else
{
<LoadingComponent/>
}

View File

@@ -0,0 +1,99 @@
using System.ComponentModel;
using Microsoft.AspNetCore.Components;
using WatchIt.Common.Model.Roles;
using WatchIt.Common.Query;
namespace WatchIt.Website.Components.MediaPage;
public partial class RoleListComponent<TRole, TQuery> : ComponentBase where TRole : IRoleResponse, IQueryOrderable<TRole> where TQuery : QueryParameters<TRole>
{
#region PROPERTIES
[Parameter] public required long Id { get; set; }
[Parameter] public required Func<long, TQuery, Action<IEnumerable<TRole>>, Task> GetRolesAction { get; set; }
[Parameter] public TQuery Query { get; set; } = Activator.CreateInstance<TQuery>();
[Parameter] public Func<TRole, string>? AdditionalTextSource { get; set; }
#endregion
#region FIELDS
private readonly int _pageSize = 20;
private bool _loaded;
private bool _allItemsLoaded;
private List<TRole> _roles = new List<TRole>();
private bool _rolesFetching;
#endregion
#region PUBLIC METHODS
public async Task Refresh()
{
_loaded = false;
_roles.Clear();
Query.After = null;
Query.First = _pageSize;
await GetRoles(true);
_loaded = true;
}
#endregion
#region PRIVATE METHODS
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
List<Task> endTasks = new List<Task>();
// INIT
Query.First = _pageSize;
// STEP 0
endTasks.AddRange(
[
GetRoles(true)
]);
// END
await Task.WhenAll(endTasks);
_loaded = true;
StateHasChanged();
}
}
private async Task GetRoles(bool firstFetch = false)
{
_rolesFetching = true;
await GetRolesAction(Id, Query, data =>
{
_roles.AddRange(data);
if (data.Count() < _pageSize)
{
_allItemsLoaded = true;
}
else
{
if (firstFetch)
{
Query.After = 0;
}
}
Query.After += data.Count();
_rolesFetching = false;
});
}
#endregion
}

View File

@@ -12,7 +12,7 @@
<div class="container-xl"> <div class="container-xl">
<div class="row sticky-top top-3 mb-2rem"> <div class="row sticky-top top-3 mb-2rem">
<div class="col"> <div class="col">
<div class="panel panel-header rounded-3 px-3"> <div class="panel panel-header">
<div class="container-grid"> <div class="container-grid">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col"> <div class="col">

View File

@@ -190,6 +190,31 @@ else
</div> </div>
</div> </div>
</div> </div>
<div class="row mt-over-panel-menu">
<div class="col">
<Tabs Pills
RenderMode="TabsRenderMode.LazyLoad"
SelectedTab="actors"
Class="panel panel-menu panel-background-menu">
<Items>
<Tab Name="actors">Actors</Tab>
<Tab Name="creators">Creators</Tab>
</Items>
<Content>
<TabPanel Name="actors">
<div class="mt-default">
<ActorRolesPanelComponent Id="@(Id)"/>
</div>
</TabPanel>
<TabPanel Name="creators">
<div class="mt-default">
<CreatorRolesPanelComponent Id="@(Id)"/>
</div>
</TabPanel>
</Content>
</Tabs>
</div>
</div>
} }
else else
{ {

View File

@@ -11,6 +11,7 @@
@using WatchIt.Website.Components @using WatchIt.Website.Components
@using WatchIt.Website.Components.PersonEditPage @using WatchIt.Website.Components.PersonEditPage
@using WatchIt.Website.Components.MediaEditPage @using WatchIt.Website.Components.MediaEditPage
@using WatchIt.Website.Components.MediaPage
@using WatchIt.Common.Model.Accounts @using WatchIt.Common.Model.Accounts
@using WatchIt.Common.Model.Media @using WatchIt.Common.Model.Media
@using WatchIt.Website.Services.Utility.Tokens @using WatchIt.Website.Services.Utility.Tokens

View File

@@ -10,6 +10,9 @@
"AccessToken": "access_token", "AccessToken": "access_token",
"RefreshToken": "refresh_token" "RefreshToken": "refresh_token"
}, },
"Style": {
"DefaultPanelPadding": 4
},
"Endpoints": { "Endpoints": {
"Base": "https://localhost:7160", "Base": "https://localhost:7160",
"Accounts": { "Accounts": {

View File

@@ -0,0 +1,17 @@
/* DEFAULT */
.mt-default {
margin-top: 1rem !important;
}
.gx-default {
--bs-gutter-x: 1rem !important;
}
/* OTHERS */
.mt-over-panel-menu {
margin-top: 3rem !important;
}

View File

@@ -25,6 +25,8 @@ body, html {
font-family: "PT Sans"; font-family: "PT Sans";
} }
/* CLASSES */ /* CLASSES */
.container-grid { .container-grid {
@@ -53,6 +55,8 @@ body, html {
.top-3 { .top-3 {
top: 1rem !important; top: 1rem !important;
} }
@@ -69,22 +73,16 @@ body, html {
margin-top: 9rem !important; margin-top: 9rem !important;
} }
.panel-header {
background-color: rgba(0, 0, 0, 0.8);
}
.panel-regular { .panel-regular {
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6) !important;
} }
.panel-yellow { .panel-yellow {
background-color: rgba(255, 184, 58, 0.6); background-color: rgba(255, 184, 58, 0.6) !important;
} }
.panel {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(25px);
}

View File

@@ -0,0 +1,68 @@
/* MAIN */
.panel {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(25px);
background-color: rgba(0, 0, 0, 0.6);
border-radius: 0.5rem;
padding: 1.5rem;
}
/* HEADER */
.panel-header {
padding: 0 1rem !important;
background-color: rgba(0, 0, 0, 0.8);
}
/* MENU */
.panel-menu {
gap: 1rem;
padding: 1rem 1.5rem !important;
background-color: rgba(0, 0, 0, 0.8);
}
.panel-menu > li > a {
--bs-nav-pills-link-active-bg: transparent;
--bs-nav-link-hover-color: white;
--bs-nav-link-color: #a6a6a6;
--bs-nav-pills-border-radius: 1.25rem;
padding: 0.25rem 0.5rem;
border-style: solid;
border-width: 2px;
border-color: transparent;
}
.panel-menu > li > a.active {
--bs-nav-link-color: white;
border-color: white;
}
/* BACKGROUNDS */
.panel-background-gold {
background-color: rgba(255, 184, 58, 0.6);
}
/* OTHERS */
.panel-text-title {
font-size: 1.5rem;
font-weight: bold;
}