Most Changes for v0.1.0-beta #51
@@ -1,7 +1,6 @@
|
|||||||
@page "/GlobalList"
|
@page "/GlobalList"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
|
|
||||||
@using System.Security.Claims
|
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using CouchLog.Data
|
@using CouchLog.Data
|
||||||
@using Microsoft.AspNetCore.Identity
|
@using Microsoft.AspNetCore.Identity
|
||||||
@@ -11,17 +10,17 @@
|
|||||||
@inject ApplicationDbContext CouchLogDb
|
@inject ApplicationDbContext CouchLogDb
|
||||||
@inject UserManager<ApplicationUser> UserManager
|
@inject UserManager<ApplicationUser> UserManager
|
||||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||||
@inject NavigationManager NavigationManager
|
|
||||||
@inject IDialogService DialogService
|
@inject IDialogService DialogService
|
||||||
@inject ISnackbar Snackbar
|
@inject ISnackbar Snackbar
|
||||||
|
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
<PageTitle>GlobalList</PageTitle>
|
<PageTitle>GlobalList</PageTitle>
|
||||||
|
@*
|
||||||
TODO: Add Paging
|
TODO: Add Paging
|
||||||
TODO: Add only reload for the pages
|
TODO: Add only reload for the pages
|
||||||
TODO: Make Search work
|
TODO: Make Search work
|
||||||
|
*@
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.False">
|
<MudContainer MaxWidth="MaxWidth.False">
|
||||||
@*Top of the Page*@
|
@*Top of the Page*@
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
@using CouchLog.Data
|
||||||
|
|
||||||
|
<MudContainer>
|
||||||
|
<MudText Typo="Typo.h6">@PrivateEntity.GlobalEntity.Title</MudText>
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter] public PrivateEntity PrivateEntity { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
@page "/PrivateList"
|
@page "/PrivateList"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
|
|
||||||
|
@using CouchLog.Components.Pages.PrivateList.Components
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using CouchLog.Data
|
@using CouchLog.Data
|
||||||
@using Microsoft.AspNetCore.Identity
|
@using Microsoft.AspNetCore.Identity
|
||||||
@@ -15,218 +16,79 @@
|
|||||||
|
|
||||||
<PageTitle>Private List</PageTitle>
|
<PageTitle>Private List</PageTitle>
|
||||||
|
|
||||||
<div class="container-fluid mt-4 px-4">
|
<MudContainer MaxWidth="MaxWidth.False">
|
||||||
|
@*Top of the Page*@
|
||||||
|
<MudContainer>
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
|
@*Page Title*@
|
||||||
|
<MudText Typo="Typo.h3" Style="font-weight: bold;">Private List</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
<!-- Active Watching Section -->
|
<br/>
|
||||||
@if (ActiveWatchingEntities.Any())
|
|
||||||
{
|
|
||||||
<div class="mb-5">
|
|
||||||
<h2 class="mb-4">Aktiv am Schauen</h2>
|
|
||||||
<div class="row g-4">
|
|
||||||
@foreach (var entity in ActiveWatchingEntities)
|
|
||||||
{
|
|
||||||
@RenderEntityCard(entity)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Library / Completed Section -->
|
<MudContainer>
|
||||||
@if (LibraryEntities.Any())
|
<MudPaper Class="pa-2" Elevation="1">
|
||||||
{
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
<div class="mb-5">
|
<MudTextField @bind-Value="_searchString"
|
||||||
<h2 class="mb-4">Bibliothek</h2>
|
Placeholder="Search..."
|
||||||
<div class="row g-4">
|
Adornment="Adornment.Start"
|
||||||
@foreach (var entity in LibraryEntities)
|
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||||
{
|
Immediate="true"
|
||||||
@RenderEntityCard(entity)
|
Variant="Variant.Outlined"
|
||||||
}
|
Margin="Margin.Dense"
|
||||||
</div>
|
Style="max-width: 300px;"/>
|
||||||
</div>
|
</MudStack>
|
||||||
}
|
</MudPaper>
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
@if (!PrivateEntities.Any())
|
<br/>
|
||||||
{
|
|
||||||
<div class="text-center text-muted py-5">
|
<MudContainer MaxWidth="MaxWidth.False">
|
||||||
<p>Keine Einträge vorhanden.</p>
|
<MudGrid Spacing="6" Justify="Justify.Center">
|
||||||
</div>
|
@foreach (var privateEntity in ActiveWatchingEntities)
|
||||||
}
|
{
|
||||||
</div>
|
<EntityCard PrivateEntity="privateEntity" />
|
||||||
|
}
|
||||||
|
@foreach (var privateEntity in LibraryEntities)
|
||||||
|
{
|
||||||
|
<EntityCard PrivateEntity="privateEntity" />
|
||||||
|
}
|
||||||
|
</MudGrid>
|
||||||
|
</MudContainer>
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private List<PrivateEntity> PrivateEntities = new List<PrivateEntity>();
|
//Search
|
||||||
private List<UserWatchStatus> userWatchStatuses = new List<UserWatchStatus>();
|
private string _searchString;
|
||||||
ApplicationUser? AppUser = new();
|
|
||||||
|
|
||||||
private List<PrivateEntity> ActiveWatchingEntities => PrivateEntities.Where(x => x.UserWatchStatus?.Name == "Watching").ToList();
|
//User
|
||||||
private List<PrivateEntity> LibraryEntities => PrivateEntities.Where(x => x.UserWatchStatus?.Name != "Watching").ToList();
|
private ApplicationUser? _appUser;
|
||||||
|
|
||||||
|
//Entities
|
||||||
|
private List<PrivateEntity> _privateEntities = [];
|
||||||
|
private List<UserWatchStatus> _userWatchStatuses = [];
|
||||||
|
|
||||||
|
private List<PrivateEntity> ActiveWatchingEntities => _privateEntities.Where(x => x.UserWatchStatus?.Name == "Watching").ToList();
|
||||||
|
private List<PrivateEntity> LibraryEntities => _privateEntities.Where(x => x.UserWatchStatus?.Name != "Watching").ToList();
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
var AuthState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
AppUser = await UserManager.GetUserAsync(AuthState.User);
|
_appUser = await UserManager.GetUserAsync(authState.User);
|
||||||
|
|
||||||
if (AppUser != null)
|
if (_appUser != null)
|
||||||
{
|
{
|
||||||
PrivateEntities = await CouchLogDB.PrivateEntities
|
_privateEntities = await CouchLogDB.PrivateEntities
|
||||||
.Where(x => x.UserId == AppUser.Id)
|
.Where(x => x.UserId == _appUser.Id)
|
||||||
.Include(x => x.GlobalEntity)
|
.Include(x => x.GlobalEntity)
|
||||||
.Include(x => x.UserWatchStatus)
|
.Include(x => x.UserWatchStatus)
|
||||||
.Include(x => x.GlobalEntity.MediaType)
|
.Include(x => x.GlobalEntity.MediaType)
|
||||||
.OrderByDescending(Entity => Entity.LastChange ?? Entity.CreationTime)
|
.OrderByDescending(entity => entity.LastChange ?? entity.CreationTime)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
userWatchStatuses = await CouchLogDB.UserWatchStatuses.OrderByDescending(Entity => Entity.Id).ToListAsync();
|
_userWatchStatuses = await CouchLogDB.UserWatchStatuses.OrderByDescending(entity => entity.Id).ToListAsync();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private RenderFragment RenderEntityCard(PrivateEntity Entity) => __builder =>
|
|
||||||
{
|
|
||||||
<div class="col-12 col-lg-6 col-xxl-4">
|
|
||||||
<div class="card shadow-sm border-0 overflow-hidden private-entity-card">
|
|
||||||
<div class="row g-0 h-100">
|
|
||||||
|
|
||||||
<div class="col-auto" style="flex: 0 0 auto; min-width: 120px; max-width: 150px;">
|
|
||||||
@if (!string.IsNullOrEmpty(Entity.GlobalEntity?.PicturePath))
|
|
||||||
{
|
|
||||||
<img src="/@Entity.GlobalEntity.PicturePath"
|
|
||||||
class="entity-img w-100 h-100"
|
|
||||||
style="object-fit: cover;"
|
|
||||||
alt="@Entity.GlobalEntity.Title">
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="d-flex align-items-center justify-content-center h-100 bg-light text-muted small">
|
|
||||||
Kein Bild
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Content -->
|
|
||||||
<div class="col" style="min-width: 0;">
|
|
||||||
<div class="card-body d-flex flex-column h-100 py-2 px-3">
|
|
||||||
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
||||||
<div style="min-width: 0; flex: 1;">
|
|
||||||
<h5 class="card-title fw-bold mb-0" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
|
||||||
@Entity.GlobalEntity?.Title
|
|
||||||
</h5>
|
|
||||||
<small class="text-muted meta-text">
|
|
||||||
@Entity.CreationTime.ToShortDateString()
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dropdown ms-2" style="flex-shrink: 0;">
|
|
||||||
<button class="btn btn-link menu-btn" type="button" data-bs-toggle="modal" data-bs-target="#modal-@Entity.Id">
|
|
||||||
⋮
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Watch Status Dropdown -->
|
|
||||||
<select class="form-select"
|
|
||||||
value="@Entity.UserWatchStatusId"
|
|
||||||
@onchange="@(e => UpdateWatchStatus(Entity, e.Value))">
|
|
||||||
@foreach (var status in userWatchStatuses)
|
|
||||||
{
|
|
||||||
<option value="@status.Id">@status.Name</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
@if (Entity.GlobalEntity?.MediaType.Name == "Series" || Entity.GlobalEntity?.MediaType.Name == "Anime")
|
|
||||||
{
|
|
||||||
<select class="form-select" value="@Entity.Season" @onchange="@(e => UpdateSeason(Entity, e.Value))">
|
|
||||||
@{
|
|
||||||
int currentSeason = Entity.Season ?? 1;
|
|
||||||
int startOffsetSeason = currentSeason > 1 ? -1 : 0;
|
|
||||||
}
|
|
||||||
@for (int i = startOffsetSeason; i <= 5; i++)
|
|
||||||
{
|
|
||||||
int season = currentSeason + i;
|
|
||||||
<option value="@season">Staffel @season</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select class="form-select" value="@Entity.Episode" @onchange="@(e => UpdateEpisode(Entity, e.Value))">
|
|
||||||
@{
|
|
||||||
int currentEpisode = Entity.Episode ?? 1;
|
|
||||||
int startOffsetEpisode = currentEpisode > 1 ? -1 : 0;
|
|
||||||
}
|
|
||||||
@for (int i = startOffsetEpisode; i <= 5; i++)
|
|
||||||
{
|
|
||||||
int episode = currentEpisode + i;
|
|
||||||
<option value="@episode">Episode @episode</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Footer Button -->
|
|
||||||
<div class="mt-auto d-flex justify-content-end">
|
|
||||||
<button class="btn btn-outline-secondary btn-sm btn-details" type="button">Details</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modal -->
|
|
||||||
<div class="modal fade" id="modal-@Entity.Id" tabindex="-1" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Optionen</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<button type="button" class="btn btn-danger" @onclick="@(e => RemoveEntityFromPrivateList(Entity))" data-bs-dismiss="modal">Aus PrivateList entfernen</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
};
|
|
||||||
|
|
||||||
private async Task RemoveEntityFromPrivateList(PrivateEntity entity)
|
|
||||||
{
|
|
||||||
//Delete DB
|
|
||||||
CouchLogDB.PrivateEntities.Remove(entity);
|
|
||||||
await CouchLogDB.SaveChangesAsync();
|
|
||||||
|
|
||||||
//Delete Memory List
|
|
||||||
PrivateEntities.Remove(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateWatchStatus(PrivateEntity entity, object? newValue)
|
|
||||||
{
|
|
||||||
if (int.TryParse(newValue?.ToString(), out int newId))
|
|
||||||
{
|
|
||||||
entity.UserWatchStatusId = newId;
|
|
||||||
entity.UserWatchStatus = userWatchStatuses.FirstOrDefault(s => s.Id == newId);
|
|
||||||
await CouchLogDB.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateSeason(PrivateEntity entity, object? newSeasonValue)
|
|
||||||
{
|
|
||||||
if (int.TryParse(newSeasonValue?.ToString(), out int newSeason))
|
|
||||||
{
|
|
||||||
entity.Season = newSeason;
|
|
||||||
entity.Episode = 1;
|
|
||||||
await CouchLogDB.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateEpisode(PrivateEntity entity, object? newEpisodeValue)
|
|
||||||
{
|
|
||||||
if (int.TryParse(newEpisodeValue?.ToString(), out int newEpisode))
|
|
||||||
{
|
|
||||||
entity.Episode = newEpisode;
|
|
||||||
await CouchLogDB.SaveChangesAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/* PrivateList.razor.css */
|
|
||||||
|
|
||||||
.private-entity-card {
|
|
||||||
height: 220px;
|
|
||||||
background-color: #fff;
|
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.private-entity-card:hover {
|
|
||||||
transform: translateY(-3px);
|
|
||||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
NEU: Feste Breite für das Bild!
|
|
||||||
150px Breite bei 220px Höhe passt perfekt für Poster.
|
|
||||||
*/
|
|
||||||
.img-wrapper {
|
|
||||||
width: 150px;
|
|
||||||
background-color: #f0f2f5;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Bild füllt den Wrapper komplett aus */
|
|
||||||
.entity-img {
|
|
||||||
object-fit: cover;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
/* Positioniert das Bild oben, falls das Format doch leicht abweicht (Köpfe/Titel sind meist oben) */
|
|
||||||
object-position: top center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-truncate-multiline {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-text {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-btn {
|
|
||||||
color: #212529;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
line-height: 1;
|
|
||||||
padding: 0 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-btn:hover {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-details {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
padding: 0.2rem 0.6rem;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user