Merge pull request #171 from mateuszskoczek/features/genres
genre page added
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using WatchIt.Common.Model.Genres;
|
using WatchIt.Common.Model.Genres;
|
||||||
|
using WatchIt.Common.Model.Media;
|
||||||
using WatchIt.WebAPI.Services.Controllers.Genres;
|
using WatchIt.WebAPI.Services.Controllers.Genres;
|
||||||
|
|
||||||
namespace WatchIt.WebAPI.Controllers;
|
namespace WatchIt.WebAPI.Controllers;
|
||||||
@@ -10,16 +11,20 @@ namespace WatchIt.WebAPI.Controllers;
|
|||||||
[Route("genres")]
|
[Route("genres")]
|
||||||
public class GenresController(IGenresControllerService genresControllerService) : ControllerBase
|
public class GenresController(IGenresControllerService genresControllerService) : ControllerBase
|
||||||
{
|
{
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
#region Main
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ProducesResponseType(typeof(IEnumerable<GenreResponse>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(IEnumerable<GenreResponse>), StatusCodes.Status200OK)]
|
||||||
public async Task<ActionResult> GetAll(GenreQueryParameters query) => await genresControllerService.GetAll(query);
|
public async Task<ActionResult> GetGenres(GenreQueryParameters query) => await genresControllerService.GetGenres(query);
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ProducesResponseType(typeof(GenreResponse), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(GenreResponse), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<ActionResult> Get([FromRoute]short id) => await genresControllerService.Get(id);
|
public async Task<ActionResult> GetGenre([FromRoute]short id) => await genresControllerService.GetGenre(id);
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@@ -27,7 +32,7 @@ public class GenresController(IGenresControllerService genresControllerService)
|
|||||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||||
public async Task<ActionResult> Post([FromBody]GenreRequest body) => await genresControllerService.Post(body);
|
public async Task<ActionResult> PostGenre([FromBody]GenreRequest body) => await genresControllerService.PostGenre(body);
|
||||||
|
|
||||||
[HttpPut("{id}")]
|
[HttpPut("{id}")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@@ -35,7 +40,7 @@ public class GenresController(IGenresControllerService genresControllerService)
|
|||||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<ActionResult> Put([FromRoute]short id, [FromBody]GenreRequest body) => await genresControllerService.Put(id, body);
|
public async Task<ActionResult> PutGenre([FromRoute]short id, [FromBody]GenreRequest body) => await genresControllerService.PutGenre(id, body);
|
||||||
|
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@@ -44,5 +49,19 @@ public class GenresController(IGenresControllerService genresControllerService)
|
|||||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<ActionResult> Delete([FromRoute]short id) => await genresControllerService.Delete(id);
|
public async Task<ActionResult> DeleteGenre([FromRoute]short id) => await genresControllerService.DeleteGenre(id);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Media
|
||||||
|
|
||||||
|
[HttpGet("{id}/media")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<MediaResponse>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult> GetGenreMedia([FromRoute]short id, MediaQueryParameters query) => await genresControllerService.GetGenreMedia(id, query);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using WatchIt.Common.Model.Genres;
|
using WatchIt.Common.Model.Genres;
|
||||||
|
using WatchIt.Common.Model.Media;
|
||||||
using WatchIt.Database;
|
using WatchIt.Database;
|
||||||
using WatchIt.Database.Model.Media;
|
using WatchIt.Database.Model.Media;
|
||||||
using WatchIt.WebAPI.Services.Controllers.Common;
|
using WatchIt.WebAPI.Services.Controllers.Common;
|
||||||
@@ -12,14 +13,16 @@ public class GenresControllerService(DatabaseContext database, IUserService user
|
|||||||
{
|
{
|
||||||
#region PUBLIC METHODS
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
public async Task<RequestResult> GetAll(GenreQueryParameters query)
|
#region Main
|
||||||
|
|
||||||
|
public async Task<RequestResult> GetGenres(GenreQueryParameters query)
|
||||||
{
|
{
|
||||||
IEnumerable<GenreResponse> data = await database.Genres.Select(x => new GenreResponse(x)).ToListAsync();
|
IEnumerable<GenreResponse> data = await database.Genres.Select(x => new GenreResponse(x)).ToListAsync();
|
||||||
data = query.PrepareData(data);
|
data = query.PrepareData(data);
|
||||||
return RequestResult.Ok(data);
|
return RequestResult.Ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestResult> Get(short id)
|
public async Task<RequestResult> GetGenre(short id)
|
||||||
{
|
{
|
||||||
Genre? item = await database.Genres.FirstOrDefaultAsync(x => x.Id == id);
|
Genre? item = await database.Genres.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
@@ -31,7 +34,7 @@ public class GenresControllerService(DatabaseContext database, IUserService user
|
|||||||
return RequestResult.Ok(data);
|
return RequestResult.Ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestResult> Post(GenreRequest data)
|
public async Task<RequestResult> PostGenre(GenreRequest data)
|
||||||
{
|
{
|
||||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||||
if (!validator.IsValid)
|
if (!validator.IsValid)
|
||||||
@@ -46,7 +49,7 @@ public class GenresControllerService(DatabaseContext database, IUserService user
|
|||||||
return RequestResult.Created($"genres/{item.Id}", new GenreResponse(item));
|
return RequestResult.Created($"genres/{item.Id}", new GenreResponse(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestResult> Put(short id, GenreRequest data)
|
public async Task<RequestResult> PutGenre(short id, GenreRequest data)
|
||||||
{
|
{
|
||||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||||
if (!validator.IsValid)
|
if (!validator.IsValid)
|
||||||
@@ -66,7 +69,7 @@ public class GenresControllerService(DatabaseContext database, IUserService user
|
|||||||
return RequestResult.Ok();
|
return RequestResult.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestResult> Delete(short id)
|
public async Task<RequestResult> DeleteGenre(short id)
|
||||||
{
|
{
|
||||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||||
if (!validator.IsValid)
|
if (!validator.IsValid)
|
||||||
@@ -88,6 +91,26 @@ public class GenresControllerService(DatabaseContext database, IUserService user
|
|||||||
|
|
||||||
return RequestResult.Ok();
|
return RequestResult.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Media
|
||||||
|
|
||||||
|
public async Task<RequestResult> GetGenreMedia(short id, MediaQueryParameters query)
|
||||||
|
{
|
||||||
|
if (!database.Genres.Any(x => x.Id == id))
|
||||||
|
{
|
||||||
|
return RequestResult.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<Database.Model.Media.Media> rawData = await database.Media.Where(x => x.MediaGenres.Any(y => y.GenreId == id))
|
||||||
|
.ToListAsync();
|
||||||
|
IEnumerable<MediaResponse> data = rawData.Select(x => new MediaResponse(x, database.MediaMovies.Any(y => y.Id == x.Id) ? MediaType.Movie : MediaType.Series));
|
||||||
|
data = query.PrepareData(data);
|
||||||
|
return RequestResult.Ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
using WatchIt.Common.Model.Genres;
|
using WatchIt.Common.Model.Genres;
|
||||||
|
using WatchIt.Common.Model.Media;
|
||||||
using WatchIt.WebAPI.Services.Controllers.Common;
|
using WatchIt.WebAPI.Services.Controllers.Common;
|
||||||
|
|
||||||
namespace WatchIt.WebAPI.Services.Controllers.Genres;
|
namespace WatchIt.WebAPI.Services.Controllers.Genres;
|
||||||
|
|
||||||
public interface IGenresControllerService
|
public interface IGenresControllerService
|
||||||
{
|
{
|
||||||
Task<RequestResult> GetAll(GenreQueryParameters query);
|
Task<RequestResult> GetGenres(GenreQueryParameters query);
|
||||||
Task<RequestResult> Get(short id);
|
Task<RequestResult> GetGenre(short id);
|
||||||
Task<RequestResult> Post(GenreRequest data);
|
Task<RequestResult> PostGenre(GenreRequest data);
|
||||||
Task<RequestResult> Put(short id, GenreRequest data);
|
Task<RequestResult> PutGenre(short id, GenreRequest data);
|
||||||
Task<RequestResult> Delete(short id);
|
Task<RequestResult> DeleteGenre(short id);
|
||||||
|
Task<RequestResult> GetGenreMedia(short id, MediaQueryParameters query);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
using WatchIt.Common.Model.Genders;
|
||||||
|
using WatchIt.Common.Model.Genres;
|
||||||
|
using WatchIt.Common.Model.Media;
|
||||||
|
using WatchIt.Common.Services.HttpClient;
|
||||||
|
using WatchIt.Website.Services.Configuration;
|
||||||
|
|
||||||
|
namespace WatchIt.Website.Services.Client.Genres;
|
||||||
|
|
||||||
|
public class GenresClientService : BaseClientService, IGenresClientService
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
private IHttpClientService _httpClientService;
|
||||||
|
private IConfigurationService _configurationService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public GenresClientService(IHttpClientService httpClientService, IConfigurationService configurationService) : base(configurationService)
|
||||||
|
{
|
||||||
|
_httpClientService = httpClientService;
|
||||||
|
_configurationService = configurationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
#region Main
|
||||||
|
|
||||||
|
public async Task GetGenres(GenreQueryParameters? query = null, Action<IEnumerable<GenreResponse>>? successAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Genres.GetGenres);
|
||||||
|
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||||
|
request.Query = query;
|
||||||
|
|
||||||
|
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task GetGenre(long id, Action<GenreResponse>? successAction = null, Action? notFoundAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Genres.GetGenre, id);
|
||||||
|
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||||
|
|
||||||
|
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor404NotFound(notFoundAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PostGenre(GenreRequest data, Action<GenreResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Genres.PostGenre);
|
||||||
|
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
|
||||||
|
request.Body = data;
|
||||||
|
|
||||||
|
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor400BadRequest(badRequestAction)
|
||||||
|
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||||
|
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteGenre(long id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Genres.DeleteGenre, id);
|
||||||
|
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||||
|
|
||||||
|
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||||
|
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Media
|
||||||
|
|
||||||
|
public async Task GetGenreMedia(short id, MediaQueryParameters? query = null, Action<IEnumerable<MediaResponse>>? successAction = null, Action notFoundAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Genres.GetGenreMedia, id);
|
||||||
|
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||||
|
request.Query = query;
|
||||||
|
|
||||||
|
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor404NotFound(notFoundAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected override string GetServiceBase() => EndpointsConfiguration.Genres.Base;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using WatchIt.Common.Model.Genres;
|
||||||
|
using WatchIt.Common.Model.Media;
|
||||||
|
|
||||||
|
namespace WatchIt.Website.Services.Client.Genres;
|
||||||
|
|
||||||
|
public interface IGenresClientService
|
||||||
|
{
|
||||||
|
Task GetGenres(GenreQueryParameters? query = null, Action<IEnumerable<GenreResponse>>? successAction = null);
|
||||||
|
Task GetGenre(long id, Action<GenreResponse>? successAction = null, Action? notFoundAction = null);
|
||||||
|
Task PostGenre(GenreRequest data, Action<GenreResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||||
|
Task DeleteGenre(long id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||||
|
Task GetGenreMedia(short id, MediaQueryParameters? query = null, Action<IEnumerable<MediaResponse>>? successAction = null, Action notFoundAction = null);
|
||||||
|
}
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
public class Genres
|
public class Genres
|
||||||
{
|
{
|
||||||
public string Base { get; set; }
|
public string Base { get; set; }
|
||||||
public string GetAll { get; set; }
|
public string GetGenres { get; set; }
|
||||||
public string Get { get; set; }
|
public string GetGenre { get; set; }
|
||||||
public string Post { get; set; }
|
public string PostGenre { get; set; }
|
||||||
public string Put { get; set; }
|
public string PutGenre { get; set; }
|
||||||
public string Delete { get; set; }
|
public string DeleteGenre { get; set; }
|
||||||
|
public string GetGenreMedia { get; set; }
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,7 @@ public partial class ListComponent<TItem, TQuery> : ComponentBase where TItem :
|
|||||||
[Parameter] public Func<long, RatingRequest, Task>? PutRatingMethod { get; set; }
|
[Parameter] public Func<long, RatingRequest, Task>? PutRatingMethod { get; set; }
|
||||||
[Parameter] public Func<long, Task>? DeleteRatingMethod { get; set; }
|
[Parameter] public Func<long, Task>? DeleteRatingMethod { get; set; }
|
||||||
[Parameter] public required string PosterPlaceholder { get; set; }
|
[Parameter] public required string PosterPlaceholder { get; set; }
|
||||||
|
[Parameter] public TQuery Query { get; set; } = Activator.CreateInstance<TQuery>()!;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -51,14 +52,6 @@ public partial class ListComponent<TItem, TQuery> : ComponentBase where TItem :
|
|||||||
private bool _itemsLoading;
|
private bool _itemsLoading;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public TQuery Query { get; set; } = Activator.CreateInstance<TQuery>()!;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
@inherits WatchIt.Website.Components.Common.ListComponent.FilterFormComponent<WatchIt.Common.Model.Media.MediaResponse, WatchIt.Common.Model.Media.MediaQueryParameters>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<EditForm Model="@(Query)">
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row mb-1">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="col-3 input-group-text">Type</span>
|
||||||
|
<InputSelect TValue="MediaType?" class="col form-control" @bind-Value="@(Query.Type)">
|
||||||
|
<option @onclick="() => Query.Type = null">No choice</option>
|
||||||
|
<option value="@(MediaType.Movie)">Movies</option>
|
||||||
|
<option value="@(MediaType.Series)">TV series</option>
|
||||||
|
</InputSelect>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="col-3 input-group-text">Title</span>
|
||||||
|
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.Title)"></InputText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-1">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="col-3 input-group-text">Original title</span>
|
||||||
|
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.OriginalTitle)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-1">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="col-3 input-group-text">Description</span>
|
||||||
|
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.Description)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-1">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="col-3 input-group-text">Release date</span>
|
||||||
|
<InputDate TValue="DateOnly?" class="col form-control" @bind-Value="@(Query.ReleaseDateFrom)"/>
|
||||||
|
<span class="col-auto input-group-text">-</span>
|
||||||
|
<InputDate TValue="DateOnly?" class="col form-control" @bind-Value="@(Query.ReleaseDateTo)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-1">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="col-3 input-group-text">Length</span>
|
||||||
|
<NumericEdit TValue="short?" Class="col form-control" Min="0" @bind-Value="@(Query.LengthFrom)"/>
|
||||||
|
<span class="col-auto input-group-text">-</span>
|
||||||
|
<NumericEdit TValue="short?" Class="col form-control" Min="0" @bind-Value="@(Query.LengthTo)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-1">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="col-3 input-group-text">Rating (count)</span>
|
||||||
|
<NumericEdit TValue="long?" Class="col form-control" Min="0" @bind-Value="@(Query.RatingCountFrom)"/>
|
||||||
|
<span class="col-auto input-group-text">-</span>
|
||||||
|
<NumericEdit TValue="long?" Class="col form-control" Min="0" @bind-Value="@(Query.RatingCountTo)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-1">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="col-3 input-group-text">Rating (average)</span>
|
||||||
|
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="@(0.01M)" @bind-Value="@(Query.RatingAverageFrom)"/>
|
||||||
|
<span class="col-auto input-group-text">-</span>
|
||||||
|
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="@(0.01M)" @bind-Value="@(Query.RatingAverageTo)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<div class="panel" role="button" @onclick="@(MediaData is not null ? () => NavigationManager.NavigateTo($"/media/{MediaData.Id}") : null)" style="cursor: @(MediaData is null ? "default" : "pointer")">
|
||||||
|
<div class="d-flex gap-3 align-items-center">
|
||||||
|
<PictureComponent Picture="_poster" Height="60" Placeholder="/assets/media_poster.png" AlternativeText="poster"/>
|
||||||
|
<div class="d-flex-inline flex-column">
|
||||||
|
<h2 id="primaryText" class="m-0">
|
||||||
|
@if (MediaData is null)
|
||||||
|
{
|
||||||
|
<span class="fw-bold">New @(MediaType)</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span><span class="fw-bold">@(MediaData.Title)</span>@(MediaData.ReleaseDate.HasValue ? $" ({MediaData.ReleaseDate.Value.Year})" : string.Empty)</span>
|
||||||
|
}
|
||||||
|
</h2>
|
||||||
|
<span id="secondaryText" class="text-secondary">Media settings</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using WatchIt.Common.Model.Media;
|
||||||
|
using WatchIt.Website.Services.Client.Media;
|
||||||
|
|
||||||
|
namespace WatchIt.Website.Components.Pages.MediaEditPage.Panels;
|
||||||
|
|
||||||
|
public partial class MediaEditPageHeaderPanelComponent : ComponentBase
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject] public IMediaClientService MediaClientService { get; set; } = default!;
|
||||||
|
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PARAMETERS
|
||||||
|
|
||||||
|
[Parameter] public required MediaResponse? MediaData { get; set; }
|
||||||
|
[Parameter] public required string MediaType { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private MediaPosterResponse? _poster;
|
||||||
|
private List<KeyValuePair<string, object>> _attr = new List<KeyValuePair<string, object>>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
if (MediaData is not null)
|
||||||
|
{
|
||||||
|
await MediaClientService.GetMediaPoster(MediaData.Id, data => _poster = data);
|
||||||
|
_attr.Add(new KeyValuePair<string, object>("@onclick", () => NavigationManager.NavigateTo($"/media/{MediaData.Id}")));
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/* IDS */
|
||||||
|
|
||||||
|
#primaryText {
|
||||||
|
margin-top: -8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#secondaryText {
|
||||||
|
color: lightgray !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
@using Blazorise.Extensions
|
||||||
|
@using WatchIt.Common.Model.Genres
|
||||||
|
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<div class="vstack gap-3">
|
||||||
|
<h4 class="fw-bold">Genres</h4>
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<InputSelect class="w-100 form-control" TValue="short?" @bind-Value="@(_selectedGenre)">
|
||||||
|
<option value="@(default(short?))" selected hidden="hidden">Choose genre...</option>
|
||||||
|
@foreach (GenreResponse genre in _availableGenres)
|
||||||
|
{
|
||||||
|
<option value="@(genre.Id)">@(genre.Name)</option>
|
||||||
|
}
|
||||||
|
</InputSelect>
|
||||||
|
<button class="btn btn-secondary" @onclick="AddGenre" disabled="@(_selectedGenre is null || _addLoading || _chosenGenres.Values.Any(x => x))">
|
||||||
|
<LoadingButtonContentComponent Content="Add" LoadingContent="Adding..." IsLoading="@(_addLoading)"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@if (_chosenGenres.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
<span class="text-center">No items</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<table class="table table-sm table-transparent">
|
||||||
|
<tbody>
|
||||||
|
@foreach (KeyValuePair<GenreResponse, bool> genre in _chosenGenres)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td class="align-middle">
|
||||||
|
@(genre.Key.Name)
|
||||||
|
</td>
|
||||||
|
<td class="align-middle table-cell-fit">
|
||||||
|
<button class="btn btn-outline-danger btn-sm w-100" type="button" disabled="@(_addLoading || genre.Value)" @onclick="@(() => RemoveGenre(genre.Key))">
|
||||||
|
<LoadingButtonContentComponent IsLoading="@(genre.Value)">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</LoadingButtonContentComponent>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using WatchIt.Common.Model.Genres;
|
||||||
|
using WatchIt.Common.Model.Media;
|
||||||
|
using WatchIt.Website.Services.Client.Genres;
|
||||||
|
using WatchIt.Website.Services.Client.Media;
|
||||||
|
|
||||||
|
namespace WatchIt.Website.Components.Pages.MediaEditPage.Panels;
|
||||||
|
|
||||||
|
public partial class MediaGenresEditPanelComponent : ComponentBase
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject] private IGenresClientService GenresClientService { get; set; } = default!;
|
||||||
|
[Inject] private IMediaClientService MediaClientService { get; set; } = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PARAMETERS
|
||||||
|
|
||||||
|
[Parameter] public required MediaResponse Data { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private Dictionary<GenreResponse, bool> _chosenGenres = new Dictionary<GenreResponse, bool>();
|
||||||
|
private List<GenreResponse> _availableGenres = new List<GenreResponse>();
|
||||||
|
|
||||||
|
private short? _selectedGenre;
|
||||||
|
private bool _addLoading;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
foreach (GenreResponse genre in Data.Genres)
|
||||||
|
{
|
||||||
|
_chosenGenres[genre] = false;
|
||||||
|
}
|
||||||
|
await GenresClientService.GetGenres(successAction: data =>
|
||||||
|
{
|
||||||
|
IEnumerable<short> tempSelected = _chosenGenres.Keys.Select(x => x.Id);
|
||||||
|
_availableGenres.AddRange(data.Where(x => !tempSelected.Contains(x.Id)));
|
||||||
|
});
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddGenre()
|
||||||
|
{
|
||||||
|
_addLoading = true;
|
||||||
|
await MediaClientService.PostMediaGenre(Data.Id, _selectedGenre!.Value, () =>
|
||||||
|
{
|
||||||
|
GenreResponse selectedGenre = _availableGenres.First(x => x.Id == _selectedGenre);
|
||||||
|
_availableGenres.Remove(selectedGenre);
|
||||||
|
_chosenGenres[selectedGenre] = false;
|
||||||
|
_addLoading = false;
|
||||||
|
_selectedGenre = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveGenre(GenreResponse genre)
|
||||||
|
{
|
||||||
|
_chosenGenres[genre] = true;
|
||||||
|
await MediaClientService.DeleteMediaGenre(Data.Id, genre.Id, () =>
|
||||||
|
{
|
||||||
|
_chosenGenres.Remove(genre);
|
||||||
|
_availableGenres.Add(genre);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="d-flex gap-3 align-items-center">
|
<div class="d-flex gap-3 align-items-center">
|
||||||
<AccountPictureComponent @ref="_accountPicture" Id="@(AccountData.Id)" Size="60"/>
|
<AccountPictureComponent @ref="_accountPicture" Id="@(AccountData.Id)" Size="60"/>
|
||||||
<div class="d-flex-inline flex-column">
|
<div class="d-flex-inline flex-column">
|
||||||
<h2 id="username" class="fw-bold m-0">@(AccountData.Username)</h2>
|
<h2 id="primaryText" class="fw-bold m-0">@(AccountData.Username)</h2>
|
||||||
<span id="secondaryText" class="text-secondary">User settings</span>
|
<span id="secondaryText" class="text-secondary">User settings</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* IDS */
|
/* IDS */
|
||||||
|
|
||||||
#username {
|
#primaryText {
|
||||||
margin-top: -8px !important;
|
margin-top: -8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
54
WatchIt.Website/WatchIt.Website/Pages/GenrePage.razor
Normal file
54
WatchIt.Website/WatchIt.Website/Pages/GenrePage.razor
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
@using System.Text
|
||||||
|
@using WatchIt.Website.Components.Common.ListComponent
|
||||||
|
|
||||||
|
@page "/genre/{id:int}"
|
||||||
|
|
||||||
|
@{
|
||||||
|
StringBuilder sb = new StringBuilder(" - WatchIt");
|
||||||
|
|
||||||
|
if (!_loaded) sb.Insert(0, "Loading...");
|
||||||
|
else if (_data is null) sb.Insert(0, "Error");
|
||||||
|
else sb.Insert(0, $"\"{_data.Name}\" genre");
|
||||||
|
|
||||||
|
<PageTitle>@(sb.ToString())</PageTitle>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@if (!_loaded)
|
||||||
|
{
|
||||||
|
<div class="m-5">
|
||||||
|
<LoadingComponent/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (_data is null)
|
||||||
|
{
|
||||||
|
<ErrorPanelComponent ErrorMessage="@($"Genre with ID {Id} was not found")"/>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<ListComponent TItem="MediaResponse"
|
||||||
|
TQuery="MediaQueryParameters"
|
||||||
|
Title="@(_data.Name)"
|
||||||
|
IdSource="@(item => item.Id)"
|
||||||
|
NameSource="@(item => item.Title)"
|
||||||
|
AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)"
|
||||||
|
RatingSource="@(item => item.Rating)"
|
||||||
|
UrlIdTemplate="/media/{0}"
|
||||||
|
PictureDownloadingTask="@((id, action) => MediaClientService.GetMediaPoster(id, action))"
|
||||||
|
ItemDownloadingTask="@((query, action) => GenresClientService.GetGenreMedia(_data.Id, query, action))"
|
||||||
|
SortingOptions="@(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "rating.count", "Number of ratings" },
|
||||||
|
{ "rating.average", "Average rating" },
|
||||||
|
{ "title", "Title" },
|
||||||
|
{ "release_date", "Release date" },
|
||||||
|
})"
|
||||||
|
PosterPlaceholder="/assets/media_poster.png"
|
||||||
|
GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))"
|
||||||
|
GetUserRatingMethod="@((id, userId, successAction, notfoundAction) => MediaClientService.GetMediaRatingByUser(id, userId, successAction, notfoundAction))"
|
||||||
|
PutRatingMethod="@((id, request) => MediaClientService.PutMediaRating(id, request))"
|
||||||
|
DeleteRatingMethod="@(id => MediaClientService.DeleteMediaRating(id))">
|
||||||
|
<MediaFilterFormComponent/>
|
||||||
|
</ListComponent>
|
||||||
|
}
|
||||||
55
WatchIt.Website/WatchIt.Website/Pages/GenrePage.razor.cs
Normal file
55
WatchIt.Website/WatchIt.Website/Pages/GenrePage.razor.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using WatchIt.Common.Model.Genres;
|
||||||
|
using WatchIt.Website.Layout;
|
||||||
|
using WatchIt.Website.Services.Client.Genres;
|
||||||
|
using WatchIt.Website.Services.Client.Media;
|
||||||
|
|
||||||
|
namespace WatchIt.Website.Pages;
|
||||||
|
|
||||||
|
public partial class GenrePage : ComponentBase
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject] private IGenresClientService GenresClientService { get; set; } = default!;
|
||||||
|
[Inject] private IMediaClientService MediaClientService { get; set; } = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PARAMETERS
|
||||||
|
|
||||||
|
[Parameter] public int Id { get; set; }
|
||||||
|
|
||||||
|
[CascadingParameter] public MainLayout Layout { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private bool _loaded;
|
||||||
|
private GenreResponse? _data;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
Layout.BackgroundPhoto = null;
|
||||||
|
|
||||||
|
await GenresClientService.GetGenre(Id, data => _data = data);
|
||||||
|
|
||||||
|
_loaded = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@using Microsoft.IdentityModel.Tokens
|
@using System.Text
|
||||||
|
@using Microsoft.IdentityModel.Tokens
|
||||||
@using WatchIt.Common.Model.Movies
|
@using WatchIt.Common.Model.Movies
|
||||||
@using WatchIt.Common.Model.Photos
|
@using WatchIt.Common.Model.Photos
|
||||||
@using WatchIt.Common.Model.Series
|
@using WatchIt.Common.Model.Series
|
||||||
@@ -7,361 +8,319 @@
|
|||||||
@page "/media/{id:long}/edit"
|
@page "/media/{id:long}/edit"
|
||||||
@page "/media/new/{type?}"
|
@page "/media/new/{type?}"
|
||||||
|
|
||||||
|
@{
|
||||||
|
StringBuilder sb = new StringBuilder(" - WatchIt");
|
||||||
|
|
||||||
<PageTitle>
|
if (!_loaded) sb.Insert(0, "Loading...");
|
||||||
WatchIt -
|
else if (!string.IsNullOrWhiteSpace(_error) || _user?.IsAdmin != true) sb.Insert(0, "Error");
|
||||||
@if (_loaded)
|
else
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_error) && _user?.IsAdmin == true)
|
if (_media is not null) sb.Insert(0, $"Edit \"{_media.Title}\"");
|
||||||
{
|
|
||||||
if (_media is not null)
|
|
||||||
{
|
|
||||||
@($"Edit \"")@(_media.Title)@("\"")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_movieRequest is null)
|
|
||||||
{
|
|
||||||
@("New TV series")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@("New movie")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@("Error")
|
if (_movieRequest is null) sb.Insert(0, "TV series");
|
||||||
|
else sb.Insert(0, "movie");
|
||||||
|
sb.Insert(0, "New ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<PageTitle>@(sb.ToString())</PageTitle>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="vstack gap-default">
|
||||||
|
@if (!_loaded)
|
||||||
|
{
|
||||||
|
<div class="m-5">
|
||||||
|
<LoadingComponent/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(_error))
|
||||||
|
{
|
||||||
|
<ErrorPanelComponent ErrorMessage="@_error"/>
|
||||||
|
}
|
||||||
|
else if (_user?.IsAdmin != true)
|
||||||
|
{
|
||||||
|
<ErrorPanelComponent ErrorMessage="You do not have permission to view this site"/>
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@("Loading")
|
<MediaEditPageHeaderPanelComponent MediaData="@(_media)"
|
||||||
}
|
MediaType="@(_movieRequest is null ? "TV series" : "movie")"/>
|
||||||
</PageTitle>
|
<div class="d-flex align-items-stretch gap-3">
|
||||||
|
<PictureEditorPanelComponent Id="@(Id)"
|
||||||
<div class="container-grid">
|
PictureGetTask="@(async (id, action) => await MediaClientService.GetMediaPoster(id, action))"
|
||||||
@if (_loaded)
|
PicturePutTask="@(async (id, data, action) => await MediaClientService.PutMediaPoster(id, new MediaPosterRequest(data), action))"
|
||||||
{
|
PictureDeleteTask="@(async (id, action) => await MediaClientService.DeleteMediaPoster(id, action))"
|
||||||
if (string.IsNullOrWhiteSpace(_error))
|
PicturePlaceholder="/assets/media_poster.png"/>
|
||||||
{
|
<div class="rounded-3 panel panel-regular p-4 w-100">
|
||||||
if (_user?.IsAdmin == true)
|
<EditForm Model="_mediaRequest">
|
||||||
{
|
<AntiforgeryToken/>
|
||||||
<div class="row">
|
<div class="container-grid">
|
||||||
<div class="col">
|
<div class="row form-group mb-1">
|
||||||
<div class="rounded-3 panel panel-regular p-2">
|
<label for="title" class="col-2 col-form-label">Title*</label>
|
||||||
<div class="m-0 mx-2 mb-1 p-0">
|
<div class="col-10">
|
||||||
@if (_media is not null)
|
<InputText id="title" class="form-control" @bind-Value="_mediaRequest!.Title"/>
|
||||||
{
|
</div>
|
||||||
<a class="text-decoration-none text-reset" href="/media/@(_media.Id)">
|
</div>
|
||||||
<h3>Edit @(_movieRequest is not null ? "movie" : "series") "@(_media.Title)"</h3>
|
<div class="row form-group my-1">
|
||||||
</a>
|
<label for="og-title" class="col-2 col-form-label">Original title</label>
|
||||||
}
|
<div class="col-10">
|
||||||
else
|
<InputText id="og-title" class="form-control" @bind-Value="_mediaRequest!.OriginalTitle"/>
|
||||||
{
|
</div>
|
||||||
<h3>Create new @(_movieRequest is not null ? "movie" : "series")</h3>
|
</div>
|
||||||
}
|
<div class="row form-group my-1">
|
||||||
|
<label for="desc" class="col-2 col-form-label">Description</label>
|
||||||
|
<div class="col-10">
|
||||||
|
<InputTextArea id="desc" class="form-control" @bind-Value="_mediaRequest!.Description"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row form-group my-1">
|
||||||
|
<label for="rel-date" class="col-2 col-form-label">Release date</label>
|
||||||
|
<div class="col-4">
|
||||||
|
<InputDate TValue="DateOnly?" id="rel-date" class="form-control" @bind-Value="_mediaRequest!.ReleaseDate"/>
|
||||||
|
</div>
|
||||||
|
<label for="length" class="col-2 col-form-label">Length</label>
|
||||||
|
<div class="col-4">
|
||||||
|
<InputNumber TValue="short?" id="length" class="form-control" @bind-Value="_mediaRequest!.Length"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_mediaRequest is MovieRequest)
|
||||||
|
{
|
||||||
|
<div class="row form-group mt-1">
|
||||||
|
<label for="budget" class="col-2 col-form-label">Budget</label>
|
||||||
|
<div class="col-10">
|
||||||
|
<InputNumber TValue="decimal?" id="budget" class="form-control" @bind-Value="((MovieRequest)_mediaRequest)!.Budget"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row form-group mt-1">
|
||||||
|
<label class="col-2 col-form-label">Has ended</label>
|
||||||
|
<div class="col-10 col-form-label">
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<InputRadioGroup TValue="bool" @bind-Value="((SeriesRequest)_mediaRequest)!.HasEnded">
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<InputRadio TValue="bool" Value="true"/>
|
||||||
|
Yes
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<InputRadio TValue="bool" Value="false"/>
|
||||||
|
No
|
||||||
|
</div>
|
||||||
|
</InputRadioGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col">
|
||||||
|
<div class="d-flex justify-content-end align-items-center gap-3">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_basicDataError))
|
||||||
|
{
|
||||||
|
<div class="text-danger">@_basicDataError</div>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="SaveBasicData">
|
||||||
|
@if (!_basicDataSaving)
|
||||||
|
{
|
||||||
|
<span>Save</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||||
|
<span>Saving...</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</EditForm>
|
||||||
<div class="row mt-3 gx-3">
|
</div>
|
||||||
<div class="col-auto">
|
</div>
|
||||||
<PictureEditorPanelComponent Id="@(Id)"
|
<Tabs Pills
|
||||||
PictureGetTask="@(async (id, action) => await MediaClientService.GetMediaPoster(id, action))"
|
RenderMode="TabsRenderMode.LazyLoad"
|
||||||
PicturePutTask="@(async (id, data, action) => await MediaClientService.PutMediaPoster(id, new MediaPosterRequest(data), action))"
|
Class="panel panel-menu panel-background-menu justify-content-center"
|
||||||
PictureDeleteTask="@(async (id, action) => await MediaClientService.DeleteMediaPoster(id, action))"
|
SelectedTab="genres">
|
||||||
PicturePlaceholder="/assets/media_poster.png"
|
<Items>
|
||||||
Class="h-100"/>
|
<Tab Name="genres">Genres</Tab>
|
||||||
</div>
|
<Tab Name="actors">Actors</Tab>
|
||||||
<div class="col">
|
<Tab Name="creators">Creators</Tab>
|
||||||
<div class="rounded-3 panel panel-regular p-4 h-100">
|
<Tab Name="photos">Photos</Tab>
|
||||||
<EditForm Model="_mediaRequest">
|
</Items>
|
||||||
<AntiforgeryToken/>
|
<Content>
|
||||||
<div class="container-grid">
|
<TabPanel Name="genres">
|
||||||
<div class="row form-group mb-1">
|
<MediaGenresEditPanelComponent Data="@(_media)"/>
|
||||||
<label for="title" class="col-2 col-form-label">Title*</label>
|
</TabPanel>
|
||||||
<div class="col-10">
|
<TabPanel Name="actors">
|
||||||
<InputText id="title" class="form-control" @bind-Value="_mediaRequest!.Title"/>
|
<MediaActorRolesEditPanelComponent Id="@(Id)"
|
||||||
</div>
|
Persons="@(_persons)"/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="creators">
|
||||||
|
<MediaCreatorRolesEditPanelComponent Id="@(Id)"
|
||||||
|
Persons="@(_persons)"/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="photos">
|
||||||
|
<div class="rounded-3 panel panel-regular p-4">
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<div class="d-flex align-items-center h-100">
|
||||||
|
<h4 class="m-0"><strong>Photos</strong></h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="row form-group my-1">
|
</div>
|
||||||
<label for="og-title" class="col-2 col-form-label">Original title</label>
|
<div class="col-auto">
|
||||||
<div class="col-10">
|
@if (!_photoEditMode)
|
||||||
<InputText id="og-title" class="form-control" @bind-Value="_mediaRequest!.OriginalTitle"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row form-group my-1">
|
|
||||||
<label for="desc" class="col-2 col-form-label">Description</label>
|
|
||||||
<div class="col-10">
|
|
||||||
<InputTextArea id="desc" class="form-control" @bind-Value="_mediaRequest!.Description"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row form-group my-1">
|
|
||||||
<label for="rel-date" class="col-2 col-form-label">Release date</label>
|
|
||||||
<div class="col-4">
|
|
||||||
<InputDate TValue="DateOnly?" id="rel-date" class="form-control" @bind-Value="_mediaRequest!.ReleaseDate"/>
|
|
||||||
</div>
|
|
||||||
<label for="length" class="col-2 col-form-label">Length</label>
|
|
||||||
<div class="col-4">
|
|
||||||
<InputNumber TValue="short?" id="length" class="form-control" @bind-Value="_mediaRequest!.Length"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if (_mediaRequest is MovieRequest)
|
|
||||||
{
|
{
|
||||||
<div class="row form-group mt-1">
|
<button type="button" class="btn btn-secondary" disabled="@(!Id.HasValue)" @onclick="() => InitEditPhoto(null)">Add new photo</button>
|
||||||
<label for="budget" class="col-2 col-form-label">Budget</label>
|
|
||||||
<div class="col-10">
|
|
||||||
<InputNumber TValue="decimal?" id="budget" class="form-control" @bind-Value="((MovieRequest)_mediaRequest)!.Budget"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="row form-group mt-1">
|
<div class="d-flex gap-3 align-items-center">
|
||||||
<label class="col-2 col-form-label">Has ended</label>
|
@if (!string.IsNullOrWhiteSpace(_photoEditError))
|
||||||
<div class="col-10 col-form-label">
|
|
||||||
<div class="d-flex gap-3">
|
|
||||||
<InputRadioGroup TValue="bool" @bind-Value="((SeriesRequest)_mediaRequest)!.HasEnded">
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<InputRadio TValue="bool" Value="true"/>
|
|
||||||
Yes
|
|
||||||
</div>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<InputRadio TValue="bool" Value="false"/>
|
|
||||||
No
|
|
||||||
</div>
|
|
||||||
</InputRadioGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col">
|
|
||||||
<div class="d-flex justify-content-end align-items-center gap-3">
|
|
||||||
@if (!string.IsNullOrWhiteSpace(_basicDataError))
|
|
||||||
{
|
|
||||||
<div class="text-danger">@_basicDataError</div>
|
|
||||||
}
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="SaveBasicData">
|
|
||||||
@if (!_basicDataSaving)
|
|
||||||
{
|
|
||||||
<span>Save</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
|
||||||
<span>Saving...</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</EditForm>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-3">
|
|
||||||
<div class="col">
|
|
||||||
<MediaActorRolesEditPanelComponent Id="@(Id)"
|
|
||||||
Persons="@(_persons)"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-3">
|
|
||||||
<div class="col">
|
|
||||||
<MediaCreatorRolesEditPanelComponent Id="@(Id)"
|
|
||||||
Persons="@(_persons)"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-3">
|
|
||||||
<div class="col">
|
|
||||||
<div class="rounded-3 panel panel-regular p-4">
|
|
||||||
<div class="container-grid">
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col">
|
|
||||||
<div class="d-flex align-items-center h-100">
|
|
||||||
<h4 class="m-0"><strong>Photos</strong></h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
@if (!_photoEditMode)
|
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-secondary" disabled="@(!Id.HasValue)" @onclick="() => InitEditPhoto(null)">Add new photo</button>
|
<div class="text-danger">
|
||||||
|
@_photoEditError
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
else
|
<button type="button" class="btn btn-secondary" disabled="@(_photoEditSaving)" @onclick="SaveEditPhoto">
|
||||||
{
|
@if (!_photoEditSaving)
|
||||||
<div class="d-flex gap-3 align-items-center">
|
|
||||||
@if (!string.IsNullOrWhiteSpace(_photoEditError))
|
|
||||||
{
|
|
||||||
<div class="text-danger">
|
|
||||||
@_photoEditError
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<button type="button" class="btn btn-secondary" disabled="@(_photoEditSaving)" @onclick="SaveEditPhoto">
|
|
||||||
@if (!_photoEditSaving)
|
|
||||||
{
|
|
||||||
<span>Save</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
|
||||||
<span>Saving...</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-secondary" disabled="@(_photoEditSaving)" @onclick="CancelEditPhoto">Cancel</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
@if (!_photoEditMode)
|
|
||||||
{
|
|
||||||
if (!_photos.IsNullOrEmpty())
|
|
||||||
{
|
{
|
||||||
<div id="scrollPhotos" class="d-flex p-3 gap-3" data-bs-spy="scroll" tabindex="0">
|
<span>Save</span>
|
||||||
@foreach (PhotoResponse photo in _photos)
|
|
||||||
{
|
|
||||||
<div class="container-grid photo-container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<PictureComponent Picture="@(photo)" AlternativeText="photo" Width="350" Placeholder="/assets/photo.png" AspectRatio="PictureComponent.PictureComponentAspectRatio.Photo"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-2 gx-2">
|
|
||||||
@if (photo.Background is not null)
|
|
||||||
{
|
|
||||||
<div class="col-auto">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div id="backgroundIndicator" class="border rounded-circle circle-@(photo.Background.IsUniversalBackground ? "blue" : "grey") p-1" data-toggle="tooltip" data-placement="top" title="@(photo.Background.IsUniversalBackground ? "Universal" : "Media-only") background">
|
|
||||||
<img class="no-vertical-align" src="assets/icons/background.png" alt="background_icon" height="20px" width="20px"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="col">
|
|
||||||
<div class="d-flex align-items-center h-100 text-size-upload-date">
|
|
||||||
Upload: @(photo.UploadDate.ToString())
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<button type="button" class="btn btn-secondary btn-sm" @onclick="() => InitEditPhoto(photo.Id)" disabled="@(_photoDeleting.Contains(photo.Id))">
|
|
||||||
<img src="assets/icons/edit.png" alt="edit_icon" height="20px" width="20px"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<button type="button" class="btn btn-danger btn-sm" disabled="@(_photoDeleting.Contains(photo.Id))" @onclick="() => DeletePhoto(photo.Id)">
|
|
||||||
@if (!_photoDeleting.Contains(photo.Id))
|
|
||||||
{
|
|
||||||
<img src="assets/icons/delete.png" alt="delete_icon" height="20px" width="20px"/>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="d-flex justify-content-center">
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||||
Photo list is empty
|
<span>Saving...</span>
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
}
|
</button>
|
||||||
else
|
<button type="button" class="btn btn-secondary" disabled="@(_photoEditSaving)" @onclick="CancelEditPhoto">Cancel</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
@if (!_photoEditMode)
|
||||||
|
{
|
||||||
|
if (!_photos.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
<div id="scrollPhotos" class="d-flex p-3 gap-3" data-bs-spy="scroll" tabindex="0">
|
||||||
|
@foreach (PhotoResponse photo in _photos)
|
||||||
{
|
{
|
||||||
<div class="container-grid">
|
<div class="container-grid photo-container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto">
|
<div class="col">
|
||||||
<div class="container-grid">
|
<PictureComponent Picture="@(photo)" AlternativeText="photo" Width="350" Placeholder="/assets/photo.png" AspectRatio="PictureComponent.PictureComponentAspectRatio.Photo"/>
|
||||||
<div class="row">
|
</div>
|
||||||
<div class="col">
|
</div>
|
||||||
<PictureComponent Picture="@(_photoEditRequest)" Placeholder="/assets/photo.png" AlternativeText="edit_photo" Width="300" AspectRatio="PictureComponent.PictureComponentAspectRatio.Photo"/>
|
<div class="row mt-2 gx-2">
|
||||||
</div>
|
@if (photo.Background is not null)
|
||||||
</div>
|
{
|
||||||
@if (_photoEditId is null)
|
<div class="col-auto">
|
||||||
{
|
<div class="d-flex align-items-center">
|
||||||
<div class="row mt-2">
|
<div id="backgroundIndicator" class="border rounded-circle circle-@(photo.Background.IsUniversalBackground ? "blue" : "grey") p-1" data-toggle="tooltip" data-placement="top" title="@(photo.Background.IsUniversalBackground ? "Universal" : "Media-only") background">
|
||||||
<div class="col">
|
<img class="no-vertical-align" src="assets/icons/background.png" alt="background_icon" height="20px" width="20px"/>
|
||||||
<InputFile class="form-control" OnChange="LoadPhoto" autocomplete="off" style="width: 300px;"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
</div>
|
||||||
<div class="container-grid">
|
}
|
||||||
<div class="row form-group">
|
<div class="col">
|
||||||
<div class="col">
|
<div class="d-flex align-items-center h-100 text-size-upload-date">
|
||||||
<div class="form-check">
|
Upload: @(photo.UploadDate.ToString())
|
||||||
<InputCheckbox class="form-check-input" @bind-Value="_photoEditIsBackground"/>
|
</div>
|
||||||
<label class="form-check-label">Use as background</label>
|
</div>
|
||||||
</div>
|
<div class="col-auto">
|
||||||
</div>
|
<button type="button" class="btn btn-secondary btn-sm" @onclick="() => InitEditPhoto(photo.Id)" disabled="@(_photoDeleting.Contains(photo.Id))">
|
||||||
<div class="col">
|
<img src="assets/icons/edit.png" alt="edit_icon" height="20px" width="20px"/>
|
||||||
<div class="form-check">
|
</button>
|
||||||
<InputCheckbox class="form-check-input" @bind-Value="_photoEditBackgroundData.IsUniversalBackground" disabled="@(!_photoEditIsBackground)"/>
|
</div>
|
||||||
<label class="form-check-label">Use as universal background</label>
|
<div class="col-auto">
|
||||||
</div>
|
<button type="button" class="btn btn-danger btn-sm" disabled="@(_photoDeleting.Contains(photo.Id))" @onclick="() => DeletePhoto(photo.Id)">
|
||||||
</div>
|
@if (!_photoDeleting.Contains(photo.Id))
|
||||||
|
{
|
||||||
|
<img src="assets/icons/delete.png" alt="delete_icon" height="20px" width="20px"/>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
Photo list is empty
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<PictureComponent Picture="@(_photoEditRequest)" Placeholder="/assets/photo.png" AlternativeText="edit_photo" Width="300" AspectRatio="PictureComponent.PictureComponentAspectRatio.Photo"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_photoEditId is null)
|
||||||
|
{
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col">
|
||||||
|
<InputFile class="form-control" OnChange="LoadPhoto" autocomplete="off" style="width: 300px;"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row form-group">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-check">
|
||||||
|
<InputCheckbox class="form-check-input" @bind-Value="_photoEditIsBackground"/>
|
||||||
|
<label class="form-check-label">Use as background</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="row form-group my-1">
|
</div>
|
||||||
<label for="first-gradient-color" class="col-4 col-form-label">First gradient color</label>
|
<div class="col">
|
||||||
<div class="col-8">
|
<div class="form-check">
|
||||||
<input type="color" class="form-control form-control-color w-100" id="first-gradient-color" value="#@(Convert.ToHexString(_photoEditBackgroundData.FirstGradientColor))" disabled="@(!_photoEditIsBackground)" @onchange="EditPhotoFirstGradientColorChanged">
|
<InputCheckbox class="form-check-input" @bind-Value="_photoEditBackgroundData.IsUniversalBackground" disabled="@(!_photoEditIsBackground)"/>
|
||||||
</div>
|
<label class="form-check-label">Use as universal background</label>
|
||||||
</div>
|
|
||||||
<div class="row form-group">
|
|
||||||
<label for="second-gradient-color" class="col-4 col-form-label">Second gradient color</label>
|
|
||||||
<div class="col-8">
|
|
||||||
<input type="color" class="form-control form-control-color w-100" id="second-gradient-color" value="#@(Convert.ToHexString(_photoEditBackgroundData.SecondGradientColor))" disabled="@(!_photoEditIsBackground)" @onchange="EditPhotoSecondGradientColorChanged">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row form-group my-1">
|
||||||
|
<label for="first-gradient-color" class="col-4 col-form-label">First gradient color</label>
|
||||||
|
<div class="col-8">
|
||||||
|
<input type="color" class="form-control form-control-color w-100" id="first-gradient-color" value="#@(Convert.ToHexString(_photoEditBackgroundData.FirstGradientColor))" disabled="@(!_photoEditIsBackground)" @onchange="EditPhotoFirstGradientColorChanged">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row form-group">
|
||||||
|
<label for="second-gradient-color" class="col-4 col-form-label">Second gradient color</label>
|
||||||
|
<div class="col-8">
|
||||||
|
<input type="color" class="form-control form-control-color w-100" id="second-gradient-color" value="#@(Convert.ToHexString(_photoEditBackgroundData.SecondGradientColor))" disabled="@(!_photoEditIsBackground)" @onchange="EditPhotoSecondGradientColorChanged">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</TabPanel>
|
||||||
}
|
</Content>
|
||||||
else
|
</Tabs>
|
||||||
{
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<ErrorPanelComponent ErrorMessage="You do not have permission to view this site"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<ErrorPanelComponent ErrorMessage="@_error"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<div class="m-5">
|
|
||||||
<LoadingComponent/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -10,6 +10,7 @@ using WatchIt.Website.Services.Configuration;
|
|||||||
using WatchIt.Website.Services.Tokens;
|
using WatchIt.Website.Services.Tokens;
|
||||||
using WatchIt.Website.Services.Client.Accounts;
|
using WatchIt.Website.Services.Client.Accounts;
|
||||||
using WatchIt.Website.Services.Client.Genders;
|
using WatchIt.Website.Services.Client.Genders;
|
||||||
|
using WatchIt.Website.Services.Client.Genres;
|
||||||
using WatchIt.Website.Services.Client.Media;
|
using WatchIt.Website.Services.Client.Media;
|
||||||
using WatchIt.Website.Services.Client.Movies;
|
using WatchIt.Website.Services.Client.Movies;
|
||||||
using WatchIt.Website.Services.Client.Persons;
|
using WatchIt.Website.Services.Client.Persons;
|
||||||
@@ -75,6 +76,7 @@ public static class Program
|
|||||||
// WebAPI
|
// WebAPI
|
||||||
builder.Services.AddScoped<IAccountsClientService, AccountsClientService>();
|
builder.Services.AddScoped<IAccountsClientService, AccountsClientService>();
|
||||||
builder.Services.AddSingleton<IGendersClientService, GendersClientService>();
|
builder.Services.AddSingleton<IGendersClientService, GendersClientService>();
|
||||||
|
builder.Services.AddSingleton<IGenresClientService, GenresClientService>();
|
||||||
builder.Services.AddSingleton<IMediaClientService, MediaClientService>();
|
builder.Services.AddSingleton<IMediaClientService, MediaClientService>();
|
||||||
builder.Services.AddSingleton<IMoviesClientService, MoviesClientService>();
|
builder.Services.AddSingleton<IMoviesClientService, MoviesClientService>();
|
||||||
builder.Services.AddSingleton<ISeriesClientService, SeriesClientService>();
|
builder.Services.AddSingleton<ISeriesClientService, SeriesClientService>();
|
||||||
|
|||||||
@@ -46,11 +46,12 @@
|
|||||||
},
|
},
|
||||||
"Genres": {
|
"Genres": {
|
||||||
"Base": "/genres",
|
"Base": "/genres",
|
||||||
"GetAll": "",
|
"GetGenres": "",
|
||||||
"Get": "/{0}",
|
"GetGenre": "/{0}",
|
||||||
"Post": "",
|
"PostGenre": "",
|
||||||
"Put": "/{0}",
|
"PutGenre": "/{0}",
|
||||||
"Delete": "/{0}"
|
"DeleteGenre": "/{0}",
|
||||||
|
"GetGenreMedia": "/{0}/media"
|
||||||
},
|
},
|
||||||
"Media": {
|
"Media": {
|
||||||
"Base": "/media",
|
"Base": "/media",
|
||||||
|
|||||||
Reference in New Issue
Block a user