Bulk Operations (#596)

* Implemented the ability to perform multi-selections on cards. Basic selection code is done, CSS needed and exposing actions.

* Implemented a bulk selection bar. Added logic to the card on when to force show checkboxes.

* Fixed some bad parsing groups and cases for Comic Chapters.

* Hooked up some bulk actions on series detail page. Not hooked up to backend yet.

* Fixes #593. URI Enocde library names as sometimes they can have & in them.

* Implemented the ability to mark volume/chapters as read/unread.

* Hooked up mark as unread with specials as well.

* Add to reading list hooked up for Series Detail

* Implemented ability to add multiple series to a reading list.

* Implemented bulk selection for series cards

* Added comments to the new code in ReaderService.cs

* Implemented proper styling on bulk operation bar and integrated for collections.

* Fixed an issue with shift clicking

* Cleaned up css of bulk operations bar

* Code cleanup
This commit is contained in:
Joseph Milazzo 2021-09-24 17:27:47 -07:00 committed by GitHub
parent 52c4285168
commit f5229fd0e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1129 additions and 172 deletions

View file

@ -11,14 +11,6 @@
</h2>
</div>
<div class="row no-gutters mt-2 mb-2">
<!-- <div>
<button class="btn btn-primary" (click)="read()" [disabled]="isLoading">
<span>
<i class="fa fa-book-open"></i>
</span>
<span class="read-btn--text">&nbsp;Read</span>
</button>
</div> -->
<div class="ml-2" *ngIf="isAdmin">
<button class="btn btn-secondary" (click)="openEditCollectionTagModal(collectionTag)" title="Edit Series information">
<span>
@ -33,6 +25,7 @@
</div>
</div>
<hr>
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
<app-card-detail-layout
header="Series"
@ -44,7 +37,9 @@
(applyFilter)="updateFilter($event)"
>
<ng-template #cardItem let-item let-position="idx">
<app-series-card [data]="item" [libraryId]="item.libraryId" (reload)="loadPage()"></app-series-card>
<app-series-card [data]="item" [libraryId]="item.libraryId" (reload)="loadPage()"
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)" [selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"
></app-series-card>
</ng-template>
</app-card-detail-layout>

View file

@ -1,19 +1,20 @@
import { Component, OnInit } from '@angular/core';
import { Component, HostListener, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Router, ActivatedRoute } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { take } from 'rxjs/operators';
import { BulkSelectionService } from 'src/app/cards/bulk-selection.service';
import { UpdateFilterEvent } from 'src/app/cards/card-detail-layout/card-detail-layout.component';
import { EditCollectionTagsComponent } from 'src/app/cards/_modals/edit-collection-tags/edit-collection-tags.component';
import { UtilityService } from 'src/app/shared/_services/utility.service';
import { KEY_CODES } from 'src/app/shared/_services/utility.service';
import { CollectionTag } from 'src/app/_models/collection-tag';
import { MangaFormat } from 'src/app/_models/manga-format';
import { Pagination } from 'src/app/_models/pagination';
import { Series } from 'src/app/_models/series';
import { FilterItem, mangaFormatFilters, SeriesFilter } from 'src/app/_models/series-filter';
import { AccountService } from 'src/app/_services/account.service';
import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/action-factory.service';
import { ActionService } from 'src/app/_services/action.service';
import { CollectionTagService } from 'src/app/_services/collection-tag.service';
import { ImageService } from 'src/app/_services/image.service';
import { SeriesService } from 'src/app/_services/series.service';
@ -39,9 +40,31 @@ export class CollectionDetailComponent implements OnInit {
mangaFormat: null
};
bulkActionCallback = (action: Action, data: any) => {
const selectedSeriesIndexies = this.bulkSelectionService.getSelectedCardsForSource('series');
const selectedSeries = this.series.filter((series, index: number) => selectedSeriesIndexies.includes(index + ''));
switch (action) {
case Action.AddToReadingList:
this.actionService.addMultipleSeriesToReadingList(selectedSeries);
break;
case Action.MarkAsRead:
this.actionService.markMultipleSeriesAsRead(selectedSeries, () => {
this.loadPage();
});
break;
case Action.MarkAsUnread:
this.actionService.markMultipleSeriesAsUnread(selectedSeries, () => {
this.loadPage();
});
break;
}
}
constructor(public imageService: ImageService, private collectionService: CollectionTagService, private router: Router, private route: ActivatedRoute,
private seriesService: SeriesService, private toastr: ToastrService, private actionFactoryService: ActionFactoryService,
private modalService: NgbModal, private titleService: Title, private accountService: AccountService, private utilityService: UtilityService) {
private modalService: NgbModal, private titleService: Title, private accountService: AccountService,
public bulkSelectionService: BulkSelectionService, private actionService: ActionService) {
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
@ -63,6 +86,20 @@ export class CollectionDetailComponent implements OnInit {
this.collectionTagActions = this.actionFactoryService.getCollectionTagActions(this.handleCollectionActionCallback.bind(this));
}
@HostListener('document:keydown.shift', ['$event'])
handleKeypress(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = true;
}
}
@HostListener('document:keyup.shift', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = false;
}
}
updateTag(tagId: number) {
this.collectionService.allTags().subscribe(tags => {
this.collections = tags;