From 193e9b1da95bbfcef57c57ac884af35bdac41b50 Mon Sep 17 00:00:00 2001 From: Fesaa <77553571+Fesaa@users.noreply.github.com> Date: Wed, 4 Jun 2025 09:45:10 +0200 Subject: [PATCH] A collection of bug fixes (#3820) Co-authored-by: Joseph Milazzo --- API/Controllers/SeriesController.cs | 4 +- API/Controllers/UsersController.cs | 1 + UI/Web/src/app/_services/account.service.ts | 11 +++++ .../actionable-modal.component.html | 2 +- .../actionable-modal.component.ts | 2 +- .../book-reader/book-reader.component.html | 15 +++--- .../book-reader/book-reader.component.scss | 6 +-- .../book-reader/book-reader.component.ts | 30 +++++++----- .../table-of-contents.component.html | 4 +- .../table-of-contents.component.scss | 3 +- .../table-of-contents.component.ts | 29 ++++++++++- .../cards/card-item/card-item.component.html | 2 +- .../cards/card-item/card-item.component.ts | 6 ++- .../all-collections.component.html | 2 +- .../all-collections.component.ts | 14 +++++- .../collection-detail.component.html | 2 +- .../collection-detail.component.ts | 14 +++++- .../manga-reader/manga-reader.component.ts | 26 ++++++---- .../grouped-typeahead.component.html | 2 +- .../reading-list-detail.component.html | 2 +- .../reading-list-detail.component.ts | 15 +++++- .../reading-lists.component.html | 2 +- .../reading-lists/reading-lists.component.ts | 20 ++++++-- .../series-detail/series-detail.component.ts | 16 +++++-- .../confirm-dialog/_models/confirm-config.ts | 4 +- .../confirm-dialog.component.html | 14 +++++- .../confirm-dialog.component.ts | 17 +++++-- UI/Web/src/app/shared/confirm.service.ts | 48 ++++++++++++++++--- .../change-password.component.html | 6 ++- .../change-password.component.ts | 3 +- .../manage-user-preferences.component.ts | 7 ++- UI/Web/src/assets/langs/en.json | 16 +++++-- UI/Web/src/styles.scss | 1 + UI/Web/src/theme/components/_buttons.scss | 6 +-- UI/Web/src/theme/components/_tooltip.scss | 4 ++ UI/Web/src/theme/themes/dark.scss | 1 - 36 files changed, 268 insertions(+), 89 deletions(-) create mode 100644 UI/Web/src/theme/components/_tooltip.scss diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs index 7cd897c32..84eacc838 100644 --- a/API/Controllers/SeriesController.cs +++ b/API/Controllers/SeriesController.cs @@ -310,7 +310,7 @@ public class SeriesController : BaseApiController /// /// /// - /// + /// This is not in use /// [HttpPost("all-v2")] public async Task>> GetAllSeriesV2(FilterV2Dto filterDto, [FromQuery] UserParams userParams, @@ -321,8 +321,6 @@ public class SeriesController : BaseApiController await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdV2Async(userId, userParams, filterDto, context); // Apply progress/rating information (I can't work out how to do this in initial query) - if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-series")); - await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index 944ea987b..e5cfb626a 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -128,6 +128,7 @@ public class UsersController : BaseApiController existingPreferences.PromptForDownloadSize = preferencesDto.PromptForDownloadSize; existingPreferences.NoTransitions = preferencesDto.NoTransitions; existingPreferences.SwipeToPaginate = preferencesDto.SwipeToPaginate; + existingPreferences.AllowAutomaticWebtoonReaderDetection = preferencesDto.AllowAutomaticWebtoonReaderDetection; existingPreferences.CollapseSeriesRelationships = preferencesDto.CollapseSeriesRelationships; existingPreferences.ShareReviews = preferencesDto.ShareReviews; diff --git a/UI/Web/src/app/_services/account.service.ts b/UI/Web/src/app/_services/account.service.ts index 6b8cdc243..8e8576069 100644 --- a/UI/Web/src/app/_services/account.service.ts +++ b/UI/Web/src/app/_services/account.service.ts @@ -102,11 +102,22 @@ export class AccountService { return true; } + /** + * If the user has any role in the restricted roles array or is an Admin + * @param user + * @param roles + * @param restrictedRoles + */ hasAnyRole(user: User, roles: Array, restrictedRoles: Array = []) { if (!user || !user.roles) { return false; } + // If the user is an admin, they have the role + if (this.hasAdminRole(user)) { + return true; + } + // If restricted roles are provided and the user has any of them, deny access if (restrictedRoles.length > 0 && restrictedRoles.some(role => user.roles.includes(role))) { return false; diff --git a/UI/Web/src/app/_single-module/actionable-modal/actionable-modal.component.html b/UI/Web/src/app/_single-module/actionable-modal/actionable-modal.component.html index caf8bf683..7573c554a 100644 --- a/UI/Web/src/app/_single-module/actionable-modal/actionable-modal.component.html +++ b/UI/Web/src/app/_single-module/actionable-modal/actionable-modal.component.html @@ -15,7 +15,7 @@
@for (action of currentItems; track action.title) { - @if (willRenderAction(action)) { + @if (willRenderAction(action, user!)) {
- + + @if (layoutMode !== BookPageLayoutMode.Default) { + @let vp = getVirtualPage();
{{t('page-label')}}
-
-
{{vp[0]}}
- +
-
{{vp[1]}}
+
{{vp[1]}}
-
- + }
{{t('pagination-header')}}
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 dcfa9ddcd..8f45302c3 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 @@ -277,9 +277,9 @@ $action-bar-height: 38px; } .virt-pagination-cont { - padding-bottom: 5px; - margin-bottom: 5px; - box-shadow: var(--drawer-pagination-horizontal-rule); + padding-bottom: 5px; + margin-bottom: 5px; + box-shadow: var(--drawer-pagination-horizontal-rule); } .bottom-bar { diff --git a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts index 6abd619f8..002d769e8 100644 --- a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts +++ b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts @@ -63,6 +63,7 @@ import { PersonalToCEvent } from "../personal-table-of-contents/personal-table-of-contents.component"; import {translate, TranslocoDirective} from "@jsverse/transloco"; +import {ConfirmService} from "../../../shared/confirm.service"; enum TabID { @@ -133,6 +134,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { private readonly utilityService = inject(UtilityService); private readonly libraryService = inject(LibraryService); private readonly themeService = inject(ThemeService); + private readonly confirmService = inject(ConfirmService); private readonly cdRef = inject(ChangeDetectorRef); protected readonly BookPageLayoutMode = BookPageLayoutMode; @@ -730,7 +732,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } @HostListener('window:keydown', ['$event']) - handleKeyPress(event: KeyboardEvent) { + async handleKeyPress(event: KeyboardEvent) { const activeElement = document.activeElement as HTMLElement; const isInputFocused = activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA'; if (isInputFocused) return; @@ -748,7 +750,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { event.stopPropagation(); event.preventDefault(); } else if (event.key === KEY_CODES.G) { - this.goToPage(); + await this.goToPage(); } else if (event.key === KEY_CODES.F) { this.toggleFullscreen() } @@ -905,33 +907,35 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } - promptForPage() { - const question = translate('book-reader.go-to-page-prompt', {totalPages: this.maxPages - 1}); - const goToPageNum = window.prompt(question, ''); + async promptForPage() { + const promptConfig = {...this.confirmService.defaultPrompt}; + // Pages are called sections in the UI, manga reader uses the go-to-page string so we use a different one here + promptConfig.header = translate('book-reader.go-to-section'); + promptConfig.content = translate('book-reader.go-to-section-prompt', {totalSections: this.maxPages - 1}); + + const goToPageNum = await this.confirmService.prompt(undefined, promptConfig); + if (goToPageNum === null || goToPageNum.trim().length === 0) { return null; } return goToPageNum; } - goToPage(pageNum?: number) { + async goToPage(pageNum?: number) { let page = pageNum; if (pageNum === null || pageNum === undefined) { - const goToPageNum = this.promptForPage(); + const goToPageNum = await this.promptForPage(); if (goToPageNum === null) { return; } + page = parseInt(goToPageNum.trim(), 10); } if (page === undefined || this.pageNum === page) { return; } - if (page > this.maxPages) { - page = this.maxPages; + if (page > this.maxPages - 1) { + page = this.maxPages - 1; } else if (page < 0) { page = 0; } - if (!(page === 0 || page === this.maxPages - 1)) { - page -= 1; - } - this.pageNum = page; this.loadPage(); } diff --git a/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.html b/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.html index 585f3af42..ead8b3540 100644 --- a/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.html +++ b/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.html @@ -17,12 +17,12 @@ } @else { @for (chapterGroup of chapters; track chapterGroup.title + chapterGroup.children.length) {
    -
  • +
  • {{chapterGroup.title}}
  • @for(chapter of chapterGroup.children; track chapter.title + chapter.part) { diff --git a/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.scss b/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.scss index e556f0e78..ca8e747f4 100644 --- a/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.scss +++ b/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.scss @@ -3,9 +3,10 @@ &.active { font-weight: bold; + color: var(--primary-color); } } .chapter-title { padding-inline-start: 1rem; -} \ No newline at end of file +} diff --git a/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.ts b/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.ts index cb6417874..ce3a180ed 100644 --- a/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.ts +++ b/UI/Web/src/app/book-reader/_components/table-of-contents/table-of-contents.component.ts @@ -31,9 +31,8 @@ export class TableOfContentsComponent implements OnChanges { @Output() loadChapter: EventEmitter<{pageNum: number, part: string}> = new EventEmitter(); ngOnChanges(changes: SimpleChanges) { - console.log('Current Page: ', this.pageNum, this.currentPageAnchor); + //console.log('Current Page: ', this.pageNum, this.currentPageAnchor); this.cdRef.markForCheck(); - } cleanIdSelector(id: string) { @@ -47,4 +46,30 @@ export class TableOfContentsComponent implements OnChanges { loadChapterPage(pageNum: number, part: string) { this.loadChapter.emit({pageNum, part}); } + + isChapterSelected(chapterGroup: BookChapterItem) { + if (chapterGroup.page === this.pageNum) { + return true; + } + + const idx = this.chapters.indexOf(chapterGroup); + if (idx < 0) { + return false; // should never happen + } + + const nextIdx = idx + 1; + // Last chapter + if (nextIdx >= this.chapters.length) { + return chapterGroup.page < this.pageNum; + } + + // Passed chapter, and next chapter has not been reached + const next = this.chapters[nextIdx]; + return chapterGroup.page < this.pageNum && next.page > this.pageNum; + } + + isAnchorSelected(chapter: BookChapterItem) { + return this.cleanIdSelector(chapter.part) === this.currentPageAnchor + } + } diff --git a/UI/Web/src/app/cards/card-item/card-item.component.html b/UI/Web/src/app/cards/card-item/card-item.component.html index dc89c563c..c0a4c0890 100644 --- a/UI/Web/src/app/cards/card-item/card-item.component.html +++ b/UI/Web/src/app/cards/card-item/card-item.component.html @@ -94,7 +94,7 @@ @if (actions && actions.length > 0) { - + }
diff --git a/UI/Web/src/app/cards/card-item/card-item.component.ts b/UI/Web/src/app/cards/card-item/card-item.component.ts index 6bdbcaf18..37de9ca13 100644 --- a/UI/Web/src/app/cards/card-item/card-item.component.ts +++ b/UI/Web/src/app/cards/card-item/card-item.component.ts @@ -26,7 +26,7 @@ import {Series} from 'src/app/_models/series'; import {User} from 'src/app/_models/user'; import {Volume} from 'src/app/_models/volume'; import {AccountService} from 'src/app/_services/account.service'; -import {Action, ActionFactoryService, ActionItem} from 'src/app/_services/action-factory.service'; +import {Action, ActionableEntity, ActionFactoryService, ActionItem} from 'src/app/_services/action-factory.service'; import {ImageService} from 'src/app/_services/image.service'; import {LibraryService} from 'src/app/_services/library.service'; import {EVENTS, MessageHubService} from 'src/app/_services/message-hub.service'; @@ -118,6 +118,10 @@ export class CardItemComponent implements OnInit { * This is the entity we are representing. It will be returned if an action is executed. */ @Input({required: true}) entity!: CardEntity; + /** + * An optional entity to be used in the action callback + */ + @Input() actionEntity: ActionableEntity | null = null; /** * If the entity is selected or not. */ diff --git a/UI/Web/src/app/collections/_components/all-collections/all-collections.component.html b/UI/Web/src/app/collections/_components/all-collections/all-collections.component.html index 09923b239..599d1c156 100644 --- a/UI/Web/src/app/collections/_components/all-collections/all-collections.component.html +++ b/UI/Web/src/app/collections/_components/all-collections/all-collections.component.html @@ -14,7 +14,7 @@ [trackByIdentity]="trackByIdentity" > - { if (!user) return; - this.collectionTagActions = this.actionFactoryService.getCollectionTagActions(this.handleCollectionActionCallback.bind(this)) + this.collectionTagActions = this.actionFactoryService.getCollectionTagActions( + this.handleCollectionActionCallback.bind(this), this.shouldRenderCollection.bind(this)) .filter(action => this.collectionService.actionListFilter(action, user)); this.cdRef.markForCheck(); }); } + shouldRenderCollection(action: ActionItem, entity: UserCollection, user: User) { + switch (action.action) { + case Action.Promote: + return !entity.promoted; + case Action.UnPromote: + return entity.promoted; + default: + return true; + } + } + loadCollection(item: UserCollection) { this.router.navigate(['collections', item.id]); } diff --git a/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.html b/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.html index 927316f99..1fad4b6e8 100644 --- a/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.html +++ b/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.html @@ -11,7 +11,7 @@ } -
{{t('item-count', {num: series.length})}}
+
{{t('item-count', {num: series.length})}}
} diff --git a/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.ts b/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.ts index ceb539718..d99626b64 100644 --- a/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.ts +++ b/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.ts @@ -207,7 +207,8 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked { this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => { if (!user) return; this.user = user; - this.collectionTagActions = this.actionFactoryService.getCollectionTagActions(this.handleCollectionActionCallback.bind(this)) + this.collectionTagActions = this.actionFactoryService.getCollectionTagActions( + this.handleCollectionActionCallback.bind(this), this.shouldRenderCollection.bind(this)) .filter(action => this.collectionService.actionListFilter(action, user)); this.cdRef.markForCheck(); }); @@ -225,6 +226,17 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked { }); } + shouldRenderCollection(action: ActionItem, entity: UserCollection, user: User) { + switch (action.action) { + case Action.Promote: + return !entity.promoted; + case Action.UnPromote: + return entity.promoted; + default: + return true; + } + } + ngAfterContentChecked(): void { this.scrollService.setScrollContainer(this.scrollingBlock); } diff --git a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts index 595ae6079..a7bbe2d90 100644 --- a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts +++ b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts @@ -70,6 +70,7 @@ import {LoadingComponent} from '../../../shared/loading/loading.component'; import {translate, TranslocoDirective} from "@jsverse/transloco"; import {shareReplay} from "rxjs/operators"; import {DblClickDirective} from "../../../_directives/dbl-click.directive"; +import {ConfirmService} from "../../../shared/confirm.service"; const PREFETCH_PAGES = 10; @@ -150,9 +151,11 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { private readonly modalService = inject(NgbModal); private readonly cdRef = inject(ChangeDetectorRef); private readonly toastr = inject(ToastrService); - public readonly readerService = inject(ReaderService); - public readonly utilityService = inject(UtilityService); - public readonly mangaReaderService = inject(MangaReaderService); + private readonly confirmService = inject(ConfirmService); + protected readonly readerService = inject(ReaderService); + protected readonly utilityService = inject(UtilityService); + protected readonly mangaReaderService = inject(MangaReaderService); + protected readonly KeyDirection = KeyDirection; protected readonly ReaderMode = ReaderMode; @@ -647,7 +650,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { } @HostListener('window:keyup', ['$event']) - handleKeyPress(event: KeyboardEvent) { + async handleKeyPress(event: KeyboardEvent) { switch (this.readerMode) { case ReaderMode.LeftRight: if (event.key === KEY_CODES.RIGHT_ARROW) { @@ -682,7 +685,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { } else if (event.key === KEY_CODES.SPACE) { this.toggleMenu(); } else if (event.key === KEY_CODES.G) { - const goToPageNum = this.promptForPage(); + const goToPageNum = await this.promptForPage(); if (goToPageNum === null) { return; } this.goToPage(parseInt(goToPageNum.trim(), 10)); } else if (event.key === KEY_CODES.B) { @@ -1593,9 +1596,16 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { } // This is menu only code - promptForPage() { - const question = translate('book-reader.go-to-page-prompt', {totalPages: this.maxPages}); - const goToPageNum = window.prompt(question, ''); + async promptForPage() { + // const question = translate('book-reader.go-to-page-prompt', {totalPages: this.maxPages}); + // const goToPageNum = window.prompt(question, ''); + + const promptConfig = {...this.confirmService.defaultPrompt}; + promptConfig.header = translate('book-reader.go-to-page'); + promptConfig.content = translate('book-reader.go-to-page-prompt', {totalPages: this.maxPages}); + + const goToPageNum = await this.confirmService.prompt(undefined, promptConfig); + if (goToPageNum === null || goToPageNum.trim().length === 0) { return null; } return goToPageNum; } diff --git a/UI/Web/src/app/nav/_components/grouped-typeahead/grouped-typeahead.component.html b/UI/Web/src/app/nav/_components/grouped-typeahead/grouped-typeahead.component.html index 6f36e9b5a..4a51435fc 100644 --- a/UI/Web/src/app/nav/_components/grouped-typeahead/grouped-typeahead.component.html +++ b/UI/Web/src/app/nav/_components/grouped-typeahead/grouped-typeahead.component.html @@ -16,7 +16,7 @@ } } @else { -
+
Ctrl+K
} diff --git a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html index 9f45cd55a..1d1ce4c7e 100644 --- a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html +++ b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html @@ -83,7 +83,7 @@ } -
+
diff --git a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts index 511811fe8..6e8e3b22a 100644 --- a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts +++ b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts @@ -58,6 +58,7 @@ import {DefaultValuePipe} from "../../../_pipes/default-value.pipe"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {DetailsTabComponent} from "../../../_single-module/details-tab/details-tab.component"; import {IHasCast} from "../../../_models/common/i-has-cast"; +import {User} from "../../../_models/user"; enum TabID { Storyline = 'storyline-tab', @@ -251,7 +252,8 @@ export class ReadingListDetailComponent implements OnInit { if (user) { this.isAdmin = this.accountService.hasAdminRole(user); - this.actions = this.actionFactoryService.getReadingListActions(this.handleReadingListActionCallback.bind(this)) + this.actions = this.actionFactoryService + .getReadingListActions(this.handleReadingListActionCallback.bind(this), this.shouldRenderReadingListAction.bind(this)) .filter(action => this.readingListService.actionListFilter(action, readingList, this.isAdmin)); this.isOwnedReadingList = this.actions.filter(a => a.action === Action.Edit).length > 0; this.cdRef.markForCheck(); @@ -307,6 +309,17 @@ export class ReadingListDetailComponent implements OnInit { } } + shouldRenderReadingListAction(action: ActionItem, entity: ReadingList, user: User) { + switch (action.action) { + case Action.Promote: + return !entity.promoted; + case Action.UnPromote: + return entity.promoted; + default: + return true; + } + } + editReadingList(readingList: ReadingList) { this.actionService.editReadingList(readingList, (readingList: ReadingList) => { // Reload information around list diff --git a/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.html b/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.html index dd7dcab9a..a66ec008f 100644 --- a/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.html +++ b/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.html @@ -21,7 +21,7 @@ [trackByIdentity]="trackByIdentity" > - this.readingListService.actionListFilter(action, readingList, this.isAdmin || this.hasPromote)); - - return this.actionFactoryService.getReadingListActions(this.handleReadingListActionCallback.bind(this)) + return this.actionFactoryService + .getReadingListActions(this.handleReadingListActionCallback.bind(this), this.shouldRenderReadingListAction.bind(this)) .filter(action => this.readingListService.actionListFilter(action, readingList, this.isAdmin || this.hasPromote)); } @@ -172,4 +171,15 @@ export class ReadingListsComponent implements OnInit { break; } } + + shouldRenderReadingListAction(action: ActionItem, entity: ReadingList, user: User) { + switch (action.action) { + case Action.Promote: + return !entity.promoted; + case Action.UnPromote: + return entity.promoted; + default: + return true; + } + } } 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 05580bed0..6353664f3 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 @@ -551,7 +551,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { this.location.replaceState(newUrl) } - handleSeriesActionCallback(action: ActionItem, series: Series) { + async handleSeriesActionCallback(action: ActionItem, series: Series) { this.cdRef.markForCheck(); switch(action.action) { case(Action.MarkAsRead): @@ -565,16 +565,16 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { }); break; case(Action.Scan): - this.actionService.scanSeries(series); + await this.actionService.scanSeries(series); break; case(Action.RefreshMetadata): - this.actionService.refreshSeriesMetadata(series, undefined, true, false); + await this.actionService.refreshSeriesMetadata(series, undefined, true, false); break; case(Action.GenerateColorScape): - this.actionService.refreshSeriesMetadata(series, undefined, false, true); + await this.actionService.refreshSeriesMetadata(series, undefined, false, true); break; case(Action.Delete): - this.deleteSeries(series); + await this.deleteSeries(series); break; case(Action.AddToReadingList): this.actionService.addSeriesToReadingList(series); @@ -645,6 +645,9 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { this.actionService.sendToDevice(volume.chapters.map(c => c.id), device); break; } + case (Action.Download): + this.downloadService.download('volume', volume); + break; default: break; } @@ -679,6 +682,9 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { this.cdRef.markForCheck(); }); break; + case (Action.Download): + this.downloadService.download('chapter', chapter); + break; default: break; } diff --git a/UI/Web/src/app/shared/confirm-dialog/_models/confirm-config.ts b/UI/Web/src/app/shared/confirm-dialog/_models/confirm-config.ts index 481c9b48c..7cfd257e2 100644 --- a/UI/Web/src/app/shared/confirm-dialog/_models/confirm-config.ts +++ b/UI/Web/src/app/shared/confirm-dialog/_models/confirm-config.ts @@ -1,7 +1,7 @@ -import { ConfirmButton } from './confirm-button'; +import {ConfirmButton} from './confirm-button'; export class ConfirmConfig { - _type: 'confirm' | 'alert' | 'info' = 'confirm'; + _type: 'confirm' | 'alert' | 'info' | 'prompt' = 'confirm'; header: string = 'Confirm'; content: string = ''; buttons: Array = []; diff --git a/UI/Web/src/app/shared/confirm-dialog/confirm-dialog.component.html b/UI/Web/src/app/shared/confirm-dialog/confirm-dialog.component.html index 21b741cd3..213c80ceb 100644 --- a/UI/Web/src/app/shared/confirm-dialog/confirm-dialog.component.html +++ b/UI/Web/src/app/shared/confirm-dialog/confirm-dialog.component.html @@ -5,8 +5,18 @@ }
- + + @if (config._type === 'prompt') { + + } @else { + + } +