diff --git a/API.Tests/Services/ScannerServiceTests.cs b/API.Tests/Services/ScannerServiceTests.cs index 14626e2dc..26ba00497 100644 --- a/API.Tests/Services/ScannerServiceTests.cs +++ b/API.Tests/Services/ScannerServiceTests.cs @@ -259,7 +259,7 @@ public class ScannerServiceTests : AbstractDbTest comicInfo.AppendLine(""); // People Tags - string[] people = { /* Your list of people here */ }; + string[] people = { "Joe Shmo", "Tommy Two Hands"}; string[] genres = { /* Your list of genres here */ }; void AddRandomTag(string tagName, string[] choices) diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index e7429a9b2..8ed9fdd1c 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -164,8 +164,6 @@ public class SettingsController : BaseApiController foreach (var setting in currentSettings) { - UpdateSchedulingSettings(setting, updateSettingsDto); - if (setting.Key == ServerSettingKey.OnDeckProgressDays && updateSettingsDto.OnDeckProgressDays + string.Empty != setting.Value) { @@ -205,6 +203,8 @@ public class SettingsController : BaseApiController _unitOfWork.SettingsRepository.Update(setting); } + UpdateSchedulingSettings(setting, updateSettingsDto); + UpdateEmailSettings(setting, updateSettingsDto); @@ -293,14 +293,6 @@ public class SettingsController : BaseApiController { setting.Value = updateSettingsDto.AllowStatCollection + string.Empty; _unitOfWork.SettingsRepository.Update(setting); - if (!updateSettingsDto.AllowStatCollection) - { - _taskScheduler.CancelStatsTasks(); - } - else - { - await _taskScheduler.ScheduleStatsTasks(); - } } if (setting.Key == ServerSettingKey.TotalBackups && @@ -341,20 +333,27 @@ public class SettingsController : BaseApiController { await _unitOfWork.CommitAsync(); + if (!updateSettingsDto.AllowStatCollection) + { + _taskScheduler.CancelStatsTasks(); + } + else + { + await _taskScheduler.ScheduleStatsTasks(); + } + if (updateBookmarks) { - _directoryService.ExistOrCreate(bookmarkDirectory); - _directoryService.CopyDirectoryToDirectory(originalBookmarkDirectory, bookmarkDirectory); - _directoryService.ClearAndDeleteDirectory(originalBookmarkDirectory); + BackgroundJob.Enqueue(() => UpdateBookmarkDirectory(originalBookmarkDirectory, bookmarkDirectory)); } if (updateSettingsDto.EnableFolderWatching) { - await _libraryWatcher.StartWatching(); + BackgroundJob.Enqueue(() => _libraryWatcher.StartWatching()); } else { - _libraryWatcher.StopWatching(); + BackgroundJob.Enqueue(() => _libraryWatcher.StopWatching()); } } catch (Exception ex) @@ -366,10 +365,18 @@ public class SettingsController : BaseApiController _logger.LogInformation("Server Settings updated"); - await _taskScheduler.ScheduleTasks(); + BackgroundJob.Enqueue(() => _taskScheduler.ScheduleTasks()); + return Ok(updateSettingsDto); } + public void UpdateBookmarkDirectory(string originalBookmarkDirectory, string bookmarkDirectory) + { + _directoryService.ExistOrCreate(bookmarkDirectory); + _directoryService.CopyDirectoryToDirectory(originalBookmarkDirectory, bookmarkDirectory); + _directoryService.ClearAndDeleteDirectory(originalBookmarkDirectory); + } + private void UpdateSchedulingSettings(ServerSetting setting, ServerSettingDto updateSettingsDto) { if (setting.Key == ServerSettingKey.TaskBackup && updateSettingsDto.TaskBackup != setting.Value) diff --git a/API/Services/SeriesService.cs b/API/Services/SeriesService.cs index a6b3cb347..e040d212b 100644 --- a/API/Services/SeriesService.cs +++ b/API/Services/SeriesService.cs @@ -175,6 +175,10 @@ public class SeriesService : ISeriesService series.Metadata.Genres.Add(genre); }, () => series.Metadata.GenresLocked = true); } + else + { + series.Metadata.Genres = new List(); + } if (updateSeriesMetadataDto.SeriesMetadata?.Tags is {Count: > 0}) @@ -188,6 +192,10 @@ public class SeriesService : ISeriesService series.Metadata.Tags.Add(tag); }, () => series.Metadata.TagsLocked = true); } + else + { + series.Metadata.Tags = new List(); + } if (updateSeriesMetadataDto.SeriesMetadata != null) { diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5ccd27541..9085e9444 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ We're always looking for people to help make Kavita even better, there are a number of ways to contribute. ## Documentation ## -Setup guides, FAQ, the more information we have on the [wiki](https://wiki.kavitareader.com/) the better. +Setup guides, FAQ, the more information we have on the [wiki](https://wiki.kavitareader.com/contributing) the better. ## Development ## diff --git a/UI/Web/src/_series-detail-common.scss b/UI/Web/src/_series-detail-common.scss index 88b97ffd1..5da0a9434 100644 --- a/UI/Web/src/_series-detail-common.scss +++ b/UI/Web/src/_series-detail-common.scss @@ -1,3 +1,5 @@ +@import './theme/variables'; + .title { color: white; font-weight: bold; @@ -142,7 +144,7 @@ object-fit: contain; } -@media (max-width: 768px) { +@media (max-width: $grid-breakpoints-lg) { .carousel-tabs-container { mask-image: linear-gradient(transparent, black 0%, black 90%, transparent 100%); -webkit-mask-image: linear-gradient(to right, transparent, black 0%, black 90%, transparent 100%); @@ -155,7 +157,7 @@ } /* col-lg */ -@media screen and (max-width: 991px) { +@media (max-width: $grid-breakpoints-lg) { .image-container.mobile-bg{ width: 100vw; top: calc(var(--nav-offset) - 20px); @@ -194,17 +196,9 @@ font-size: 0.9rem; } -@media (max-width: 768px) { +@media (max-width: $grid-breakpoints-lg) { .carousel-tabs-container { mask-image: linear-gradient(to right, transparent, black 0%, black 90%, transparent 100%); -webkit-mask-image: linear-gradient(to right, transparent, black 0%, black 90%, transparent 100%); } } - -.under-image { - background-color: var(--breadcrumb-bg-color); - color: white; - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; - text-align: center; -} diff --git a/UI/Web/src/app/_models/chapter.ts b/UI/Web/src/app/_models/chapter.ts index 3e21b37a6..e52efd202 100644 --- a/UI/Web/src/app/_models/chapter.ts +++ b/UI/Web/src/app/_models/chapter.ts @@ -7,6 +7,7 @@ import {Person} from "./metadata/person"; import {IHasCast} from "./common/i-has-cast"; import {IHasReadingTime} from "./common/i-has-reading-time"; import {IHasCover} from "./common/i-has-cover"; +import {IHasProgress} from "./common/i-has-progress"; export const LooseLeafOrDefaultNumber = -100000; export const SpecialVolumeNumber = 100000; @@ -14,7 +15,7 @@ export const SpecialVolumeNumber = 100000; /** * Chapter table object. This does not have metadata on it, use ChapterMetadata which is the same Chapter but with those fields. */ -export interface Chapter extends IHasCast, IHasReadingTime, IHasCover { +export interface Chapter extends IHasCast, IHasReadingTime, IHasCover, IHasProgress { id: number; range: string; /** diff --git a/UI/Web/src/app/_models/common/i-has-progress.ts b/UI/Web/src/app/_models/common/i-has-progress.ts new file mode 100644 index 000000000..4605dc0a8 --- /dev/null +++ b/UI/Web/src/app/_models/common/i-has-progress.ts @@ -0,0 +1,4 @@ +export interface IHasProgress { + pages: number; + pagesRead: number; +} diff --git a/UI/Web/src/app/_models/series.ts b/UI/Web/src/app/_models/series.ts index ef460a9f5..8d4f773bb 100644 --- a/UI/Web/src/app/_models/series.ts +++ b/UI/Web/src/app/_models/series.ts @@ -2,8 +2,9 @@ import { MangaFormat } from './manga-format'; import { Volume } from './volume'; import {IHasCover} from "./common/i-has-cover"; import {IHasReadingTime} from "./common/i-has-reading-time"; +import {IHasProgress} from "./common/i-has-progress"; -export interface Series extends IHasCover, IHasReadingTime { +export interface Series extends IHasCover, IHasReadingTime, IHasProgress { id: number; name: string; /** diff --git a/UI/Web/src/app/_models/volume.ts b/UI/Web/src/app/_models/volume.ts index 56ca68ceb..fa4a7989b 100644 --- a/UI/Web/src/app/_models/volume.ts +++ b/UI/Web/src/app/_models/volume.ts @@ -2,8 +2,9 @@ import { Chapter } from './chapter'; import { HourEstimateRange } from './series-detail/hour-estimate-range'; import {IHasCover} from "./common/i-has-cover"; import {IHasReadingTime} from "./common/i-has-reading-time"; +import {IHasProgress} from "./common/i-has-progress"; -export interface Volume extends IHasCover, IHasReadingTime { +export interface Volume extends IHasCover, IHasReadingTime, IHasProgress { id: number; minNumber: number; maxNumber: number; diff --git a/UI/Web/src/app/_services/nav.service.ts b/UI/Web/src/app/_services/nav.service.ts index 0578fc500..2bec44706 100644 --- a/UI/Web/src/app/_services/nav.service.ts +++ b/UI/Web/src/app/_services/nav.service.ts @@ -102,6 +102,7 @@ export class NavService { this.renderer.setStyle(bodyElem, 'margin-top', 'var(--nav-offset)'); this.renderer.removeStyle(bodyElem, 'scrollbar-gutter'); this.renderer.setStyle(bodyElem, 'height', 'calc(var(--vh)*100 - var(--nav-offset))'); + this.renderer.setStyle(bodyElem, 'overflow', 'hidden'); this.renderer.setStyle(this.document.querySelector('html'), 'height', 'calc(var(--vh)*100 - var(--nav-offset))'); this.navbarVisibleSource.next(true); }, 10); @@ -114,9 +115,10 @@ export class NavService { setTimeout(() => { const bodyElem = this.document.querySelector('body'); this.renderer.removeStyle(bodyElem, 'height'); - this.renderer.removeStyle(this.document.querySelector('html'), 'height'); this.renderer.setStyle(bodyElem, 'margin-top', '0px', RendererStyleFlags2.Important); this.renderer.setStyle(bodyElem, 'scrollbar-gutter', 'initial', RendererStyleFlags2.Important); + this.renderer.removeStyle(this.document.querySelector('html'), 'height'); + this.renderer.setStyle(bodyElem, 'overflow', 'auto'); this.navbarVisibleSource.next(false); }, 10); } diff --git a/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.ts b/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.ts index 2bf7dae4b..8c054c69b 100644 --- a/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.ts +++ b/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.ts @@ -11,7 +11,7 @@ import { import {NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle} from '@ng-bootstrap/ng-bootstrap'; import { AccountService } from 'src/app/_services/account.service'; import { Action, ActionItem } from 'src/app/_services/action-factory.service'; -import {AsyncPipe, CommonModule, NgTemplateOutlet} from "@angular/common"; +import {AsyncPipe, NgTemplateOutlet} from "@angular/common"; import {TranslocoDirective} from "@jsverse/transloco"; import {DynamicListPipe} from "./_pipes/dynamic-list.pipe"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; diff --git a/UI/Web/src/app/_single-module/cover-image/cover-image.component.html b/UI/Web/src/app/_single-module/cover-image/cover-image.component.html new file mode 100644 index 000000000..18a137cfa --- /dev/null +++ b/UI/Web/src/app/_single-module/cover-image/cover-image.component.html @@ -0,0 +1,31 @@ + + @if(mobileSeriesImgBackground === 'true') { + + } @else { + + } +
+
+
+ + +
+ +
+
+
+
+ + @if (entity.pagesRead < entity.pages && entity.pagesRead > 0) { +
+ +
+ @if (continueTitle !== '') { +
+
+ {{t('continue-from', {title: continueTitle})}} +
+
+ } + } +
diff --git a/UI/Web/src/app/_single-module/cover-image/cover-image.component.scss b/UI/Web/src/app/_single-module/cover-image/cover-image.component.scss new file mode 100644 index 000000000..d90b194ae --- /dev/null +++ b/UI/Web/src/app/_single-module/cover-image/cover-image.component.scss @@ -0,0 +1,139 @@ +.overlay-information { + position: relative; + top: -364px; + height: 364px; + transition: all 0.2s; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + + &:hover { + cursor: pointer; + background-color: var(--card-overlay-hover-bg-color) !important; + + .overlay-information--centered { + visibility: visible; + } + } + + .overlay-information--centered { + position: absolute; + border-radius: 15px; + background-color: rgba(0, 0, 0, 0.7); + border-radius: 50px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 115; + visibility: hidden; + + &:hover { + background-color: var(--primary-color) !important; + cursor: pointer; + } + + div { + width: 60px; + height: 60px; + i { + font-size: 1.6rem; + line-height: 60px; + width: 100%; + } + } + } +} + +.overlay-information { + position: absolute; + top: 0; + left: 12px; + width: calc(100% - 24px); + height: 100%; + transition: all 0.2s; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + + &:hover { + background-color: var(--card-overlay-hover-bg-color); + cursor: pointer; + } + + .overlay-information--centered { + position: absolute; + border-radius: 15px; + background-color: rgba(0, 0, 0, 0.7); + border-radius: 50px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 115; + + &:hover { + background-color: var(--primary-color) !important; + cursor: pointer; + } + } +} + +.series { + .overlay-information--centered { + div { + height: 32px; + width: 32px; + i { + font-size: 1.4rem; + line-height: 32px; + } + } + } +} + +::ng-deep .image-container app-image img { + border-radius: 4px 4px 0 0; +} + +.progress { + border-radius: 0; +} + +.progress-banner.series { + position: relative; +} + +::ng-deep .progress-banner.series span { + position: absolute; + left: 50%; + transform: translate(-50%, -50%); + color: white; + top: 50%; +} + +.under-image { + position: relative; + + .continue-from { + background-color: var(--breadcrumb-bg-color); + color: white; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + text-align: center; + position: absolute; + width: 100%; + } +} + +@media screen and (max-width: 991px) { + .overlay-information { + visibility: hidden; + .overlay-information--centered { + visibility: hidden !important; + } + } + .progress-banner { + display: none; + } + + .under-image { + display: none; + } +} diff --git a/UI/Web/src/app/_single-module/cover-image/cover-image.component.ts b/UI/Web/src/app/_single-module/cover-image/cover-image.component.ts new file mode 100644 index 000000000..233884223 --- /dev/null +++ b/UI/Web/src/app/_single-module/cover-image/cover-image.component.ts @@ -0,0 +1,36 @@ +import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from '@angular/core'; +import {DecimalPipe, NgClass} from "@angular/common"; +import {TranslocoDirective} from "@jsverse/transloco"; +import {ImageComponent} from "../../shared/image/image.component"; +import {NgbProgressbar, NgbTooltip} from "@ng-bootstrap/ng-bootstrap"; +import {IHasProgress} from "../../_models/common/i-has-progress"; + +/** + * Used for the Series/Volume/Chapter Detail pages + */ +@Component({ + selector: 'app-cover-image', + standalone: true, + imports: [ + NgClass, + TranslocoDirective, + ImageComponent, + NgbProgressbar, + DecimalPipe, + NgbTooltip + ], + templateUrl: './cover-image.component.html', + styleUrl: './cover-image.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CoverImageComponent { + + @Input({required: true}) coverImage!: string; + @Input({required: true}) entity!: IHasProgress; + @Input() continueTitle: string = ''; + @Output() read = new EventEmitter(); + + mobileSeriesImgBackground = getComputedStyle(document.documentElement) + .getPropertyValue('--mobile-series-img-background').trim(); + +} diff --git a/UI/Web/src/app/_single-module/details-tab/details-tab.component.html b/UI/Web/src/app/_single-module/details-tab/details-tab.component.html index bed1498e2..6eb3a9c9a 100644 --- a/UI/Web/src/app/_single-module/details-tab/details-tab.component.html +++ b/UI/Web/src/app/_single-module/details-tab/details-tab.component.html @@ -1,23 +1,21 @@
- - - - {{item.title}} - +

{{t('genres-title')}}

+ + + {{item.title}} -
+
- - - - {{item.title}} - +

{{t('tags-title')}}

+ + + {{item.title}} -
+
diff --git a/UI/Web/src/app/_single-module/details-tab/details-tab.component.ts b/UI/Web/src/app/_single-module/details-tab/details-tab.component.ts index d93619700..39b3b1fe5 100644 --- a/UI/Web/src/app/_single-module/details-tab/details-tab.component.ts +++ b/UI/Web/src/app/_single-module/details-tab/details-tab.component.ts @@ -14,18 +14,20 @@ import {TagBadgeComponent, TagBadgeCursor} from "../../shared/tag-badge/tag-badg import {ImageComponent} from "../../shared/image/image.component"; import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe"; import {ImageService} from "../../_services/image.service"; +import {BadgeExpanderComponent} from "../../shared/badge-expander/badge-expander.component"; @Component({ selector: 'app-details-tab', standalone: true, - imports: [ - CarouselReelComponent, - PersonBadgeComponent, - TranslocoDirective, - TagBadgeComponent, - ImageComponent, - SafeHtmlPipe - ], + imports: [ + CarouselReelComponent, + PersonBadgeComponent, + TranslocoDirective, + TagBadgeComponent, + ImageComponent, + SafeHtmlPipe, + BadgeExpanderComponent + ], templateUrl: './details-tab.component.html', styleUrl: './details-tab.component.scss', changeDetection: ChangeDetectionStrategy.OnPush diff --git a/UI/Web/src/app/_single-module/edit-chapter-modal/edit-chapter-modal.component.html b/UI/Web/src/app/_single-module/edit-chapter-modal/edit-chapter-modal.component.html index 2e05a3d4d..54561a254 100644 --- a/UI/Web/src/app/_single-module/edit-chapter-modal/edit-chapter-modal.component.html +++ b/UI/Web/src/app/_single-module/edit-chapter-modal/edit-chapter-modal.component.html @@ -553,15 +553,17 @@
} - - - @for (file of chapter.files; track file.id) { -
- {{file.filePath}}{{file.bytes | bytes}} -
- } -
-
+ @if (accountService.isAdmin$ | async) { + + + @for (file of chapter.files; track file.id) { +
+ {{file.filePath}}{{file.bytes | bytes}} +
+ } +
+
+ } diff --git a/UI/Web/src/app/_single-module/edit-chapter-modal/edit-chapter-modal.component.ts b/UI/Web/src/app/_single-module/edit-chapter-modal/edit-chapter-modal.component.ts index 9f595cd72..5a0b5bac4 100644 --- a/UI/Web/src/app/_single-module/edit-chapter-modal/edit-chapter-modal.component.ts +++ b/UI/Web/src/app/_single-module/edit-chapter-modal/edit-chapter-modal.component.ts @@ -28,7 +28,6 @@ import {Language} from "../../_models/metadata/language"; import {Person, PersonRole} from "../../_models/metadata/person"; import {Genre} from "../../_models/metadata/genre"; import {AgeRatingDto} from "../../_models/metadata/age-rating-dto"; -import {SeriesService} from "../../_services/series.service"; import {ImageService} from "../../_services/image.service"; import {UploadService} from "../../_services/upload.service"; import {MetadataService} from "../../_services/metadata.service"; diff --git a/UI/Web/src/app/_single-module/edit-volume-modal/edit-volume-modal.component.html b/UI/Web/src/app/_single-module/edit-volume-modal/edit-volume-modal.component.html index b3ef54146..c148d959c 100644 --- a/UI/Web/src/app/_single-module/edit-volume-modal/edit-volume-modal.component.html +++ b/UI/Web/src/app/_single-module/edit-volume-modal/edit-volume-modal.component.html @@ -88,16 +88,17 @@
- - - - @for (file of files; track file.id) { -
- {{file.filePath}}{{file.bytes | bytes}} -
- } -
-
+ @if (accountService.isAdmin$ | async) { + + + @for (file of files; track file.id) { +
+ {{file.filePath}}{{file.bytes | bytes}} +
+ } +
+
+ } diff --git a/UI/Web/src/app/_single-module/review-card-modal/review-card-modal.component.ts b/UI/Web/src/app/_single-module/review-card-modal/review-card-modal.component.ts index d1329e0f3..45f9e27bb 100644 --- a/UI/Web/src/app/_single-module/review-card-modal/review-card-modal.component.ts +++ b/UI/Web/src/app/_single-module/review-card-modal/review-card-modal.component.ts @@ -7,7 +7,7 @@ import { ViewContainerRef, ViewEncapsulation } from '@angular/core'; -import {CommonModule, DOCUMENT, NgOptimizedImage} from '@angular/common'; +import {DOCUMENT, NgOptimizedImage} from '@angular/common'; import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; import {ReactiveFormsModule} from "@angular/forms"; import {UserReview} from "../review-card/user-review"; @@ -20,7 +20,7 @@ import {ProviderImagePipe} from "../../_pipes/provider-image.pipe"; @Component({ selector: 'app-review-card-modal', standalone: true, - imports: [CommonModule, ReactiveFormsModule, SpoilerComponent, SafeHtmlPipe, TranslocoDirective, DefaultValuePipe, NgOptimizedImage, ProviderImagePipe], + imports: [ReactiveFormsModule, SpoilerComponent, SafeHtmlPipe, TranslocoDirective, DefaultValuePipe, NgOptimizedImage, ProviderImagePipe], templateUrl: './review-card-modal.component.html', styleUrls: ['./review-card-modal.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/UI/Web/src/app/_single-module/review-card/review-card.component.ts b/UI/Web/src/app/_single-module/review-card/review-card.component.ts index c7ba47ad8..034173f55 100644 --- a/UI/Web/src/app/_single-module/review-card/review-card.component.ts +++ b/UI/Web/src/app/_single-module/review-card/review-card.component.ts @@ -8,13 +8,12 @@ import { OnInit, Output } from '@angular/core'; -import {CommonModule, NgOptimizedImage} from '@angular/common'; +import {NgOptimizedImage} from '@angular/common'; import {UserReview} from "./user-review"; import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; import {ReviewCardModalComponent} from "../review-card-modal/review-card-modal.component"; import {AccountService} from "../../_services/account.service"; import { - ReviewSeriesModalCloseAction, ReviewSeriesModalCloseEvent, ReviewSeriesModalComponent } from "../review-series-modal/review-series-modal.component"; diff --git a/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.html b/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.html index 90ff4867a..6539a4e41 100644 --- a/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.html +++ b/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.html @@ -13,14 +13,16 @@ -
- @if (reviewGroup.get('reviewBody')?.errors?.required) { -
{{t('required')}}
- } - @if (reviewGroup.get('reviewBody')?.errors?.minlength) { -
{{t('min-length', {count: minLength})}}
- } -
+ @if (reviewGroup.dirty || reviewGroup.touched) { +
+ @if (reviewGroup.get('reviewBody')?.errors?.required) { +
{{t('required')}}
+ } + @if (reviewGroup.get('reviewBody')?.errors?.minlength) { +
{{t('min-length', {count: minLength})}}
+ } +
+ } diff --git a/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.ts b/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.ts index 227938b25..3bf9e02a4 100644 --- a/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.ts +++ b/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.ts @@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - EventEmitter, inject, Input, OnInit @@ -11,7 +10,6 @@ import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/ import {NgbActiveModal, NgbRating} from '@ng-bootstrap/ng-bootstrap'; import { SeriesService } from 'src/app/_services/series.service'; import {UserReview} from "../review-card/user-review"; -import {CommonModule} from "@angular/common"; import {translate, TranslocoDirective} from "@jsverse/transloco"; import {ConfirmService} from "../../shared/confirm.service"; import {ToastrService} from "ngx-toastr"; @@ -31,7 +29,7 @@ export interface ReviewSeriesModalCloseEvent { @Component({ selector: 'app-review-series-modal', standalone: true, - imports: [CommonModule, NgbRating, ReactiveFormsModule, TranslocoDirective], + imports: [NgbRating, ReactiveFormsModule, TranslocoDirective], templateUrl: './review-series-modal.component.html', styleUrls: ['./review-series-modal.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush diff --git a/UI/Web/src/app/_single-module/series-preview-drawer/series-preview-drawer.component.scss b/UI/Web/src/app/_single-module/series-preview-drawer/series-preview-drawer.component.scss index 6ecae6dba..d3c04eff2 100644 --- a/UI/Web/src/app/_single-module/series-preview-drawer/series-preview-drawer.component.scss +++ b/UI/Web/src/app/_single-module/series-preview-drawer/series-preview-drawer.component.scss @@ -22,6 +22,6 @@ a.read-more-link { } .offcanvas-body { - mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%); - -webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%); + mask-image: linear-gradient(to bottom, transparent, black 0%, black 97%, transparent 100%); + -webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 97%, transparent 100%); } diff --git a/UI/Web/src/app/_single-module/spoiler/spoiler.component.html b/UI/Web/src/app/_single-module/spoiler/spoiler.component.html index 67b6e2a5e..c8a4ddc53 100644 --- a/UI/Web/src/app/_single-module/spoiler/spoiler.component.html +++ b/UI/Web/src/app/_single-module/spoiler/spoiler.component.html @@ -1,8 +1,9 @@
- {{t('click-to-show')}} - + @if (isCollapsed) { + {{t('click-to-show')}} + } @else {
-
+ }
diff --git a/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts b/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts index 4c5021479..781212b9f 100644 --- a/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts +++ b/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts @@ -7,14 +7,13 @@ import { OnInit, ViewEncapsulation } from '@angular/core'; -import {CommonModule} from '@angular/common'; import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe"; import {TranslocoDirective} from "@jsverse/transloco"; @Component({ selector: 'app-spoiler', standalone: true, - imports: [CommonModule, SafeHtmlPipe, TranslocoDirective], + imports: [SafeHtmlPipe, TranslocoDirective], templateUrl: './spoiler.component.html', styleUrls: ['./spoiler.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.ts b/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.ts index 2d275a27c..1b30bb168 100644 --- a/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.ts +++ b/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.ts @@ -1,5 +1,4 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core'; -import {CommonModule} from '@angular/common'; import {ScrobbleProvider, ScrobblingService} from "../../_services/scrobbling.service"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; @@ -21,7 +20,7 @@ import {LooseLeafOrDefaultNumber, SpecialVolumeNumber} from "../../_models/chapt @Component({ selector: 'app-user-scrobble-history', standalone: true, - imports: [CommonModule, ScrobbleEventTypePipe, NgbPagination, ReactiveFormsModule, SortableHeader, TranslocoModule, + imports: [ScrobbleEventTypePipe, NgbPagination, ReactiveFormsModule, SortableHeader, TranslocoModule, DefaultValuePipe, TranslocoLocaleModule, UtcToLocalTimePipe, NgbTooltip], templateUrl: './user-scrobble-history.component.html', styleUrls: ['./user-scrobble-history.component.scss'], diff --git a/UI/Web/src/app/_single-modules/age-rating-image/age-rating-image.component.html b/UI/Web/src/app/_single-modules/age-rating-image/age-rating-image.component.html index 58ae847ae..2be9b618d 100644 --- a/UI/Web/src/app/_single-modules/age-rating-image/age-rating-image.component.html +++ b/UI/Web/src/app/_single-modules/age-rating-image/age-rating-image.component.html @@ -1 +1 @@ - + diff --git a/UI/Web/src/app/_single-modules/age-rating-image/age-rating-image.component.ts b/UI/Web/src/app/_single-modules/age-rating-image/age-rating-image.component.ts index a44916f5e..b9bf2befd 100644 --- a/UI/Web/src/app/_single-modules/age-rating-image/age-rating-image.component.ts +++ b/UI/Web/src/app/_single-modules/age-rating-image/age-rating-image.component.ts @@ -4,6 +4,9 @@ import {ImageComponent} from "../../shared/image/image.component"; import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap"; import {AgeRatingPipe} from "../../_pipes/age-rating.pipe"; import {AsyncPipe} from "@angular/common"; +import {FilterUtilitiesService} from "../../shared/_services/filter-utilities.service"; +import {FilterComparison} from "../../_models/metadata/v2/filter-comparison"; +import {FilterField} from "../../_models/metadata/v2/filter-field"; const basePath = './assets/images/ratings/'; @@ -22,9 +25,11 @@ const basePath = './assets/images/ratings/'; }) export class AgeRatingImageComponent implements OnInit { private readonly cdRef = inject(ChangeDetectorRef); + private readonly filterUtilityService = inject(FilterUtilitiesService); + + protected readonly AgeRating = AgeRating; @Input({required: true}) rating: AgeRating = AgeRating.Unknown; - protected readonly AgeRating = AgeRating; imageUrl: string = 'unknown-rating.png'; @@ -79,4 +84,9 @@ export class AgeRatingImageComponent implements OnInit { this.cdRef.markForCheck(); } + openRating() { + this.filterUtilityService.applyFilter(['all-series'], FilterField.AgeRating, FilterComparison.Equal, `${this.rating}`).subscribe(); + } + + } diff --git a/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html b/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html index 569f98187..33482af62 100644 --- a/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html +++ b/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html @@ -1,4 +1,6 @@ +

{{t('description')}}

+

{{t('setting-description')}}

diff --git a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html index 923052f4c..d94961fb7 100644 --- a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html +++ b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html @@ -24,10 +24,6 @@ - - @if (formControl.dirty) { - - } } diff --git a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts index b6a8d7e59..dc4df9825 100644 --- a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts +++ b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts @@ -75,6 +75,12 @@ export class ManageMediaSettingsComponent implements OnInit { return this.settingsService.updateServerSettings(data); }), tap(settings => { + + const encodingChanged = this.serverSettings.encodeMediaAs !== settings.encodeMediaAs; + if (encodingChanged) { + this.toastr.info(translate('manage-media-settings.media-warning')); + } + this.serverSettings = settings; this.resetForm(); this.cdRef.markForCheck(); diff --git a/UI/Web/src/app/admin/manage-scrobble-errors/manage-scrobble-errors.component.ts b/UI/Web/src/app/admin/manage-scrobble-errors/manage-scrobble-errors.component.ts index 13c8164e0..74d1729f3 100644 --- a/UI/Web/src/app/admin/manage-scrobble-errors/manage-scrobble-errors.component.ts +++ b/UI/Web/src/app/admin/manage-scrobble-errors/manage-scrobble-errors.component.ts @@ -10,7 +10,6 @@ import { QueryList, ViewChildren } from '@angular/core'; -import {CommonModule} from '@angular/common'; import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms"; import {compare, SortableHeader, SortEvent} from "../../_single-module/table/_directives/sortable-header.directive"; import {KavitaMediaError} from "../_models/media-error"; @@ -34,7 +33,7 @@ import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe"; @Component({ selector: 'app-manage-scrobble-errors', standalone: true, - imports: [CommonModule, ReactiveFormsModule, FilterPipe, LoadingComponent, SortableHeader, TranslocoModule, DefaultDatePipe, DefaultValuePipe, TranslocoLocaleModule, UtcToLocalTimePipe], + imports: [ReactiveFormsModule, FilterPipe, LoadingComponent, SortableHeader, TranslocoModule, DefaultDatePipe, DefaultValuePipe, TranslocoLocaleModule, UtcToLocalTimePipe], templateUrl: './manage-scrobble-errors.component.html', styleUrls: ['./manage-scrobble-errors.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush diff --git a/UI/Web/src/app/admin/manage-settings/manage-settings.component.ts b/UI/Web/src/app/admin/manage-settings/manage-settings.component.ts index bd5789435..ac1480009 100644 --- a/UI/Web/src/app/admin/manage-settings/manage-settings.component.ts +++ b/UI/Web/src/app/admin/manage-settings/manage-settings.component.ts @@ -14,7 +14,7 @@ import {SettingItemComponent} from "../../settings/_components/setting-item/sett import {SettingSwitchComponent} from "../../settings/_components/setting-switch/setting-switch.component"; import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe"; import {ConfirmService} from "../../shared/confirm.service"; -import {debounceTime, distinctUntilChanged, filter, switchMap, tap} from "rxjs"; +import {debounceTime, distinctUntilChanged, filter, of, switchMap, tap} from "rxjs"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; const ValidIpAddress = /^(\s*((([12]?\d{1,2}\.){3}[12]?\d{1,2})|(([\da-f]{0,4}\:){0,7}([\da-f]{0,4})))\s*\,)*\s*((([12]?\d{1,2}\.){3}[12]?\d{1,2})|(([\da-f]{0,4}\:){0,7}([\da-f]{0,4})))\s*$/i; @@ -69,7 +69,6 @@ export class ManageSettingsComponent implements OnInit { this.settingsForm.addControl('allowStatCollection', new FormControl(this.serverSettings.allowStatCollection, [Validators.required])); this.settingsForm.addControl('enableOpds', new FormControl(this.serverSettings.enableOpds, [Validators.required])); this.settingsForm.addControl('baseUrl', new FormControl(this.serverSettings.baseUrl, [Validators.pattern(/^(\/[\w-]+)*\/$/)])); - this.settingsForm.addControl('emailServiceUrl', new FormControl(this.serverSettings.emailServiceUrl, [Validators.required])); this.settingsForm.addControl('totalBackups', new FormControl(this.serverSettings.totalBackups, [Validators.required, Validators.min(1), Validators.max(30)])); this.settingsForm.addControl('cacheSize', new FormControl(this.serverSettings.cacheSize, [Validators.required, Validators.min(50)])); this.settingsForm.addControl('totalLogs', new FormControl(this.serverSettings.totalLogs, [Validators.required, Validators.min(1), Validators.max(30)])); diff --git a/UI/Web/src/app/admin/manage-tasks-settings/manage-tasks-settings.component.ts b/UI/Web/src/app/admin/manage-tasks-settings/manage-tasks-settings.component.ts index c0a11eb59..b99edc688 100644 --- a/UI/Web/src/app/admin/manage-tasks-settings/manage-tasks-settings.component.ts +++ b/UI/Web/src/app/admin/manage-tasks-settings/manage-tasks-settings.component.ts @@ -70,7 +70,7 @@ export class ManageTasksSettingsComponent implements OnInit { api: defer(() => { localStorage.removeItem('@transloco/translations/timestamp'); localStorage.removeItem('@transloco/translations'); - localStorage.removeItem('translocoLang'); + location.reload(); return of(); }), successMessage: 'bust-locale-task-success', diff --git a/UI/Web/src/app/all-filters/all-filters.component.ts b/UI/Web/src/app/all-filters/all-filters.component.ts index 021c76faa..365f24150 100644 --- a/UI/Web/src/app/all-filters/all-filters.component.ts +++ b/UI/Web/src/app/all-filters/all-filters.component.ts @@ -1,5 +1,4 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core'; -import {CommonModule} from '@angular/common'; import {JumpKey} from "../_models/jumpbar/jump-key"; import {TranslocoDirective} from "@jsverse/transloco"; import {CardItemComponent} from "../cards/card-item/card-item.component"; @@ -19,11 +18,12 @@ import {ActionService} from "../_services/action.service"; import {FilterPipe} from "../_pipes/filter.pipe"; import {filter} from "rxjs"; import {ManageSmartFiltersComponent} from "../sidenav/_components/manage-smart-filters/manage-smart-filters.component"; +import {DecimalPipe} from "@angular/common"; @Component({ selector: 'app-all-filters', standalone: true, - imports: [CommonModule, TranslocoDirective, CardItemComponent, SideNavCompanionBarComponent, CardDetailLayoutComponent, SafeHtmlPipe, CardActionablesComponent, RouterLink, FilterPipe, ManageSmartFiltersComponent], + imports: [TranslocoDirective, CardItemComponent, SideNavCompanionBarComponent, CardDetailLayoutComponent, SafeHtmlPipe, CardActionablesComponent, RouterLink, FilterPipe, ManageSmartFiltersComponent, DecimalPipe], templateUrl: './all-filters.component.html', styleUrl: './all-filters.component.scss', changeDetection: ChangeDetectionStrategy.OnPush @@ -40,7 +40,6 @@ export class AllFiltersComponent implements OnInit { jumpbarKeys: Array = []; filters: SmartFilter[] = []; isLoading = true; - trackByIdentity = (index: number, item: SmartFilter) => item.name; ngOnInit() { this.loadData(); @@ -55,14 +54,6 @@ export class AllFiltersComponent implements OnInit { }); } - async loadSmartFilter(filter: SmartFilter) { - await this.router.navigateByUrl('all-series?' + filter.filter); - } - - isErrored(filter: SmartFilter) { - return !decodeURIComponent(filter.filter).includes('¦'); - } - async deleteFilter(filter: SmartFilter) { await this.actionService.deleteFilter(filter.id, success => { this.filters = this.filters.filter(f => f.id != filter.id); @@ -80,6 +71,4 @@ export class AllFiltersComponent implements OnInit { break; } } - - protected readonly filter = filter; } diff --git a/UI/Web/src/app/app.component.scss b/UI/Web/src/app/app.component.scss index 02a0bc253..5bd3746a9 100644 --- a/UI/Web/src/app/app.component.scss +++ b/UI/Web/src/app/app.component.scss @@ -1,5 +1,6 @@ +@import '../theme/variables'; .content-wrapper { - padding: 0 10px 0; + padding: 0 0 0 10px; height: calc(var(--vh)* 100 - var(--nav-offset)); } @@ -7,23 +8,44 @@ .companion-bar { transition: all var(--side-nav-companion-bar-transistion); margin-left: 40px; - overflow-y: auto; + overflow-y: hidden; overflow-x: hidden; - height: calc(var(--vh)* 100 - var(--nav-offset)); - width: 100%; + height: calc(var(--vh)* 100 - var(--nav-mobile-offset)); + padding-right: 10px; + scrollbar-gutter: stable both-edges; + scrollbar-width: thin; + mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%); + -webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%); + + // For firefox + @supports (-moz-appearance:none) { + scrollbar-color: transparent transparent; + scrollbar-width: thin; + } &::-webkit-scrollbar { background-color: transparent; /*make scrollbar space invisible */ width: inherit; + display: none; + visibility: hidden; + background: transparent; } &::-webkit-scrollbar-thumb { - background-color: transparent; /*makes it invisible when not hovering*/ + background: transparent; /*makes it invisible when not hovering*/ } &:hover { + scrollbar-width: thin; + overflow-y: auto; + + // For firefox + @supports (-moz-appearance:none) { + scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0); + } &::-webkit-scrollbar-thumb { + visibility: visible; background-color: rgba(255,255,255,0.3); /*On hover, it will turn grey*/ } } @@ -36,12 +58,10 @@ .companion-bar-content { margin-left: 190px; - mask-image: linear-gradient(to bottom, transparent, black 0%, black 96%, transparent 100%); - -webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 96%, transparent 100%); - width: calc(100% - 190px); + width: calc(100% - 180px); } -@media (max-width: 768px) { +@media (max-width: $grid-breakpoints-lg) { ::ng-deep html { height: 100dvh !important; } @@ -52,34 +72,24 @@ .content-wrapper { overflow: hidden; - height: calc(var(--vh)* 100 - var(--nav-mobile-offset)); + height: calc(var(--vh)* 100); padding: 0 10px 0; &.closed { overflow: auto; + height: calc(var(--vh) * 100); } } .companion-bar { margin-left: 0; padding-left: 0; - width: calc(100vw - 30px); - padding-top: 20px; - height: calc(100dvh - var(--nav-mobile-offset)); - - &::-webkit-scrollbar { - width: inherit; - } - - &::-webkit-scrollbar-thumb { - background-color: transparent; /*makes it invisible when not hovering*/ - } - - &:hover { - &::-webkit-scrollbar-thumb { - background-color: rgba(255,255,255,0.3); /*On hover, it will turn grey*/ - } - } + width: 100%; + padding: 10px 0 0; + height: calc(var(--vh) * 100 - var(--nav-mobile-offset)); + scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0.1); + scrollbar-width: thin; + margin-bottom: 20px; } .companion-bar-content { diff --git a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.scss b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.scss index abab89f35..f0a064718 100644 --- a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.scss +++ b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.scss @@ -46,6 +46,7 @@ --accordion-header-bg-color: grey; --br-actionbar-button-hover-border-color: #6c757d; --br-actionbar-bg-color: white; + --default-state-scrollbar: var(--primary-color-scrollbar); } @@ -53,7 +54,22 @@ $dark-form-background-no-opacity: rgb(1, 4, 9); $primary-color: #0062cc; $action-bar-height: 38px; +.reader-container { + &::-webkit-scrollbar { + background-color: transparent; /*make scrollbar space invisible */ + width: inherit; + } + &::-webkit-scrollbar-thumb { + background-color: var(--default-state-scrollbar); /*makes it invisible when not hovering*/ + } + + &:hover { + &::-webkit-scrollbar-thumb { + background-color: var(--primary-color-scrollbar); /*On hover, it will turn grey*/ + } + } +} // Drawer .control-container { diff --git a/UI/Web/src/app/book-reader/_components/personal-table-of-contents/personal-table-of-contents.component.html b/UI/Web/src/app/book-reader/_components/personal-table-of-contents/personal-table-of-contents.component.html index 33b3a1a59..591972499 100644 --- a/UI/Web/src/app/book-reader/_components/personal-table-of-contents/personal-table-of-contents.component.html +++ b/UI/Web/src/app/book-reader/_components/personal-table-of-contents/personal-table-of-contents.component.html @@ -1,24 +1,32 @@
-
- {{t('no-data')}} -
+ @if (Pages.length === 0) { +
+ {{t('no-data')}} +
+ }
    -
  • - {{t('page', {value: page})}} -
      -
    • - {{bookmark.title}} - -
    • -
    -
  • + @for (page of Pages; track page) { +
  • + {{t('page', {value: page})}} +
      + @for(bookmark of bookmarks[page]; track bookmark) { +
    • + {{bookmark.title}} + +
    • + } + +
    +
  • + } +
diff --git a/UI/Web/src/app/book-reader/_components/personal-table-of-contents/personal-table-of-contents.component.ts b/UI/Web/src/app/book-reader/_components/personal-table-of-contents/personal-table-of-contents.component.ts index f57065530..c5e7bbc87 100644 --- a/UI/Web/src/app/book-reader/_components/personal-table-of-contents/personal-table-of-contents.component.ts +++ b/UI/Web/src/app/book-reader/_components/personal-table-of-contents/personal-table-of-contents.component.ts @@ -8,7 +8,7 @@ import { OnInit, Output } from '@angular/core'; -import {CommonModule, DOCUMENT} from '@angular/common'; +import {DOCUMENT} from '@angular/common'; import {ReaderService} from "../../../_services/reader.service"; import {PersonalToC} from "../../../_models/readers/personal-toc"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; @@ -23,7 +23,7 @@ export interface PersonalToCEvent { @Component({ selector: 'app-personal-table-of-contents', standalone: true, - imports: [CommonModule, NgbTooltip, TranslocoDirective], + imports: [NgbTooltip, TranslocoDirective], templateUrl: './personal-table-of-contents.component.html', styleUrls: ['./personal-table-of-contents.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush diff --git a/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.html b/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.html index 677a96962..f825df447 100644 --- a/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.html +++ b/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.html @@ -647,7 +647,7 @@ -

Volumes

+

{{t('volumes-title')}}

@if (isLoadingVolumes) {
{{t('loading')}} @@ -671,7 +671,7 @@
diff --git a/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts b/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts index a0a30b4b2..cea5ffd00 100644 --- a/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts +++ b/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts @@ -17,7 +17,7 @@ import { NgbNavOutlet, NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; -import { forkJoin, Observable, of } from 'rxjs'; +import {forkJoin, Observable, of, tap} from 'rxjs'; import { map } from 'rxjs/operators'; import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service'; import { TypeaheadSettings } from 'src/app/typeahead/_models/typeahead-settings'; @@ -184,6 +184,7 @@ export class EditSeriesModalComponent implements OnInit { */ selectedCover: string = ''; coverImageReset = false; + isAdmin: boolean = false; saveNestedComponents: EventEmitter = new EventEmitter(); @@ -202,6 +203,11 @@ export class EditSeriesModalComponent implements OnInit { this.libraryName = names[this.series.libraryId]; }); + this.accountService.isAdmin$.pipe(takeUntilDestroyed(this.destroyRef), tap(isAdmin => { + this.isAdmin = isAdmin; + this.cdRef.markForCheck(); + })).subscribe(); + this.initSeries = Object.assign({}, this.series); this.editSeriesForm = this.fb.group({ diff --git a/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.html b/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.html index 3ea157b65..0eae4f356 100644 --- a/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.html +++ b/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.html @@ -1,33 +1,36 @@ - -
-
+ @if (bulkSelectionService.selections$ | async; as selectionCount) { + @if (selectionCount > 0) { +
+
{{t('items-selected',{num: selectionCount | number})}} - + @if (hasMarkAsUnread) { } - @if (hasMarkAsRead) { - - } - + } + - Bulk Actions + Bulk Actions - + +
-
- + } + + } diff --git a/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.ts b/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.ts index 78b9a4bed..f7b138d7a 100644 --- a/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.ts +++ b/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.ts @@ -10,7 +10,7 @@ import { import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/action-factory.service'; import { BulkSelectionService } from '../bulk-selection.service'; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {AsyncPipe, CommonModule} from "@angular/common"; +import {AsyncPipe, DecimalPipe, NgStyle} from "@angular/common"; import {TranslocoModule} from "@jsverse/transloco"; import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap"; import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component"; @@ -19,11 +19,12 @@ import {CardActionablesComponent} from "../../_single-module/card-actionables/ca selector: 'app-bulk-operations', standalone: true, imports: [ - CommonModule, AsyncPipe, CardActionablesComponent, TranslocoModule, - NgbTooltip + NgbTooltip, + NgStyle, + DecimalPipe ], templateUrl: './bulk-operations.component.html', styleUrls: ['./bulk-operations.component.scss'], @@ -36,7 +37,7 @@ export class BulkOperationsComponent implements OnInit { * Modal mode means don't fix to the top */ @Input() modalMode = false; - @Input() topOffset: number = 60; + @Input() topOffset: number = 75; hasMarkAsRead: boolean = false; hasMarkAsUnread: boolean = false; actions: Array> = []; diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html index 30abed498..3f7184b59 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html @@ -26,7 +26,7 @@
-
+
@if (items.length === 0 && !isLoading) {

} diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss index 5180b428e..392ec4e83 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss @@ -2,7 +2,7 @@ display: flex; flex-direction: row; width: 100%; - height: calc((var(--vh) *100) - 173px); + height: calc((var(--vh) *100) - 157px); margin-bottom: 10px; &.empty { @@ -94,10 +94,12 @@ .virtual-scroller, virtual-scroller { width: 100%; - height: calc(var(--vh) * 100 - 143px); - mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%); - -webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%); + height: calc(var(--vh) * 100 - 155px); + mask-image: linear-gradient(to bottom, transparent, black 0%, black 97%, transparent 100%); + -webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 97%, transparent 100%); overflow: auto; + scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0.1); + scrollbar-width: thin; } @@ -163,6 +165,7 @@ h2 { justify-content: center; font-size: 18px; border-radius: 4px; + transform-style: preserve-3d; } .flip-button-back { diff --git a/UI/Web/src/app/carousel/_components/carousel-reel/carousel-reel.component.scss b/UI/Web/src/app/carousel/_components/carousel-reel/carousel-reel.component.scss index 58c983e4f..cad8e95bc 100644 --- a/UI/Web/src/app/carousel/_components/carousel-reel/carousel-reel.component.scss +++ b/UI/Web/src/app/carousel/_components/carousel-reel/carousel-reel.component.scss @@ -4,6 +4,8 @@ position: relative; display: flex; flex-direction: column; + mask-image: linear-gradient(to right, transparent, black 0%, black 99%, transparent 100%); + -webkit-mask-image: linear-gradient(to right, transparent, black 0%, black 99%, transparent 100%); } .carousel-container { diff --git a/UI/Web/src/app/chapter-detail/chapter-detail.component.html b/UI/Web/src/app/chapter-detail/chapter-detail.component.html index 4936078e5..610655734 100644 --- a/UI/Web/src/app/chapter-detail/chapter-detail.component.html +++ b/UI/Web/src/app/chapter-detail/chapter-detail.component.html @@ -5,30 +5,7 @@ @if (chapter && series && libraryType !== null) {
- - @if(mobileSeriesImgBackground === 'true') { - - } @else { - - } - - @if (chapter.pagesRead < chapter.pages && chapter.pagesRead > 0) { -
- -
- } -
-
-
- - -
- -
-
-
-
- +

diff --git a/UI/Web/src/app/chapter-detail/chapter-detail.component.ts b/UI/Web/src/app/chapter-detail/chapter-detail.component.ts index 625dc2bdd..ad64e2063 100644 --- a/UI/Web/src/app/chapter-detail/chapter-detail.component.ts +++ b/UI/Web/src/app/chapter-detail/chapter-detail.component.ts @@ -82,6 +82,7 @@ import {ActionService} from "../_services/action.service"; import {PublicationStatusPipe} from "../_pipes/publication-status.pipe"; import {DefaultDatePipe} from "../_pipes/default-date.pipe"; import {MangaFormatPipe} from "../_pipes/manga-format.pipe"; +import {CoverImageComponent} from "../_single-module/cover-image/cover-image.component"; enum TabID { Related = 'related-tab', @@ -139,7 +140,8 @@ enum TabID { PublicationStatusPipe, DatePipe, DefaultDatePipe, - MangaFormatPipe + MangaFormatPipe, + CoverImageComponent ], templateUrl: './chapter-detail.component.html', styleUrl: './chapter-detail.component.scss', diff --git a/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.scss b/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.scss index 8c21e2b4b..400d6c154 100644 --- a/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.scss +++ b/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.scss @@ -20,6 +20,7 @@ grid-template-columns: repeat(auto-fill, 160px); grid-gap: 0.5rem; justify-content: space-around; + padding-bottom: 20px; } diff --git a/UI/Web/src/app/manga-reader/_components/infinite-scroller/infinite-scroller.component.ts b/UI/Web/src/app/manga-reader/_components/infinite-scroller/infinite-scroller.component.ts index 6e1a88432..0aef10674 100644 --- a/UI/Web/src/app/manga-reader/_components/infinite-scroller/infinite-scroller.component.ts +++ b/UI/Web/src/app/manga-reader/_components/infinite-scroller/infinite-scroller.component.ts @@ -174,9 +174,9 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy, debugLogFilter: Array = ['[PREFETCH]', '[Intersection]', '[Visibility]', '[Image Load]']; /** - * Width override for maunal width control + * Width override for manual width control * 2 observables needed to avoid flickering, probably due to data races, when changing the width - * this allows to precicely define execution order + * this allows to precisely define execution order */ widthOverride$ : Observable = new Observable(); widthSliderValue$ : Observable = new Observable(); @@ -247,7 +247,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy, this.widthOverride$ = this.widthSliderValue$; - //perfom jump so the page stays in view + //perform jump so the page stays in view this.widthSliderValue$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => { this.currentPageElem = this.document.querySelector('img#page-' + this.pageNum); if(!this.currentPageElem) diff --git a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.scss b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.scss index ba8464259..cd4de992e 100644 --- a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.scss +++ b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.scss @@ -19,6 +19,22 @@ $pointer-offset: 5px; overflow: auto; text-align: center; //height: calc(var(--vh)*100); // this needs to be applied on the DOM because it breaks infinite scroller + + &::-webkit-scrollbar { + background-color: transparent; /*make scrollbar space invisible */ + width: inherit; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--default-state-scrollbar); /*makes it invisible when not hovering*/ + } + + &:hover { + &::-webkit-scrollbar-thumb { + background-color: var(--primary-color-scrollbar); /*On hover, it will turn grey*/ + } + } + } diff --git a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html index fe8e9697d..e882a0f65 100644 --- a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html +++ b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html @@ -4,34 +4,11 @@ @if (series && seriesMetadata && libraryType !== null) {
-
- @if(mobileSeriesImgBackground === 'true') { - - } @else { - - } - @if (series.pagesRead < series.pages && hasReadingProgress) { -
- -
-
- {{t('continue-from', {title: ContinuePointTitle})}} -
- - } -
-
-
- - -
- -
-
-
-
- +
+
+

{{series.name}} diff --git a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.scss b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.scss index 0a7b2cb3c..5d2a0e6c3 100644 --- a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.scss +++ b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.scss @@ -13,6 +13,7 @@ grid-template-columns: repeat(auto-fill, 160px); grid-gap: 0.5rem; justify-content: space-around; + padding-bottom: 20px; } ::ng-deep .carousel-container .header i.fa-plus, ::ng-deep .carousel-container .header i.fa-pen { diff --git a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts index 49805325c..1fce88107 100644 --- a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts +++ b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts @@ -145,6 +145,7 @@ import {CollectionTagService} from "../../../_services/collection-tag.service"; import {UserCollection} from "../../../_models/collection-tag"; import {SeriesFormatComponent} from "../../../shared/series-format/series-format.component"; import {MangaFormatPipe} from "../../../_pipes/manga-format.pipe"; +import {CoverImageComponent} from "../../../_single-module/cover-image/cover-image.component"; enum TabID { @@ -179,7 +180,7 @@ interface StoryLineItem { NgClass, NgOptimizedImage, ProviderImagePipe, AsyncPipe, PersonBadgeComponent, DetailsTabComponent, ChapterCardComponent, VolumeCardComponent, JsonPipe, AgeRatingPipe, DefaultValuePipe, ExternalRatingComponent, ReadMoreComponent, ReadTimePipe, RouterLink, TimeAgoPipe, AgeRatingImageComponent, CompactNumberPipe, IconAndTitleComponent, SafeHtmlPipe, BadgeExpanderComponent, - A11yClickDirective, ReadTimeLeftPipe, PublicationStatusPipe, MetadataDetailRowComponent, DownloadButtonComponent, RelatedTabComponent, SeriesFormatComponent, MangaFormatPipe] + A11yClickDirective, ReadTimeLeftPipe, PublicationStatusPipe, MetadataDetailRowComponent, DownloadButtonComponent, RelatedTabComponent, SeriesFormatComponent, MangaFormatPipe, CoverImageComponent] }) export class SeriesDetailComponent implements OnInit, AfterContentChecked { @@ -418,8 +419,10 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { chapterLocaleKey = 'common.issue-num-shorthand'; break; case LibraryType.Book: - case LibraryType.Manga: case LibraryType.LightNovel: + chapterLocaleKey = 'common.book-num-shorthand'; + break; + case LibraryType.Manga: case LibraryType.Images: chapterLocaleKey = 'common.chapter-num-shorthand'; break; diff --git a/UI/Web/src/app/settings/_components/settings/settings.component.html b/UI/Web/src/app/settings/_components/settings/settings.component.html index c1228f9a6..54c36428e 100644 --- a/UI/Web/src/app/settings/_components/settings/settings.component.html +++ b/UI/Web/src/app/settings/_components/settings/settings.component.html @@ -10,7 +10,7 @@ @if (accountService.hasAdminRole(user)) { @defer (when fragment === SettingsTabId.General; prefetch on idle) { @if (fragment === SettingsTabId.General) { -
+
} @@ -18,7 +18,7 @@ @defer (when fragment === SettingsTabId.Email; prefetch on idle) { @if (fragment === SettingsTabId.Email) { -
+
} @@ -26,7 +26,7 @@ @defer (when fragment === SettingsTabId.Media; prefetch on idle) { @if (fragment === SettingsTabId.Media) { -
+
} @@ -92,7 +92,7 @@ @defer (when fragment === SettingsTabId.Account; prefetch on idle) { @if (fragment === SettingsTabId.Account) { -
+
@@ -106,7 +106,7 @@ @defer (when fragment === SettingsTabId.Preferences; prefetch on idle) { @if (fragment === SettingsTabId.Preferences) { -
+
} @@ -122,7 +122,7 @@ @defer (when fragment === SettingsTabId.Clients; prefetch on idle) { @if (fragment === SettingsTabId.Clients) { -
+
} diff --git a/UI/Web/src/app/sidenav/_components/side-nav-item/side-nav-item.component.scss b/UI/Web/src/app/sidenav/_components/side-nav-item/side-nav-item.component.scss index 4fa4872ed..9dc335bba 100644 --- a/UI/Web/src/app/sidenav/_components/side-nav-item/side-nav-item.component.scss +++ b/UI/Web/src/app/sidenav/_components/side-nav-item/side-nav-item.component.scss @@ -1,3 +1,5 @@ +@import '../../../../theme/variables'; + ::ng-deep .side-nav.closed { .side-nav-item { .side-nav-text { @@ -133,7 +135,7 @@ a { color: var(--side-nav-text-color); } -@media (max-width: 768px) { +@media (max-width: $grid-breakpoints-lg) { .side-nav-item { align-items: center; padding: 0 10px; diff --git a/UI/Web/src/app/sidenav/_components/side-nav/side-nav.component.html b/UI/Web/src/app/sidenav/_components/side-nav/side-nav.component.html index ec5c475b9..033503cd7 100644 --- a/UI/Web/src/app/sidenav/_components/side-nav/side-nav.component.html +++ b/UI/Web/src/app/sidenav/_components/side-nav/side-nav.component.html @@ -76,12 +76,12 @@ } @if (utilityService.activeBreakpoint$ | async; as breakpoint) { - @if (breakpoint < Breakpoint.Tablet) { + @if (breakpoint < Breakpoint.Desktop) {
} } -
@if ((accountService.hasValidLicense$ | async) === false) {
@if (utilityService.activeBreakpoint$ | async; as breakpoint) { - @if (breakpoint < Breakpoint.Tablet) { + @if (breakpoint < Breakpoint.Desktop) {
} } diff --git a/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.scss b/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.scss index 206b609f1..d74d7858c 100644 --- a/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.scss +++ b/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.scss @@ -1,3 +1,4 @@ +@import '../../../theme/variables'; // TODO: Move this to a common file so it applies both ways .side-nav { padding-bottom: 10px; @@ -8,10 +9,46 @@ margin: 0; left: 10px; top: 73px; + overflow-y: hidden; + overflow-x: hidden; border-radius: var(--side-nav-border-radius); transition: width var(--side-nav-openclose-transition), background-color var(--side-nav-bg-color-transition), border-color var(--side-nav-border-transition); - overflow: auto; border: var(--side-nav-border); + scrollbar-gutter: stable both-edges; + scrollbar-width: thin; + + // For firefox + @supports (-moz-appearance:none) { + scrollbar-color: transparent transparent; + scrollbar-width: thin; + } + + &::-webkit-scrollbar { + background-color: transparent; /*make scrollbar space invisible */ + width: inherit; + display: none; + visibility: hidden; + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: transparent; /*makes it invisible when not hovering*/ + } + + &:hover { + scrollbar-width: thin; + overflow-y: auto; + + // For firefox + @supports (-moz-appearance:none) { + scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0); + } + + &::-webkit-scrollbar-thumb { + visibility: visible; + background-color: rgba(255,255,255,0.3); /*On hover, it will turn grey*/ + } + } &.no-donate { height: calc((var(--vh)*100) - 82px); @@ -36,7 +73,7 @@ } } -@media (max-width: 768px) { +@media (max-width: $grid-breakpoints-lg) { .side-nav { padding: 10px 0; width: 55vw; diff --git a/UI/Web/src/app/user-settings/manga-user-preferences/manage-user-preferences.component.ts b/UI/Web/src/app/user-settings/manga-user-preferences/manage-user-preferences.component.ts index 1eaa3b40c..814d69c9e 100644 --- a/UI/Web/src/app/user-settings/manga-user-preferences/manage-user-preferences.component.ts +++ b/UI/Web/src/app/user-settings/manga-user-preferences/manage-user-preferences.component.ts @@ -1,10 +1,9 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core'; -import {translate, TranslocoDirective} from "@jsverse/transloco"; +import {translate, TranslocoDirective, TranslocoService} from "@jsverse/transloco"; import { bookLayoutModes, bookWritingStyles, layoutModes, - pageLayoutModes, pageSplitOptions, pdfScrollModes, pdfSpreadModes, @@ -15,7 +14,6 @@ import { scalingOptions } from "../../_models/preferences/preferences"; import {AccountService} from "../../_services/account.service"; -import {ToastrService} from "ngx-toastr"; import {BookService} from "../../book-reader/_services/book.service"; import {Title} from "@angular/platform-browser"; import {Router} from "@angular/router"; @@ -37,7 +35,7 @@ import { NgbAccordionDirective, NgbAccordionHeader, NgbAccordionItem, NgbTooltip } from "@ng-bootstrap/ng-bootstrap"; -import {NgForOf, NgIf, NgStyle, NgTemplateOutlet, TitleCasePipe} from "@angular/common"; +import {NgStyle, NgTemplateOutlet, TitleCasePipe} from "@angular/common"; import {ColorPickerModule} from "ngx-color-picker"; import {SettingTitleComponent} from "../../settings/_components/setting-title/setting-title.component"; import {SettingItemComponent} from "../../settings/_components/setting-item/setting-item.component"; @@ -68,12 +66,10 @@ import {PdfScrollModePipe} from "../../_pipes/pdf-scroll-mode.pipe"; NgbAccordionBody, NgbAccordionHeader, NgbAccordionButton, - NgIf, NgbTooltip, NgTemplateOutlet, TitleCasePipe, ColorPickerModule, - NgForOf, SettingTitleComponent, SettingItemComponent, PageLayoutModePipe, @@ -113,7 +109,6 @@ export class ManageUserPreferencesComponent implements OnInit { protected readonly readerModes = readingModes; protected readonly layoutModes = layoutModes; protected readonly bookWritingStyles = bookWritingStyles; - protected readonly pageLayoutModes = pageLayoutModes; protected readonly bookLayoutModes = bookLayoutModes; protected readonly pdfSpreadModes = pdfSpreadModes; protected readonly pdfThemes = pdfThemes; @@ -219,7 +214,6 @@ export class ManageUserPreferencesComponent implements OnInit { tap(prefs => { if (this.user) { this.user.preferences = {...prefs}; - this.reset(); this.cdRef.markForCheck(); } }) diff --git a/UI/Web/src/app/volume-detail/volume-detail.component.html b/UI/Web/src/app/volume-detail/volume-detail.component.html index b0d8b3b96..146102f52 100644 --- a/UI/Web/src/app/volume-detail/volume-detail.component.html +++ b/UI/Web/src/app/volume-detail/volume-detail.component.html @@ -6,33 +6,8 @@ @if (volume && series && libraryType !== null) {
-
- - @if(mobileSeriesImgBackground === 'true') { - - } @else { - - } - @if (volume.pagesRead < volume.pages && volume.pagesRead > 0) { -
- -
- @if (currentlyReadingChapter) { -
- {{t('continue-from', {title: ContinuePointTitle})}} -
- } - } -
-
- - -
- -
-
-
-
+
+
diff --git a/UI/Web/src/app/volume-detail/volume-detail.component.scss b/UI/Web/src/app/volume-detail/volume-detail.component.scss index b12ac592b..48a035b18 100644 --- a/UI/Web/src/app/volume-detail/volume-detail.component.scss +++ b/UI/Web/src/app/volume-detail/volume-detail.component.scss @@ -17,6 +17,7 @@ grid-template-columns: repeat(auto-fill, 160px); grid-gap: 0.5rem; justify-content: space-around; + padding-bottom: 20px; } diff --git a/UI/Web/src/app/volume-detail/volume-detail.component.ts b/UI/Web/src/app/volume-detail/volume-detail.component.ts index 35b61b762..3f090aafb 100644 --- a/UI/Web/src/app/volume-detail/volume-detail.component.ts +++ b/UI/Web/src/app/volume-detail/volume-detail.component.ts @@ -87,6 +87,7 @@ import {EditChapterModalComponent} from "../_single-module/edit-chapter-modal/ed import {BulkOperationsComponent} from "../cards/bulk-operations/bulk-operations.component"; import {DefaultDatePipe} from "../_pipes/default-date.pipe"; import {MangaFormatPipe} from "../_pipes/manga-format.pipe"; +import {CoverImageComponent} from "../_single-module/cover-image/cover-image.component"; enum TabID { @@ -129,47 +130,48 @@ interface VolumeCast extends IHasCast { @Component({ selector: 'app-volume-detail', standalone: true, - imports: [ - LoadingComponent, - NgbNavOutlet, - DetailsTabComponent, - NgbNavItem, - NgbNavLink, - NgbNavContent, - NgbNav, - ReadMoreComponent, - AsyncPipe, - NgbDropdownItem, - NgbDropdownMenu, - NgbDropdown, - NgbDropdownToggle, - ReadTimePipe, - AgeRatingPipe, - EntityTitleComponent, - RouterLink, - NgbProgressbar, - DecimalPipe, - NgbTooltip, - ImageComponent, - NgStyle, - NgClass, - TranslocoDirective, - CardItemComponent, - VirtualScrollerModule, - ChapterCardComponent, - DefaultValuePipe, - RelatedTabComponent, - AgeRatingImageComponent, - CompactNumberPipe, - BadgeExpanderComponent, - MetadataDetailRowComponent, - DownloadButtonComponent, - CardActionablesComponent, - BulkOperationsComponent, - DatePipe, - DefaultDatePipe, - MangaFormatPipe - ], + imports: [ + LoadingComponent, + NgbNavOutlet, + DetailsTabComponent, + NgbNavItem, + NgbNavLink, + NgbNavContent, + NgbNav, + ReadMoreComponent, + AsyncPipe, + NgbDropdownItem, + NgbDropdownMenu, + NgbDropdown, + NgbDropdownToggle, + ReadTimePipe, + AgeRatingPipe, + EntityTitleComponent, + RouterLink, + NgbProgressbar, + DecimalPipe, + NgbTooltip, + ImageComponent, + NgStyle, + NgClass, + TranslocoDirective, + CardItemComponent, + VirtualScrollerModule, + ChapterCardComponent, + DefaultValuePipe, + RelatedTabComponent, + AgeRatingImageComponent, + CompactNumberPipe, + BadgeExpanderComponent, + MetadataDetailRowComponent, + DownloadButtonComponent, + CardActionablesComponent, + BulkOperationsComponent, + DatePipe, + DefaultDatePipe, + MangaFormatPipe, + CoverImageComponent + ], templateUrl: './volume-detail.component.html', styleUrl: './volume-detail.component.scss', changeDetection: ChangeDetectionStrategy.OnPush @@ -310,8 +312,10 @@ export class VolumeDetailComponent implements OnInit { chapterLocaleKey = 'common.issue-num-shorthand'; break; case LibraryType.Book: - case LibraryType.Manga: case LibraryType.LightNovel: + chapterLocaleKey = 'common.book-num-shorthand'; + break; + case LibraryType.Manga: case LibraryType.Images: chapterLocaleKey = 'common.chapter-num-shorthand'; break; diff --git a/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.scss b/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.scss index 2cef43ce4..e0c5328d9 100644 --- a/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.scss +++ b/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.scss @@ -10,4 +10,39 @@ overflow-y: auto; position: relative; overscroll-behavior-y: none; + scrollbar-gutter: stable; + scrollbar-width: thin; + + // For firefox + @supports (-moz-appearance:none) { + scrollbar-color: transparent transparent; + scrollbar-width: thin; + } + + &::-webkit-scrollbar { + background-color: transparent; /*make scrollbar space invisible */ + width: inherit; + display: none; + visibility: hidden; + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: transparent; /*makes it invisible when not hovering*/ + } + + &:hover { + scrollbar-width: thin; + overflow-y: auto; + + // For firefox + @supports (-moz-appearance:none) { + scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0); + } + + &::-webkit-scrollbar-thumb { + visibility: visible; + background-color: rgba(255,255,255,0.3); /*On hover, it will turn grey*/ + } + } } \ No newline at end of file diff --git a/UI/Web/src/assets/langs/en.json b/UI/Web/src/assets/langs/en.json index 7826bcf96..923511cfe 100644 --- a/UI/Web/src/assets/langs/en.json +++ b/UI/Web/src/assets/langs/en.json @@ -9,7 +9,7 @@ }, "dashboard": { - "no-libraries": "There are no libraries setup yet. Configure some in", + "no-libraries": "There are no libraries setup yet. Create some in", "server-settings-link": "Server settings", "not-granted": "You haven't been granted access to any libraries.", "on-deck-title": "On Deck", @@ -246,7 +246,7 @@ "platform-label": "Platform", "email-label": "Email", "name-label": "Name", - "email-setup-alert": "Want to send files to your devices? Have the your admin setup Email settings first!", + "email-setup-alert": "Want to send files to your devices? Have your admin setup Email settings first!", "add": "{{common.add}}", "delete": "{{common.delete}}", "edit": "{{common.edit}}", @@ -288,7 +288,7 @@ "email-not-confirmed": "This email is not confirmed", "email-confirmed": "This email is confirmed", "email-updated-title": "Email Updated", - "email-updated-description": "You can use the following link below to confirm the email for your account. If your server is externally accessible, an email will have been sent to the email and the link can be used to confirm the email.", + "email-updated-description": "You can use the following link below to confirm the email for your account. If email settings have been configured, an email will have been sent and the link can be used to confirm the email.", "setup-user-account": "Setup user's account", "invite-url-label": "Invite Url", "invite-url-tooltip": "Copy this and paste in a new tab", @@ -404,11 +404,11 @@ }, "directory-picker": { - "title": "Choose a Directory", + "title": "Choose a Folder", "close": "{{common.close}}", "path-label": "Path", "path-placeholder": "Start typing or select path", - "instructions": "Select a folder to view breadcrumb. Don't see your directory? Try checking / first.", + "instructions": "Select a folder to enter it. Don't see your folder? Try checking / first.", "type-header": "Type", "name-header": "Name", "cancel": "{{common.cancel}}", @@ -574,7 +574,7 @@ "reset-password": { "title": "Password Reset", - "description": "Enter the email of your account. Kavita will send you an email if a valid one on file, otherwise ask the admin for the link from logs.", + "description": "Enter the email of your account. Kavita will send you an email if a valid one is on file, otherwise ask the admin to reset your password.", "email-label": "{{common.email}}", "required-field": "{{validation.required-field}}", "valid-email": "{{validation.valid-email}}", @@ -631,7 +631,7 @@ "email": "{{common.email}}", "required-field": "{{common.required-field}}", "setup-user-title": "User invited", - "setup-user-description": "You can use the following link below to setup the account for your user or use the copy button. You may need to log out before using the link to register a new user. If Kavita can determine your server as accessibile externally (or Host Name is set), an email will have been sent to the user and the links can be used by them to finish setting up their account. Otherwise, use the link below or in logs to manually send to them or setup their account.", + "setup-user-description": "You can use the following link below to setup the account for your user or use the copy button. You may need to log out before using the link to register a new user.", "setup-user-account": "Setup user's account", "setup-user-account-tooltip": "Copy this and paste in a new tab. You may need to log out.", "invite-url-label": "Invite Url", @@ -672,7 +672,7 @@ "discord-validation": "This is not a valid Discord User Id. Your user id is not your discord username.", "activate-delete": "{{common.delete}}", "activate-reset": "{{common.reset}}", - "activate-reset-tooltip": "Untie your license with this server. Requires both License and Email.", + "activate-reset-tooltip": "Invalidate an previous registration using your license. Requires both License and Email", "activate-save": "{{common.save}}", "kavita+-desc-part-1": "Kavita+ is a premium subscription service which unlocks features for all users on this Kavita instance. Buy a subscription to unlock ", @@ -918,7 +918,7 @@ "library-name-unique": "Library name must be unique", "last-scanned-label": "Last Scanned:", "type-label": "Type", - "type-tooltip": "Library type determines how filenames are parsed and if the UI shows Chapters (Manga) vs Issues (Comics). Book work the same way as Manga but have different naming in the UI.", + "type-tooltip": "Library type determines how filenames are parsed and if the UI shows Chapters (Manga) vs Issues (Comics). Check the wiki for more details on the differences between the library types.", "kavitaplus-eligible-label": "Kavita+ Eligible", "kavitaplus-eligible-tooltip": "Will Kavita+ pull information or support Scrobbling", "folder-description": "Add folders to your library", @@ -1055,7 +1055,7 @@ "related-tab": { "reading-lists-title": "{{reading-lists.title}}", - "collections-title": "{{side-nav.title}}", + "collections-title": "{{side-nav.collections}}", "relations-title": "{{tabs.related-tab}}" }, @@ -1113,7 +1113,7 @@ "manage-media-issues": { - "description-part-1": "This table contains issues found during scan or reading of your media. This list is non-managed. You can clear it at any time and use Library (Force) Scan to perform analysis. A list of some common errors and what they mean can be found on the ", + "description-part-1": "This table contains issues found during scan or reading of your media. You can clear it at any time and use Library (Force) Scan to perform analysis. A list of some common errors and what they mean can be found on the ", "description-part-2": "wiki.", "filter-label": "{{common.filter}}", "clear-alerts": "Clear Alerts", @@ -1124,7 +1124,7 @@ }, "manage-email-settings": { - "description": "In order to use some functions of Kavita like Forgot Password and Send To Device, an email provider must be setup. Other features like Password change are less secure without Email setup.", + "description": "In order to use some functions of Kavita like Send To Device an email provider must be setup. Other features like Forgot Password require admin intervention without Email setup.", "setting-description": "You must fill out both Host Name and SMTP settings to use email-based functionality within Kavita.", "test-warning": "You must save before using Test button.", "send-to-warning": "If you want Send to Device to work you must setup your email settings", @@ -1207,7 +1207,7 @@ "manage-scrobble-errors": { "title": "Scrobbling Errors", - "description": "This table contains issues found during scrobbling. This list is non-managed. You can clear it at any time and wait for the next scrobble upload to see. If there is an unknown series, you are best correcting the series name or localized series name or adding a weblink for the providers.", + "description": "This table contains issues found during scrobbling. You can clear it at any time and wait for the next scrobble upload to see. If there is an unknown series, you are best correcting the series name or localized series name or adding a weblink for the providers.", "filter-label": "{{common.filter}}", "clear-errors": "Clear Errors", "series-header": "Series", @@ -1644,7 +1644,7 @@ "close": "{{common.close}}", "title": "CBL Import", "import-description": "To get started, import a .cbl file. Kavita will perform multiple checks before importing. Some steps will block moving forward due to issues with the file.", - "validate-description": "All files have been validated to see if there are any operations to do on the list. Any lists have have failed will not move to the next step. Fix the CBL files and retry.", + "validate-description": "All files have been validated to see if there are any operations to do on the list. Any lists that have failed will not move to the next step. Fix the CBL files and retry.", "validate-warning": "There are issues with the CBL that will prevent an import. Correct these issues then try again.", "validate-no-issue-description": "No issues found with CBL, press next.", "dry-run-description": "This is a dry run and shows what will happen if you press Next and perform the import. All Failures will not be imported.", @@ -1860,7 +1860,7 @@ "added-title": "Added", "last-modified-title": "Last Modified", "view-files": "View Files", - "pages-title": "Pages", + "pages-title": "{{edit-chapter-modal.pages-label}}", "words-title": "Words", "chapter-title": "Chapter", "volume-num": "{{common.volume-num}}", @@ -1870,7 +1870,8 @@ "force-refresh-tooltip": "Force refresh external metadata from Kavita+", "loose-leaf-volume": "Loose Leaf Chapters", "specials-volume": "Specials", - "release-year-validation": "{{validation.year-validation}}" + "release-year-validation": "{{validation.year-validation}}", + "volumes-title": "{{tab.volumes-tab}}" }, "edit-chapter-modal": { @@ -1942,12 +1943,12 @@ "words-label": "{{edit-chapter-modal.length-title}}", "pages-count": "{{edit-chapter-modal.pages-count}}", "words-count": "{{edit-chapter-modal.words-count}}", - "reading-time-label": "{{edit-chapter-modal.reading-time-title}}", + "reading-time-label": "{{edit-chapter-modal.reading-time-label}}", "date-added-label": "{{edit-chapter-modal.date-added-title}}", - "size-label": "{{edit-chapter-modal.size-title}}", - "id-label": "{{edit-chapter-modal.id-title}}", - "links-label": "{{series-metadata-detail.links-title}}", - "last-read-label": "{{edit-chapter-modal.last-read-title}}", + "size-label": "{{edit-chapter-modal.size-label}}", + "id-label": "{{edit-chapter-modal.id-label}}", + "links-label": "{{series-metadata-detail.links-label}}", + "last-read-label": "{{edit-chapter-modal.last-read-label}}", "range-hours": "{{value}} {{hourWord}}", "read-time-label": "{{edit-chapter-modal.read-time-label}}", "files-label": "{{edit-chapter-modal.files-label}}", @@ -2072,10 +2073,10 @@ "unknown-crit": "There was an unknown critical error", "user-not-auth": "User is not authenticated", "error-code": "{{num}} Error", - "download": "There was an issue downloading this file or you do not have permissions", + "download": "There was an issue downloading this file or you do not have permission", "not-found": "That url does not exist", "generic": "Something unexpected went wrong", - "rejected-cover-upload": "The image could not be fetched due to server refusing request. Please download and upload from file instead.", + "rejected-cover-upload": "The image could not be fetched via the url. Please download and upload from file instead.", "invalid-confirmation-url": "Invalid confirmation url", "invalid-confirmation-email": "Invalid confirmation email", "invalid-password-reset-url": "Invalid reset password url", @@ -2278,7 +2279,7 @@ "k+-unlocked": "Kavita+ unlocked!", "k+-error": "There was an error when activating your license. Please try again.", "k+-delete-key": "This will only delete Kavita's license key and allow a buy link to show. This will not cancel your subscription! Use this only if directed by support!", - "k+-reset-key": "This will untie your key from a server and allow you to re-register a Kavita instance.", + "k+-reset-key": "This will invalidate a previous registration using your license and allow you to re-register a Kavita instance.", "k+-reset-key-success": "Your license has been un-registered. Use Edit button to re-register your instance and re-activate Kavita+", "library-deleted": "Library {{name}} has been removed", "copied-to-clipboard": "Copied to clipboard", @@ -2309,7 +2310,7 @@ "confirm-delete-volume": "Are you sure you want to delete this volume? It will not modify files on disk.", "alert-bad-theme": "There is invalid or unsafe css in the theme. Please reach out to your admin to have this corrected. Defaulting to dark theme.", "confirm-library-delete": "Are you sure you want to delete the {{name}} library? You cannot undo this action.", - "confirm-library-type-change": "Changing library type will trigger a new scan with different parsing rules and may lead to series being re-created and hence you may loose progress and bookmarks. You should backup before you do this. Are you sure you want to continue?", + "confirm-library-type-change": "Changing library type will trigger a new scan with different parsing rules and may lead to series being re-created and hence you may lose progress and bookmarks. You should backup before you do this. Are you sure you want to continue?", "confirm-download-size": "The {{entityType}} is {{size}}. Are you sure you want to continue?", "confirm-download-size-ios": "iOS has issues downloading files that are larger than 200MB, this download might not complete.", "list-doesnt-exist": "This list doesn't exist", @@ -2375,7 +2376,7 @@ "add-to-reading-list": "Add to Reading List", "add-to-reading-list-tooltip": "Add to a Reading List", "add-to-collection": "Add to Collection", - "add-to-collection-tooltip": "A series to a collection", + "add-to-collection-tooltip": "Add series to a collection", "send-to": "Send To", "delete": "Delete", "delete-tooltip": "There is no way to revert this decision", @@ -2522,6 +2523,7 @@ "chapter-num": "Chapter", "volume-num": "Volume", "chapter-num-shorthand": "Ch {{num}}", + "book-num-shorthand": "Book {{num}}", "issue-num-shorthand": "#{{num}}", "volume-num-shorthand": "Vol {{num}}", diff --git a/UI/Web/src/styles.scss b/UI/Web/src/styles.scss index 715abf5be..332cbf0c4 100644 --- a/UI/Web/src/styles.scss +++ b/UI/Web/src/styles.scss @@ -103,9 +103,11 @@ app-root { body { scrollbar-gutter: stable both-edges; font-family: 'Poppins', sans-serif; - overflow: hidden; + overflow: hidden; // When this is enabled, it will break the webtoon reader. The nav.service will automatically remove/apply on toggling them + scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0.1); + scrollbar-width: thin; - @media (max-width: 576px) { + @media (max-width: $grid-breakpoints-lg) { margin-top: var(--nav-mobile-offset) !important; height: calc(var(--vh)* 100 - var(--nav-mobile-offset)) !important; } diff --git a/UI/Web/src/theme/_variables.scss b/UI/Web/src/theme/_variables.scss index 611bfac8f..a8202989e 100644 --- a/UI/Web/src/theme/_variables.scss +++ b/UI/Web/src/theme/_variables.scss @@ -12,6 +12,8 @@ $form-select-indicator-color:#cecece; $accordion-icon-color: #cecece; $accordion-icon-active-color: #cecece; + + $grid-breakpoints-xs: 0; $grid-breakpoints-sm: 576px; $grid-breakpoints-md: 768px; diff --git a/UI/Web/src/theme/components/_sidenav.scss b/UI/Web/src/theme/components/_sidenav.scss index 8792f8c84..36558c05c 100644 --- a/UI/Web/src/theme/components/_sidenav.scss +++ b/UI/Web/src/theme/components/_sidenav.scss @@ -1,15 +1,26 @@ -.bottom { +@import '../variables'; + +.sidenav-bottom { position: absolute; - bottom: 12px; + bottom: 0; width: 190px; - background: var(--side-nav-bg-color); - border-radius: var(--side-nav-border-radius); font-size: 12px; transition: width var(--side-nav-openclose-transition); z-index: 999; left: 10px; + .donate { + .side-nav-item { + width: 100%; + padding: 0 80px; + + &:hover { + background-color: unset; + } + } + } + &.closed { width: 45px; overflow-x: hidden; @@ -18,7 +29,7 @@ } } -:host ::ng-deep .bottom .donate .side-nav-item { +:host ::ng-deep .sidenav-bottom .donate .side-nav-item { justify-content: center; min-height: 25px; align-items: center; @@ -28,35 +39,35 @@ } } -:host ::ng-deep .bottom .donate .side-nav-item span { +:host ::ng-deep .sidenav-bottom .donate .side-nav-item span { flex-grow: unset !important; min-width: unset !important;; } -:host ::ng-deep .bottom .donate .side-nav-item span div { +:host ::ng-deep .sidenav-bottom .donate .side-nav-item span div { min-width: unset !important; } -:host ::ng-deep .bottom .donate .side-nav-item span div i{ +:host ::ng-deep .sidenav-bottom .donate .side-nav-item span div i{ font-size: 12px !important; } -:host ::ng-deep .bottom .donate .side-nav-item.closed span.phone-hidden div { +:host ::ng-deep .sidenav-bottom .donate .side-nav-item.closed span.phone-hidden div { width: 100%; } -:host ::ng-deep .bottom .donate .side-nav-item.closed span.side-nav-text div { +:host ::ng-deep .sidenav-bottom .donate .side-nav-item.closed span.side-nav-text div { width: 0; } -@media (max-width: 768px) { - :host ::ng-deep .bottom .donate .side-nav-item.closed { +@media (max-width: $grid-breakpoints-lg) { + :host ::ng-deep .sidenav-bottom .donate .side-nav-item.closed { display: none; } - :host ::ng-deep .bottom .donate .side-nav-item span.phone-hidden { + :host ::ng-deep .sidenav-bottom .donate .side-nav-item span.phone-hidden { display: block !important; } } @@ -74,6 +85,10 @@ transition: width var(--side-nav-openclose-transition), background-color var(--side-nav-bg-color-transition), border-color var(--side-nav-border-transition); border: var(--side-nav-border); + &::-webkit-scrollbar { + visibility: hidden; + } + &.preference { height: calc((var(--vh)*100)); } @@ -90,31 +105,52 @@ &.closed { width: 55px; overflow-x: hidden; - overflow-y: auto; + overflow-y: hidden; background-color: var(--side-nav-closed-bg-color); border: var(--side-nav-border-closed); } .side-nav { - overflow: auto; + overflow-y: hidden; height: 100%; - mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%); - -webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%); + mask-image: linear-gradient(to bottom, transparent, black 0%, black 97%, transparent 100%); + -webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 97%, transparent 100%); + scrollbar-gutter: stable; + scrollbar-width: thin; - &::-webkit-scrollbar { - width: inherit; + // For firefox + @supports (-moz-appearance:none) { + scrollbar-color: transparent transparent; + scrollbar-width: thin; + } + + &::-webkit-scrollbar { + background-color: transparent; /*make scrollbar space invisible */ + width: inherit; + display: none; + visibility: hidden; + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: transparent; /*makes it invisible when not hovering*/ + } + + &:hover { + scrollbar-width: thin; + overflow-y: auto; + + // For firefox + @supports (-moz-appearance:none) { + scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0); } - + &::-webkit-scrollbar-thumb { - background-color: transparent; /*makes it invisible when not hovering*/ + visibility: visible; + background-color: rgba(255,255,255,0.3); /*On hover, it will turn grey*/ } - - &:hover { - &::-webkit-scrollbar-thumb { - background-color: rgba(255,255,255,0.3); /*On hover, it will turn grey*/ - } - } - + } + .side-nav-item:first { border-top-left-radius: var(--side-nav-border-radius); border-top-right-radius: var(--side-nav-border-radius); @@ -122,7 +158,7 @@ } } -@media (max-width: 768px) { +@media (max-width: $grid-breakpoints-lg) { .side-nav-container { padding: 10px 0; width: 55vw; @@ -155,7 +191,7 @@ } } - .bottom { + .sidenav-bottom { display:none; } diff --git a/UI/Web/src/theme/themes/dark.scss b/UI/Web/src/theme/themes/dark.scss index fa0a36538..53a3d16f5 100644 --- a/UI/Web/src/theme/themes/dark.scss +++ b/UI/Web/src/theme/themes/dark.scss @@ -49,7 +49,8 @@ --bs-body-bg: #1f2020; //#343a40; --body-text-color: #efefef; --btn-icon-filter: invert(1) grayscale(100%) brightness(200%); - --primary-color-scrollbar: rgba(74,198,148,0.75); + --primary-color-scrollbar: rgba(255,255,255,0.3); + --default-state-scrollbar: transparent; --text-muted-color: hsla(0,0%,100%,.45); /* New Color scheme */ @@ -81,7 +82,7 @@ --theme-color: #000000; --color-scheme: dark; --tile-color: var(--primary-color); - --nav-offset: 75px; + --nav-offset: 70px; --nav-mobile-offset: 55px; /* Setting Item */