17 Commits

Author SHA1 Message Date
594541a103 Merge pull request 'penry-patch-1' (#47) from penry-patch-1 into main
All checks were successful
Build Docker Linux ARM64 / build-docker-linux-arm64 (push) Successful in 45s
Reviewed-on: #47
2026-02-21 22:39:18 +01:00
ee7bcc1eae README.md aktualisiert 2026-02-21 22:38:01 +01:00
4fe354add0 README.md aktualisiert 2026-02-21 22:36:12 +01:00
c6355d47df Merge pull request 'feat: added Remove Button for removing Privte Entity from PrivateList' (#46) from AddRemovePrivateEntityFromList into main
All checks were successful
Build Docker Linux ARM64 / build-docker-linux-arm64 (push) Successful in 1m35s
Reviewed-on: #46
2026-02-15 23:39:01 +01:00
97a92269ad feat: added Remove Button for removing Privte Entity from PrivateList 2026-02-15 23:38:02 +01:00
66468a1f8b Merge pull request 'feat: changed the GlobalEntity Sort Attribute to CreationTime' (#45) from ChangeGlobalEntitySortAttribute into main
All checks were successful
Build Docker Linux ARM64 / build-docker-linux-arm64 (push) Successful in 45s
Reviewed-on: #45
2026-02-13 19:49:30 +01:00
e48e8b6394 feat: changed the GlobalEntity Sort Attribute to CreationTime 2026-02-13 19:42:25 +01:00
5f39417109 Merge pull request 'feat: added Currently Watching to PrivateList' (#40) from AddCurrentlyWatchingToPrivateList into main
All checks were successful
Build Docker Linux ARM64 / build-docker-linux-arm64 (push) Successful in 4m34s
Reviewed-on: #40
2026-01-04 20:30:57 +01:00
76e8e8daa6 feat: added Currently Watching to PrivateList 2026-01-04 20:27:17 +01:00
6db2bca50b Merge pull request 'fix: edited UserWatchStatus' (#39) from EditedUserWatchStatusName into main
All checks were successful
Build Docker Linux ARM64 / build-docker-linux-arm64 (push) Successful in 9m11s
Reviewed-on: #39
2026-01-04 20:00:45 +01:00
46491dd987 fix: edited UserWatchStatus 2026-01-04 19:56:11 +01:00
8edf749e23 Merge pull request 'feat: added that Episode gets set to 1 when changing the Season' (#38) from AddedEpisodeChangeOnSeasonChange into main
All checks were successful
Build Docker Linux ARM64 / build-docker-linux-arm64 (push) Successful in 9m13s
Reviewed-on: #38
2026-01-04 19:26:59 +01:00
3cc4fd8240 feat: added that Episode gets set to 1 when changing the Season 2026-01-04 19:16:32 +01:00
d0f91fd0e1 Merge pull request 'feat: Rework first account to be register is admin and no Standard users get created' (#37) from ReworkedRegisterAndStandardAdminUser into main
All checks were successful
Build Docker Linux ARM64 / build-docker-linux-arm64 (push) Successful in 9m15s
Reviewed-on: #37
2026-01-04 18:00:21 +01:00
f1a68296ec Merge branch 'main' into ReworkedRegisterAndStandardAdminUser 2026-01-04 18:00:05 +01:00
67b559e6d7 Merge pull request '.gitea/workflows/linux_arm64_docker.yaml aktualisiert' (#34) from WrongDockerImageNameForWorkflow into main
All checks were successful
Build Docker Linux ARM64 / build-docker-linux-arm64 (push) Successful in 3m59s
Reviewed-on: #34
2026-01-03 20:13:00 +01:00
3c10721c4e .gitea/workflows/linux_arm64_docker.yaml aktualisiert 2026-01-03 20:12:48 +01:00
5 changed files with 182 additions and 145 deletions

View File

@@ -30,7 +30,7 @@ jobs:
. .
- name: Save Docker Image to Tar - name: Save Docker Image to Tar
run: docker save -o CouchLog-Linux-ARM64-Docker-Image.tar couchlog-linux-arm64 run: docker save -o CouchLog-Linux-ARM64-Docker-Image.tar gitea.penry.de/${{ env.REPO_LC }}:latest
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

@@ -159,7 +159,7 @@
/// <exception cref="NotImplementedException"></exception> /// <exception cref="NotImplementedException"></exception>
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
GlobalEntities = await CouchLogDB.GlobalEntities.OrderByDescending(Entity => Entity.Title).ToListAsync(); GlobalEntities = await CouchLogDB.GlobalEntities.OrderByDescending(Entity => Entity.CreationTime).ToListAsync();
MediaTypes = await CouchLogDB.MediaType.OrderBy(Type => Type.Id).ToListAsync(); MediaTypes = await CouchLogDB.MediaType.OrderBy(Type => Type.Id).ToListAsync();
Genres = await CouchLogDB.Genres.OrderBy(Genre => Genre.Name).ToListAsync(); Genres = await CouchLogDB.Genres.OrderBy(Genre => Genre.Name).ToListAsync();

View File

@@ -16,140 +16,42 @@
<PageTitle>Private List</PageTitle> <PageTitle>Private List</PageTitle>
<div class="container-fluid mt-4 px-4"> <div class="container-fluid mt-4 px-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Private List</h2>
</div>
<!-- Grid-Layout --> <!-- Active Watching Section -->
@if (ActiveWatchingEntities.Any())
{
<div class="mb-5">
<h2 class="mb-4">Aktiv am Schauen</h2>
<div class="row g-4"> <div class="row g-4">
@foreach (var Entity in PrivateEntities) @foreach (var entity in ActiveWatchingEntities)
{ {
<div class="col-12 col-lg-6 col-xxl-4"> @RenderEntityCard(entity)
}
</div>
</div>
}
<div class="card shadow-sm border-0 overflow-hidden private-entity-card"> <!-- Library / Completed Section -->
<div class="row g-0 h-100"> @if (LibraryEntities.Any())
<!-- Feste Breite für das Bild, kein Umbruch -->
<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" <div class="mb-5">
class="entity-img w-100 h-100" <h2 class="mb-4">Bibliothek</h2>
style="object-fit: cover;" <div class="row g-4">
alt="@Entity.GlobalEntity.Title"> @foreach (var entity in LibraryEntities)
}
else
{ {
<div class="d-flex align-items-center justify-content-center h-100 bg-light text-muted small"> @RenderEntityCard(entity)
Kein Bild
</div>
} }
</div> </div>
<!-- Der Rest nimmt den verbleibenden Platz -->
<div class="col" style="min-width: 0;">
<div class="card-body d-flex flex-column h-100 py-2 px-3">
<!-- Header mit besserer Text-Behandlung -->
<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>
}
<div class="dropdown ms-2" style="flex-shrink: 0;"> @if (!PrivateEntities.Any())
<button class="btn btn-link menu-btn" type="button" data-bs-toggle="modal" data-bs-target="#modal-@Entity.Id">
&#8942;
</button>
</div>
</div>
<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> <div class="text-center text-muted py-5">
} <p>Keine Einträge vorhanden.</p>
</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>
}
<!-- Beschreibung -->
<!--<p class="card-text text-truncate-multiline flex-grow-1 text-muted small mt-1 mb-2">
@(!string.IsNullOrWhiteSpace(Entity.Description)
? Entity.Description
: (Entity.Description ?? "Keine Beschreibung."))
</p>
-->
<!-- 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 (unverändert) -->
<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">Optionen...</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
</div> </div>
} }
</div> </div>
</div>
@code @code
{ {
@@ -157,6 +59,9 @@
private List<UserWatchStatus> userWatchStatuses = new List<UserWatchStatus>(); private List<UserWatchStatus> userWatchStatuses = new List<UserWatchStatus>();
ApplicationUser? AppUser = new(); ApplicationUser? AppUser = new();
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();
@@ -176,19 +81,132 @@
} }
} }
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">
&#8942;
</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) private async Task UpdateWatchStatus(PrivateEntity entity, object? newValue)
{ {
// Wir holen uns die gewählte ID aus dem Dropdown
if (int.TryParse(newValue?.ToString(), out int newId)) if (int.TryParse(newValue?.ToString(), out int newId))
{ {
// 1. ID im Objekt aktualisieren
entity.UserWatchStatusId = newId; entity.UserWatchStatusId = newId;
// 2. Navigation Property aktualisieren (damit die UI nicht flackert)
// Wir suchen das passende Objekt aus der geladenen Liste
entity.UserWatchStatus = userWatchStatuses.FirstOrDefault(s => s.Id == newId); entity.UserWatchStatus = userWatchStatuses.FirstOrDefault(s => s.Id == newId);
// 3. Ab in die Datenbank
await CouchLogDB.SaveChangesAsync(); await CouchLogDB.SaveChangesAsync();
} }
} }
@@ -198,7 +216,7 @@
if (int.TryParse(newSeasonValue?.ToString(), out int newSeason)) if (int.TryParse(newSeasonValue?.ToString(), out int newSeason))
{ {
entity.Season = newSeason; entity.Season = newSeason;
entity.Episode = 1;
await CouchLogDB.SaveChangesAsync(); await CouchLogDB.SaveChangesAsync();
} }
} }
@@ -208,7 +226,6 @@
if (int.TryParse(newEpisodeValue?.ToString(), out int newEpisode)) if (int.TryParse(newEpisodeValue?.ToString(), out int newEpisode))
{ {
entity.Episode = newEpisode; entity.Episode = newEpisode;
await CouchLogDB.SaveChangesAsync(); await CouchLogDB.SaveChangesAsync();
} }
} }

