mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Publication Status Enhancements (#1231)
* Trim when reading some fields from ComicInfo. Adjusted css on the site to reduce nbsp * Added Cancelled as a publication status * Ensure we track volume number from ComicInfo for the count to determine publication status * Publication Status will now check against volume number or chapter number (parsed or comicinfo). The UI will now display the progress, ie) 10/15 and will show the series as completed with a green tag badge if the progress is 100%. * Tweaked the ordering of the tabs to make it more streamlined in the reading ordering of Kavita * Tweaked the logic for filling in tag badge * Added a new publication status of Ended for series that have finished releasing, but not all items are in Kavita * Added some fields to edit series modal
This commit is contained in:
parent
cc8944718d
commit
419eee7835
@ -99,6 +99,5 @@ namespace API.DTOs.Filtering
|
|||||||
/// An optional name string to filter by. Empty string will ignore.
|
/// An optional name string to filter by. Empty string will ignore.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SeriesNameQuery { get; init; } = string.Empty;
|
public string SeriesNameQuery { get; init; } = string.Empty;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,11 +45,11 @@ namespace API.DTOs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Language { get; set; } = string.Empty;
|
public string Language { get; set; } = string.Empty;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Number in the TotalCount of issues
|
/// Max number of issues/volumes in the series (Max of Volume/Issue field in ComicInfo)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Count { get; set; }
|
public int MaxCount { get; set; } = 0;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total number of issues for the series
|
/// Total number of issues/volumes for the series
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int TotalCount { get; set; }
|
public int TotalCount { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
1513
API/Data/Migrations/20220425125505_ChangeCountToTotalCount.Designer.cs
generated
Normal file
1513
API/Data/Migrations/20220425125505_ChangeCountToTotalCount.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,48 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class ChangeCountToTotalCount : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_SeriesRelation_Series_SeriesId",
|
||||||
|
table: "SeriesRelation");
|
||||||
|
|
||||||
|
migrationBuilder.RenameColumn(
|
||||||
|
name: "Count",
|
||||||
|
table: "SeriesMetadata",
|
||||||
|
newName: "TotalCount");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_SeriesRelation_Series_SeriesId",
|
||||||
|
table: "SeriesRelation",
|
||||||
|
column: "SeriesId",
|
||||||
|
principalTable: "Series",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_SeriesRelation_Series_SeriesId",
|
||||||
|
table: "SeriesRelation");
|
||||||
|
|
||||||
|
migrationBuilder.RenameColumn(
|
||||||
|
name: "TotalCount",
|
||||||
|
table: "SeriesMetadata",
|
||||||
|
newName: "Count");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_SeriesRelation_Series_SeriesId",
|
||||||
|
table: "SeriesRelation",
|
||||||
|
column: "SeriesId",
|
||||||
|
principalTable: "Series",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1516
API/Data/Migrations/20220425131122_AddMaxCountToSeriesMetadata.Designer.cs
generated
Normal file
1516
API/Data/Migrations/20220425131122_AddMaxCountToSeriesMetadata.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,26 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddMaxCountToSeriesMetadata : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "MaxCount",
|
||||||
|
table: "SeriesMetadata",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "MaxCount",
|
||||||
|
table: "SeriesMetadata");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -520,9 +520,6 @@ namespace API.Data.Migrations
|
|||||||
b.Property<bool>("ColoristLocked")
|
b.Property<bool>("ColoristLocked")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("Count")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("CoverArtistLocked")
|
b.Property<bool>("CoverArtistLocked")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@ -544,6 +541,9 @@ namespace API.Data.Migrations
|
|||||||
b.Property<bool>("LettererLocked")
|
b.Property<bool>("LettererLocked")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("MaxCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<bool>("PencillerLocked")
|
b.Property<bool>("PencillerLocked")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@ -575,6 +575,9 @@ namespace API.Data.Migrations
|
|||||||
b.Property<bool>("TagsLocked")
|
b.Property<bool>("TagsLocked")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("TotalCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<bool>("TranslatorLocked")
|
b.Property<bool>("TranslatorLocked")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@ -1211,7 +1214,7 @@ namespace API.Data.Migrations
|
|||||||
b.HasOne("API.Entities.Series", "Series")
|
b.HasOne("API.Entities.Series", "Series")
|
||||||
.WithMany("Relations")
|
.WithMany("Relations")
|
||||||
.HasForeignKey("SeriesId")
|
.HasForeignKey("SeriesId")
|
||||||
.OnDelete(DeleteBehavior.Restrict)
|
.OnDelete(DeleteBehavior.ClientCascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Entities.Series", "TargetSeries")
|
b.HasOne("API.Entities.Series", "TargetSeries")
|
||||||
|
@ -18,6 +18,16 @@ public enum PublicationStatus
|
|||||||
/// Publication has finished releasing
|
/// Publication has finished releasing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Description("Completed")]
|
[Description("Completed")]
|
||||||
Completed = 2
|
Completed = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// Publication has been cancelled
|
||||||
|
/// </summary>
|
||||||
|
[Description("Cancelled")]
|
||||||
|
Cancelled = 3,
|
||||||
|
/// <summary>
|
||||||
|
/// Publication has been completed, but Kavita doesn't have all issues/volumes accounted for
|
||||||
|
/// </summary>
|
||||||
|
[Description("Ended")]
|
||||||
|
Ended = 4
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,9 +35,13 @@ namespace API.Entities.Metadata
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Language { get; set; } = string.Empty;
|
public string Language { get; set; } = string.Empty;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total number of issues in the series
|
/// Total number of issues/volumes in the series
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Count { get; set; } = 0;
|
public int TotalCount { get; set; } = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// Max number of issues/volumes in the series (Max of Volume/Issue field in ComicInfo)
|
||||||
|
/// </summary>
|
||||||
|
public int MaxCount { get; set; } = 0;
|
||||||
public PublicationStatus PublicationStatus { get; set; }
|
public PublicationStatus PublicationStatus { get; set; }
|
||||||
|
|
||||||
// Locks
|
// Locks
|
||||||
|
@ -109,7 +109,7 @@ namespace API.Services.Tasks.Scanner
|
|||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(info.ComicInfo.Series))
|
if (!string.IsNullOrEmpty(info.ComicInfo.Series))
|
||||||
{
|
{
|
||||||
info.Series = info.ComicInfo.Series;
|
info.Series = info.ComicInfo.Series.Trim();
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(info.ComicInfo.Number))
|
if (!string.IsNullOrEmpty(info.ComicInfo.Number))
|
||||||
{
|
{
|
||||||
@ -119,12 +119,12 @@ namespace API.Services.Tasks.Scanner
|
|||||||
// Patch is SeriesSort from ComicInfo
|
// Patch is SeriesSort from ComicInfo
|
||||||
if (!string.IsNullOrEmpty(info.ComicInfo.TitleSort))
|
if (!string.IsNullOrEmpty(info.ComicInfo.TitleSort))
|
||||||
{
|
{
|
||||||
info.SeriesSort = info.ComicInfo.TitleSort;
|
info.SeriesSort = info.ComicInfo.TitleSort.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(info.ComicInfo.SeriesSort))
|
if (!string.IsNullOrEmpty(info.ComicInfo.SeriesSort))
|
||||||
{
|
{
|
||||||
info.SeriesSort = info.ComicInfo.SeriesSort;
|
info.SeriesSort = info.ComicInfo.SeriesSort.Trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,14 +577,27 @@ public class ScannerService : IScannerService
|
|||||||
// Set the AgeRating as highest in all the comicInfos
|
// Set the AgeRating as highest in all the comicInfos
|
||||||
if (!series.Metadata.AgeRatingLocked) series.Metadata.AgeRating = chapters.Max(chapter => chapter.AgeRating);
|
if (!series.Metadata.AgeRatingLocked) series.Metadata.AgeRating = chapters.Max(chapter => chapter.AgeRating);
|
||||||
|
|
||||||
|
series.Metadata.TotalCount = chapters.Max(chapter => chapter.TotalCount);
|
||||||
|
series.Metadata.MaxCount = chapters.Max(chapter => chapter.Count);
|
||||||
|
// To not have to rely completely on ComicInfo, try to parse out if the series is complete by checking parsed filenames as well.
|
||||||
|
if (series.Metadata.MaxCount != series.Metadata.TotalCount)
|
||||||
|
{
|
||||||
|
var maxVolume = series.Volumes.Max(v => v.Number);
|
||||||
|
var maxChapter = chapters.Max(c => (int) float.Parse(c.Number));
|
||||||
|
if (maxVolume == series.Metadata.TotalCount) series.Metadata.MaxCount = maxVolume;
|
||||||
|
else if (maxChapter == series.Metadata.TotalCount) series.Metadata.MaxCount = maxChapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
series.Metadata.Count = chapters.Max(chapter => chapter.TotalCount);
|
|
||||||
if (!series.Metadata.PublicationStatusLocked)
|
if (!series.Metadata.PublicationStatusLocked)
|
||||||
{
|
{
|
||||||
series.Metadata.PublicationStatus = PublicationStatus.OnGoing;
|
series.Metadata.PublicationStatus = PublicationStatus.OnGoing;
|
||||||
if (chapters.Max(chapter => chapter.Count) >= series.Metadata.Count && series.Metadata.Count > 0)
|
if (series.Metadata.MaxCount >= series.Metadata.TotalCount && series.Metadata.TotalCount > 0)
|
||||||
{
|
{
|
||||||
series.Metadata.PublicationStatus = PublicationStatus.Completed;
|
series.Metadata.PublicationStatus = PublicationStatus.Completed;
|
||||||
|
} else if (series.Metadata.TotalCount > 0 && series.Metadata.MaxCount > 0)
|
||||||
|
{
|
||||||
|
series.Metadata.PublicationStatus = PublicationStatus.Ended;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -916,10 +929,16 @@ public class ScannerService : IScannerService
|
|||||||
chapter.TotalCount = comicInfo.Count;
|
chapter.TotalCount = comicInfo.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This needs to check against both Number and Volume to calculate Count
|
||||||
if (!string.IsNullOrEmpty(comicInfo.Number) && float.Parse(comicInfo.Number) > 0)
|
if (!string.IsNullOrEmpty(comicInfo.Number) && float.Parse(comicInfo.Number) > 0)
|
||||||
{
|
{
|
||||||
chapter.Count = (int) Math.Floor(float.Parse(comicInfo.Number));
|
chapter.Count = (int) Math.Floor(float.Parse(comicInfo.Number));
|
||||||
}
|
}
|
||||||
|
if (!string.IsNullOrEmpty(comicInfo.Volume) && float.Parse(comicInfo.Volume) > 0)
|
||||||
|
{
|
||||||
|
chapter.Count = Math.Max(chapter.Count, (int) Math.Floor(float.Parse(comicInfo.Volume)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
export enum PublicationStatus {
|
export enum PublicationStatus {
|
||||||
OnGoing = 0,
|
OnGoing = 0,
|
||||||
Hiatus = 1,
|
Hiatus = 1,
|
||||||
Completed = 2
|
Completed = 2,
|
||||||
|
Cancelled = 3,
|
||||||
|
Ended = 4
|
||||||
}
|
}
|
@ -8,8 +8,11 @@ import { Tag } from "./tag";
|
|||||||
export interface SeriesMetadata {
|
export interface SeriesMetadata {
|
||||||
seriesId: number;
|
seriesId: number;
|
||||||
summary: string;
|
summary: string;
|
||||||
collectionTags: Array<CollectionTag>;
|
|
||||||
|
|
||||||
|
totalCount: number;
|
||||||
|
maxCount: number;
|
||||||
|
|
||||||
|
collectionTags: Array<CollectionTag>;
|
||||||
genres: Array<Genre>;
|
genres: Array<Genre>;
|
||||||
tags: Array<Tag>;
|
tags: Array<Tag>;
|
||||||
writers: Array<Person>;
|
writers: Array<Person>;
|
||||||
|
@ -113,8 +113,8 @@
|
|||||||
<div #readingHtml class="book-content" [ngStyle]="{'padding-bottom': topOffset + 20 + 'px', 'margin': '0px 0px'}"
|
<div #readingHtml class="book-content" [ngStyle]="{'padding-bottom': topOffset + 20 + 'px', 'margin': '0px 0px'}"
|
||||||
[innerHtml]="page" *ngIf="page !== undefined"></div>
|
[innerHtml]="page" *ngIf="page !== undefined"></div>
|
||||||
|
|
||||||
<div class="left {{clickOverlayClass('left')}} no-observe" (click)="prevPage()" *ngIf="clickToPaginate" tabindex="-1"></div>
|
<div class="left {{clickOverlayClass('left')}} no-observe" [ngStyle]="{'padding-top': topOffset + 'px'}" (click)="prevPage()" *ngIf="clickToPaginate" tabindex="-1"></div>
|
||||||
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe" (click)="nextPage()" *ngIf="clickToPaginate" tabindex="-1"></div>
|
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe" [ngStyle]="{'padding-top': topOffset + 'px'}" (click)="nextPage()" *ngIf="clickToPaginate" tabindex="-1"></div>
|
||||||
|
|
||||||
<div *ngIf="page !== undefined && scrollbarNeeded" (click)="$event.stopPropagation();">
|
<div *ngIf="page !== undefined && scrollbarNeeded" (click)="$event.stopPropagation();">
|
||||||
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
<form [formGroup]="editSeriesForm">
|
<form [formGroup]="editSeriesForm">
|
||||||
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-pills" orientation="{{utilityService.getActiveBreakpoint() === Breakpoint.Mobile ? 'horizontal' : 'vertical'}}" style="min-width: 135px;">
|
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-pills" orientation="{{utilityService.getActiveBreakpoint() === Breakpoint.Mobile ? 'horizontal' : 'vertical'}}" style="min-width: 135px;">
|
||||||
|
|
||||||
<li [ngbNavItem]="tabs[0]">
|
<li [ngbNavItem]="tabs[TabID.General]">
|
||||||
<a ngbNavLink>{{tabs[0]}}</a>
|
<a ngbNavLink>{{tabs[TabID.General]}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="mb-3" style="width: 100%">
|
<div class="mb-3" style="width: 100%">
|
||||||
@ -55,8 +55,8 @@
|
|||||||
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
<li [ngbNavItem]="tabs[1]" *ngIf="metadata">
|
<li [ngbNavItem]="tabs[TabID.Metadata]" *ngIf="metadata">
|
||||||
<a ngbNavLink>{{tabs[1]}}</a>
|
<a ngbNavLink>{{tabs[TabID.Metadata]}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
|
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
@ -153,8 +153,8 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li [ngbNavItem]="tabs[2]">
|
<li [ngbNavItem]="tabs[TabID.People]">
|
||||||
<a ngbNavLink>{{tabs[2]}}</a>
|
<a ngbNavLink>{{tabs[TabID.People]}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@ -320,8 +320,8 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li [ngbNavItem]="tabs[3]">
|
<li [ngbNavItem]="tabs[TabID.CoverImage]">
|
||||||
<a ngbNavLink>{{tabs[3]}}</a>
|
<a ngbNavLink>{{tabs[TabID.CoverImage]}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<p class="alert alert-primary" role="alert">
|
<p class="alert alert-primary" role="alert">
|
||||||
Upload and choose a new cover image. Press Save to upload and override the cover.
|
Upload and choose a new cover image. Press Save to upload and override the cover.
|
||||||
@ -329,14 +329,14 @@
|
|||||||
<app-cover-image-chooser [(imageUrls)]="imageUrls" (imageSelected)="updateSelectedIndex($event)" (selectedBase64Url)="updateSelectedImage($event)" [showReset]="series.coverImageLocked" (resetClicked)="handleReset()"></app-cover-image-chooser>
|
<app-cover-image-chooser [(imageUrls)]="imageUrls" (imageSelected)="updateSelectedIndex($event)" (selectedBase64Url)="updateSelectedImage($event)" [showReset]="series.coverImageLocked" (resetClicked)="handleReset()"></app-cover-image-chooser>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
<li [ngbNavItem]="tabs[4]">
|
<li [ngbNavItem]="tabs[TabID.Related]">
|
||||||
<a ngbNavLink>{{tabs[4]}}</a>
|
<a ngbNavLink>{{tabs[TabID.Related]}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<app-edit-series-relation [series]="series" [save]="saveNestedComponents"></app-edit-series-relation>
|
<app-edit-series-relation [series]="series" [save]="saveNestedComponents"></app-edit-series-relation>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
<li [ngbNavItem]="tabs[5]">
|
<li [ngbNavItem]="tabs[TabID.Info]">
|
||||||
<a ngbNavLink>{{tabs[5]}}</a>
|
<a ngbNavLink>{{tabs[TabID.Info]}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<h4>Information</h4>
|
<h4>Information</h4>
|
||||||
<div class="row g-0 mb-2">
|
<div class="row g-0 mb-2">
|
||||||
@ -348,6 +348,11 @@
|
|||||||
<div class="col-md-6">Last Read: {{series.latestReadDate | date:'shortDate'}}</div>
|
<div class="col-md-6">Last Read: {{series.latestReadDate | date:'shortDate'}}</div>
|
||||||
<div class="col-md-6">Last Added To: {{series.lastChapterAdded | date:'shortDate'}}</div>
|
<div class="col-md-6">Last Added To: {{series.lastChapterAdded | date:'shortDate'}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row g-0 mb-2" *ngIf="metadata">
|
||||||
|
<div class="col-md-6">Max Items: {{metadata.maxCount}}</div>
|
||||||
|
<div class="col-md-6">Total Items: {{metadata.totalCount}}</div>
|
||||||
|
<div class="col-md-6">Publication Status: {{metadata.publicationStatus | publicationStatus}}</div>
|
||||||
|
</div>
|
||||||
<h4>Volumes</h4>
|
<h4>Volumes</h4>
|
||||||
<div class="spinner-border text-secondary" role="status" *ngIf="isLoadingVolumes">
|
<div class="spinner-border text-secondary" role="status" *ngIf="isLoadingVolumes">
|
||||||
<span class="invisible">Loading...</span>
|
<span class="invisible">Loading...</span>
|
||||||
|
@ -22,6 +22,15 @@ import { MetadataService } from 'src/app/_services/metadata.service';
|
|||||||
import { SeriesService } from 'src/app/_services/series.service';
|
import { SeriesService } from 'src/app/_services/series.service';
|
||||||
import { UploadService } from 'src/app/_services/upload.service';
|
import { UploadService } from 'src/app/_services/upload.service';
|
||||||
|
|
||||||
|
enum TabID {
|
||||||
|
General = 0,
|
||||||
|
Metadata = 1,
|
||||||
|
People = 2,
|
||||||
|
CoverImage = 3,
|
||||||
|
Related = 4,
|
||||||
|
Info = 5,
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-edit-series-modal',
|
selector: 'app-edit-series-modal',
|
||||||
templateUrl: './edit-series-modal.component.html',
|
templateUrl: './edit-series-modal.component.html',
|
||||||
@ -41,6 +50,7 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
|
|||||||
volumeCollapsed: any = {};
|
volumeCollapsed: any = {};
|
||||||
tabs = ['General', 'Metadata', 'People', 'Cover Image', 'Related', 'Info'];
|
tabs = ['General', 'Metadata', 'People', 'Cover Image', 'Related', 'Info'];
|
||||||
active = this.tabs[0];
|
active = this.tabs[0];
|
||||||
|
activeTabId = TabID.General;
|
||||||
editSeriesForm!: FormGroup;
|
editSeriesForm!: FormGroup;
|
||||||
libraryName: string | undefined = undefined;
|
libraryName: string | undefined = undefined;
|
||||||
private readonly onDestroy = new Subject<void>();
|
private readonly onDestroy = new Subject<void>();
|
||||||
@ -83,6 +93,10 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
|
|||||||
return PersonRole;
|
return PersonRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get TabID(): typeof TabID {
|
||||||
|
return TabID;
|
||||||
|
}
|
||||||
|
|
||||||
getPersonsSettings(role: PersonRole) {
|
getPersonsSettings(role: PersonRole) {
|
||||||
return this.peopleSettings[role];
|
return this.peopleSettings[role];
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ export class PublicationStatusPipe implements PipeTransform {
|
|||||||
case PublicationStatus.OnGoing: return 'Ongoing';
|
case PublicationStatus.OnGoing: return 'Ongoing';
|
||||||
case PublicationStatus.Hiatus: return 'Hiatus';
|
case PublicationStatus.Hiatus: return 'Hiatus';
|
||||||
case PublicationStatus.Completed: return 'Completed';
|
case PublicationStatus.Completed: return 'Completed';
|
||||||
|
case PublicationStatus.Cancelled: return 'Cancelled';
|
||||||
|
case PublicationStatus.Ended: return 'Ended';
|
||||||
|
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<ng-container title>
|
<ng-container title>
|
||||||
<h2 style="margin-bottom: 0px">
|
<h2 style="margin-bottom: 0px">
|
||||||
<app-card-actionables [disabled]="actionInProgress" (actionHandler)="performAction($event)" [actions]="seriesActions" [labelBy]="series.name" iconClass="fa-ellipsis-v"></app-card-actionables>
|
<app-card-actionables [disabled]="actionInProgress" (actionHandler)="performAction($event)" [actions]="seriesActions" [labelBy]="series.name" iconClass="fa-ellipsis-v"></app-card-actionables>
|
||||||
{{series?.name}}
|
<span>{{series?.name}}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container subtitle *ngIf="series?.localizedName !== series?.name">
|
<ng-container subtitle *ngIf="series?.localizedName !== series?.name">
|
||||||
@ -20,9 +20,9 @@
|
|||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button class="btn btn-primary" (click)="read()">
|
<button class="btn btn-primary" (click)="read()">
|
||||||
<span>
|
<span>
|
||||||
<i class="fa {{showBook ? 'fa-book-open' : 'fa-book'}}"></i>
|
<i class="fa {{showBook ? 'fa-book-open' : 'fa-book'}} me-1"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="d-none d-sm-inline-block"> {{(hasReadingProgress) ? 'Continue' : 'Read'}}</span>
|
<span class="d-none d-sm-inline-block">{{(hasReadingProgress) ? 'Continue' : 'Read'}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto ms-2" *ngIf="isAdmin">
|
<div class="col-auto ms-2" *ngIf="isAdmin">
|
||||||
@ -69,34 +69,6 @@
|
|||||||
<ng-container>
|
<ng-container>
|
||||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav nav-tabs mb-2" [destroyOnHide]="false" (navChange)="onNavChange($event)">
|
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav nav-tabs mb-2" [destroyOnHide]="false" (navChange)="onNavChange($event)">
|
||||||
<li [ngbNavItem]="TabID.Related" *ngIf="hasRelations">
|
|
||||||
<a ngbNavLink>Related</a>
|
|
||||||
<ng-template ngbNavContent>
|
|
||||||
<div class="row g-0">
|
|
||||||
<ng-container *ngFor="let item of relations; let idx = index; trackBy: trackByRelatedSeriesIdentiy">
|
|
||||||
<app-series-card class="col-auto p-2" [data]="item.series" [libraryId]="item.series.libraryId" [relation]="item.relation"></app-series-card>
|
|
||||||
<!--(reload)="reloadInProgress($event)" (dataChanged)="reloadInProgress($event)"-->
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
</li>
|
|
||||||
<li [ngbNavItem]="TabID.Specials" *ngIf="hasSpecials">
|
|
||||||
<a ngbNavLink>Specials</a>
|
|
||||||
<ng-template ngbNavContent>
|
|
||||||
|
|
||||||
<app-card-detail-layout
|
|
||||||
[isLoading]="isLoading"
|
|
||||||
[items]="specials">
|
|
||||||
<ng-template #cardItem let-item let-position="idx">
|
|
||||||
<app-card-item class="col-auto p-2" [entity]="item" [title]="item.title || item.range" (click)="openChapter(item)"
|
|
||||||
[imageUrl]="imageService.getChapterCoverImage(item.id)"
|
|
||||||
[read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions"
|
|
||||||
(selection)="bulkSelectionService.handleCardSelection('special', position, chapters.length, $event)"
|
|
||||||
[selected]="bulkSelectionService.isCardSelected('special', position)" [allowSelection]="true"></app-card-item>
|
|
||||||
</ng-template>
|
|
||||||
</app-card-detail-layout>
|
|
||||||
</ng-template>
|
|
||||||
</li>
|
|
||||||
<li [ngbNavItem]="TabID.Storyline" *ngIf="libraryType !== LibraryType.Book && (volumes.length > 0 || chapters.length > 0)">
|
<li [ngbNavItem]="TabID.Storyline" *ngIf="libraryType !== LibraryType.Book && (volumes.length > 0 || chapters.length > 0)">
|
||||||
<a ngbNavLink>Storyline</a>
|
<a ngbNavLink>Storyline</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
@ -148,6 +120,32 @@
|
|||||||
</app-card-detail-layout>
|
</app-card-detail-layout>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
<li [ngbNavItem]="TabID.Specials" *ngIf="hasSpecials">
|
||||||
|
<a ngbNavLink>Specials</a>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
|
<app-card-detail-layout
|
||||||
|
[isLoading]="isLoading"
|
||||||
|
[items]="specials">
|
||||||
|
<ng-template #cardItem let-item let-position="idx">
|
||||||
|
<app-card-item class="col-auto p-2" [entity]="item" [title]="item.title || item.range" (click)="openChapter(item)"
|
||||||
|
[imageUrl]="imageService.getChapterCoverImage(item.id)"
|
||||||
|
[read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions"
|
||||||
|
(selection)="bulkSelectionService.handleCardSelection('special', position, chapters.length, $event)"
|
||||||
|
[selected]="bulkSelectionService.isCardSelected('special', position)" [allowSelection]="true"></app-card-item>
|
||||||
|
</ng-template>
|
||||||
|
</app-card-detail-layout>
|
||||||
|
</ng-template>
|
||||||
|
</li>
|
||||||
|
<li [ngbNavItem]="TabID.Related" *ngIf="hasRelations">
|
||||||
|
<a ngbNavLink>Related</a>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
|
<div class="row g-0">
|
||||||
|
<ng-container *ngFor="let item of relations; let idx = index; trackBy: trackByRelatedSeriesIdentiy">
|
||||||
|
<app-series-card class="col-auto p-2" [data]="item.series" [libraryId]="item.series.libraryId" [relation]="item.relation"></app-series-card>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div [ngbNavOutlet]="nav"></div>
|
<div [ngbNavOutlet]="nav"></div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -8,7 +8,11 @@
|
|||||||
<ng-container *ngIf="series">
|
<ng-container *ngIf="series">
|
||||||
<app-tag-badge *ngIf="seriesMetadata.releaseYear > 0" title="Release date" class="col-auto">{{seriesMetadata.releaseYear}}</app-tag-badge>
|
<app-tag-badge *ngIf="seriesMetadata.releaseYear > 0" title="Release date" class="col-auto">{{seriesMetadata.releaseYear}}</app-tag-badge>
|
||||||
<app-tag-badge *ngIf="seriesMetadata.language !== null && seriesMetadata.language !== ''" title="Language" a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Languages, seriesMetadata.language)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.language}}</app-tag-badge>
|
<app-tag-badge *ngIf="seriesMetadata.language !== null && seriesMetadata.language !== ''" title="Language" a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Languages, seriesMetadata.language)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.language}}</app-tag-badge>
|
||||||
<app-tag-badge title="Publication Status" a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.PublicationStatus, seriesMetadata.publicationStatus)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.publicationStatus | publicationStatus}}</app-tag-badge>
|
|
||||||
|
<app-tag-badge title="Publication Status ({{seriesMetadata.maxCount}} / {{seriesMetadata.totalCount}})" [fillStyle]="seriesMetadata.maxCount != 0 && seriesMetadata.maxCount >= seriesMetadata.totalCount ? 'filled' : 'outline'" a11y-click="13,32" class="col-auto"
|
||||||
|
(click)="goTo(FilterQueryParam.PublicationStatus, seriesMetadata.publicationStatus)"
|
||||||
|
[selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.publicationStatus | publicationStatus}}</app-tag-badge>
|
||||||
|
|
||||||
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Format, series.format)" [selectionMode]="TagBadgeCursor.Clickable">
|
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Format, series.format)" [selectionMode]="TagBadgeCursor.Clickable">
|
||||||
<app-series-format [format]="series.format">{{utilityService.mangaFormat(series.format)}}</app-series-format>
|
<app-series-format [format]="series.format">{{utilityService.mangaFormat(series.format)}}</app-series-format>
|
||||||
</app-tag-badge>
|
</app-tag-badge>
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<div class="tagbadge {{cursor}}">
|
<div class="tagbadge {{cursor}} {{fillStyle}}">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
@ -39,3 +39,15 @@
|
|||||||
cursor: pointer !important;
|
cursor: pointer !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filled {
|
||||||
|
border: 1px solid var(--tagbadge-filled-border-color);
|
||||||
|
color: var(--tagbadge-filled-text-color);
|
||||||
|
background-color: var(--tagbadge-filled-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline {
|
||||||
|
border: 1px solid var(--tagbadge-border-color);
|
||||||
|
color: var(--tagbadge-text-color);
|
||||||
|
background-color: var(--tagbadge-bg-color);
|
||||||
|
}
|
@ -29,6 +29,7 @@ export enum TagBadgeCursor {
|
|||||||
export class TagBadgeComponent implements OnInit {
|
export class TagBadgeComponent implements OnInit {
|
||||||
|
|
||||||
@Input() selectionMode: TagBadgeCursor = TagBadgeCursor.Selectable;
|
@Input() selectionMode: TagBadgeCursor = TagBadgeCursor.Selectable;
|
||||||
|
@Input() fillStyle: 'filled' | 'outline' = 'outline';
|
||||||
|
|
||||||
cursor: string = 'default';
|
cursor: string = 'default';
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<div class="active-highlight"></div>
|
<div class="active-highlight"></div>
|
||||||
<span class="phone-hidden" title="{{title}}">
|
<span class="phone-hidden" title="{{title}}">
|
||||||
<div>
|
<div>
|
||||||
<i class="fa {{icon}}" aria-hidden="true"></i>
|
<i class="fa {{icon}}" aria-hidden="true"></i>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<span class="side-nav-text">
|
<span class="side-nav-text">
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div class="typeahead-input" [ngClass]="{'disabled': disabled}" (click)="onInputFocus($event)">
|
<div class="typeahead-input" [ngClass]="{'disabled': disabled}" (click)="onInputFocus($event)">
|
||||||
<app-tag-badge *ngFor="let option of optionSelection.selected(); let i = index">
|
<app-tag-badge *ngFor="let option of optionSelection.selected(); let i = index" fillStyle="filled">
|
||||||
<ng-container [ngTemplateOutlet]="badgeTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: i }"></ng-container>
|
<ng-container [ngTemplateOutlet]="badgeTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: i }"></ng-container>
|
||||||
<i class="fa fa-times" *ngIf="!disabled" (click)="toggleSelection(option)" tabindex="0" aria-label="close"></i>
|
<i class="fa fa-times" *ngIf="!disabled" (click)="toggleSelection(option)" tabindex="0" aria-label="close"></i>
|
||||||
</app-tag-badge>
|
</app-tag-badge>
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
@import './theme/components/buttons';
|
@import './theme/components/buttons';
|
||||||
@import './theme/components/toast';
|
@import './theme/components/toast';
|
||||||
@import './theme/components/checkbox';
|
@import './theme/components/checkbox';
|
||||||
@import './theme/components/tagbadge';
|
|
||||||
@import './theme/components/list';
|
@import './theme/components/list';
|
||||||
@import './theme/components/navbar';
|
@import './theme/components/navbar';
|
||||||
@import './theme/components/popover';
|
@import './theme/components/popover';
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
.tagbadge {
|
|
||||||
border: 1px solid var(--tagbadge-border-color);
|
|
||||||
color: var(--tagbadge-text-color);
|
|
||||||
background-color: var(--tagbadge-bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeahead-input .tagbadge {
|
|
||||||
border: 1px solid var(--tagbadge-typeahead-border-color);
|
|
||||||
color: var(--tagbadge-typeahead-text-color);
|
|
||||||
background-color: var(--tagbadge-typeahead-bg-color);
|
|
||||||
}
|
|
@ -84,9 +84,9 @@
|
|||||||
--tagbadge-border-color: rgba(239, 239, 239, 0.125);
|
--tagbadge-border-color: rgba(239, 239, 239, 0.125);
|
||||||
--tagbadge-text-color: var(--body-text-color);
|
--tagbadge-text-color: var(--body-text-color);
|
||||||
--tagbadge-bg-color: var(--nav-tab-hover-bg-color);
|
--tagbadge-bg-color: var(--nav-tab-hover-bg-color);
|
||||||
--tagbadge-typeahead-border-color: rgba(239, 239, 239, 0.125);
|
--tagbadge-filled-border-color: rgba(239, 239, 239, 0.125);
|
||||||
--tagbadge-typeahead-text-color: var(--body-text-color);
|
--tagbadge-filled-text-color: var(--body-text-color);
|
||||||
--tagbadge-typeahead-bg-color: var(--primary-color);
|
--tagbadge-filled-bg-color: var(--primary-color);
|
||||||
|
|
||||||
/* Side Nav */
|
/* Side Nav */
|
||||||
--side-nav-bg-color: rgba(0,0,0,0.2);
|
--side-nav-bg-color: rgba(0,0,0,0.2);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user