View File

@@ -138,7 +138,7 @@ namespace CouchLog
List<UserWatchStatus> UserWatchStatuses = List<UserWatchStatus> UserWatchStatuses =
[ [
new() { Name = "Not watched", CreationTime = DateTime.Now }, new() { Name = "Not watched", CreationTime = DateTime.Now },
new() { Name = "Started", CreationTime = DateTime.Now }, new() { Name = "Watching", CreationTime = DateTime.Now },
new() { Name = "Finished", CreationTime = DateTime.Now }, new() { Name = "Finished", CreationTime = DateTime.Now },
new() { Name = "Paused", CreationTime = DateTime.Now }, new() { Name = "Paused", CreationTime = DateTime.Now },
new() { Name = "Aborted", CreationTime= DateTime.Now }, new() { Name = "Aborted", CreationTime= DateTime.Now },
@@ -152,6 +152,24 @@ namespace CouchLog
} }
} }
//#################################################
//###### Old-Things that need to be deleted #######
//#################################################
List<UserWatchStatus> oldUserWatchStatuses =
[
new() { Name = "Started" , CreationTime = DateTime.Now },
];
foreach(UserWatchStatus oldUserWatchStatus in oldUserWatchStatuses)
{
UserWatchStatus? toDeletedUserWatchStatus = await CouchLogDB.UserWatchStatuses.FirstOrDefaultAsync(m => m.Name == oldUserWatchStatus.Name);
if(toDeletedUserWatchStatus != null)
{
CouchLogDB.UserWatchStatuses.Remove(toDeletedUserWatchStatus);
}
}
await CouchLogDB.SaveChangesAsync(); await CouchLogDB.SaveChangesAsync();
} }
} }

View File

@@ -1,2 +1,4 @@
# Watchlog # Watchlog
## Work in Progress ## Work in Progress
### Information
- This Repo has a Mirror on Codeberg, but the Releases a only available on [Gitea](https://gitea.penry.de/Penry/CouchLog) for now