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:
parent
52c4285168
commit
f5229fd0e6
38 changed files with 1129 additions and 172 deletions
|
|
@ -39,7 +39,4 @@ export class CardActionablesComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Insert hr to separate admin actions
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="card">
|
||||
<div class="overlay" (click)="handleClick()">
|
||||
<div class="overlay" (click)="handleClick($event)">
|
||||
<img *ngIf="total > 0 || supressArchiveWarning" class="img-top lazyload" [src]="imageService.placeholderImage" [attr.data-src]="imageUrl"
|
||||
(error)="imageService.updateErroredImage($event)" aria-hidden="true" height="230px" width="158px">
|
||||
<img *ngIf="total === 0 && !supressArchiveWarning" class="img-top lazyload" [src]="imageService.errorImage" [attr.data-src]="imageUrl"
|
||||
|
|
@ -17,20 +17,22 @@
|
|||
<div class="error-banner" *ngIf="total === 0 && !supressArchiveWarning">
|
||||
Cannot Read
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="not-read-badge" *ngIf="read === 0 && total > 0"></div>
|
||||
<div class="bulk-mode {{bulkSelectionService.hasSelections() ? 'always-show' : ''}}" (click)="handleSelection($event)" *ngIf="allowSelection">
|
||||
<input type="checkbox" attr.aria-labelledby="{{title}}_{{entity.id}}" [ngModel]="selected" [ngModelOptions]="{standalone: true}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body" *ngIf="title.length > 0 || actions.length > 0">
|
||||
<div>
|
||||
<span class="card-title" placement="top" ngbTooltip="{{title}}" (click)="handleClick()" tabindex="0">
|
||||
<span class="card-title" placement="top" id="{{title}}_{{entity.id}}" ngbTooltip="{{title}}" (click)="handleClick()" tabindex="0">
|
||||
<span *ngIf="isPromoted()">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true"></i>
|
||||
<span class="sr-only">(promoted)</span>
|
||||
</span>
|
||||
<i class="fa {{utilityService.mangaFormatIcon(format)}}" aria-hidden="true" *ngIf="format != MangaFormat.UNKNOWN" title="{{utilityService.mangaFormat(format)}}"></i><span class="sr-only">{{utilityService.mangaFormat(format)}}</span>
|
||||
{{title}}
|
||||
<span class="sr-only">(promoted)</span>
|
||||
</span>
|
||||
<span class="card-actions float-right">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="title"></app-card-actionables>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ $image-width: 160px;
|
|||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.img-top {
|
||||
height: $image-height;
|
||||
}
|
||||
|
|
@ -71,12 +73,36 @@ $image-width: 160px;
|
|||
border-color: transparent $primary-color transparent transparent;
|
||||
}
|
||||
|
||||
|
||||
.bulk-mode {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
visibility: hidden;
|
||||
|
||||
&.always-show {
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.overlay {
|
||||
height: $image-height;
|
||||
|
||||
|
||||
|
||||
&:hover {
|
||||
visibility: visible;
|
||||
|
||||
.bulk-mode {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.overlay-item {
|
||||
visibility: visible;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { Volume } from 'src/app/_models/volume';
|
|||
import { Action, 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 { BulkSelectionService } from '../bulk-selection.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-card-item',
|
||||
|
|
@ -21,23 +22,71 @@ import { LibraryService } from 'src/app/_services/library.service';
|
|||
})
|
||||
export class CardItemComponent implements OnInit, OnDestroy {
|
||||
|
||||
/**
|
||||
* Card item url. Will internally handle error and missing covers
|
||||
*/
|
||||
@Input() imageUrl = '';
|
||||
/**
|
||||
* Name of the card
|
||||
*/
|
||||
@Input() title = '';
|
||||
/**
|
||||
* Any actions to perform on the card
|
||||
*/
|
||||
@Input() actions: ActionItem<any>[] = [];
|
||||
@Input() read = 0; // Pages read
|
||||
@Input() total = 0; // Total Pages
|
||||
/**
|
||||
* Pages Read
|
||||
*/
|
||||
@Input() read = 0;
|
||||
/**
|
||||
* Total Pages
|
||||
*/
|
||||
@Input() total = 0;
|
||||
/**
|
||||
* Supress library link
|
||||
*/
|
||||
@Input() supressLibraryLink = false;
|
||||
@Input() entity!: Series | Volume | Chapter | CollectionTag; // This is the entity we are representing. It will be returned if an action is executed.
|
||||
/**
|
||||
* This is the entity we are representing. It will be returned if an action is executed.
|
||||
*/
|
||||
@Input() entity!: Series | Volume | Chapter | CollectionTag;
|
||||
/**
|
||||
* If the entity is selected or not.
|
||||
*/
|
||||
@Input() selected: boolean = false;
|
||||
/**
|
||||
* If the entity should show selection code
|
||||
*/
|
||||
@Input() allowSelection: boolean = false;
|
||||
/**
|
||||
* Event emitted when item is clicked
|
||||
*/
|
||||
@Output() clicked = new EventEmitter<string>();
|
||||
|
||||
libraryName: string | undefined = undefined; // Library name item belongs to
|
||||
/**
|
||||
* When the card is selected.
|
||||
*/
|
||||
@Output() selection = new EventEmitter<boolean>();
|
||||
/**
|
||||
* Library name item belongs to
|
||||
*/
|
||||
libraryName: string | undefined = undefined;
|
||||
libraryId: number | undefined = undefined;
|
||||
supressArchiveWarning: boolean = false; // This will supress the cannot read archive warning when total pages is 0
|
||||
/**
|
||||
* This will supress the cannot read archive warning when total pages is 0
|
||||
*/
|
||||
supressArchiveWarning: boolean = false;
|
||||
/**
|
||||
* Format of the entity (only applies to Series)
|
||||
*/
|
||||
format: MangaFormat = MangaFormat.UNKNOWN;
|
||||
|
||||
|
||||
download$: Observable<Download> | null = null;
|
||||
downloadInProgress: boolean = false;
|
||||
|
||||
isShiftDown: boolean = false;
|
||||
|
||||
|
||||
get MangaFormat(): typeof MangaFormat {
|
||||
return MangaFormat;
|
||||
}
|
||||
|
|
@ -46,7 +95,7 @@ export class CardItemComponent implements OnInit, OnDestroy {
|
|||
|
||||
constructor(public imageService: ImageService, private libraryService: LibraryService,
|
||||
public utilityService: UtilityService, private downloadService: DownloadService,
|
||||
private toastr: ToastrService) {}
|
||||
private toastr: ToastrService, public bulkSelectionService: BulkSelectionService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.entity.hasOwnProperty('promoted') && this.entity.hasOwnProperty('title')) {
|
||||
|
|
@ -69,7 +118,7 @@ export class CardItemComponent implements OnInit, OnDestroy {
|
|||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
handleClick(event?: any) {
|
||||
this.clicked.emit(this.title);
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +195,14 @@ export class CardItemComponent implements OnInit, OnDestroy {
|
|||
|
||||
isPromoted() {
|
||||
const tag = this.entity as CollectionTag;
|
||||
// TODO: Validate if this works with reading lists
|
||||
return tag.hasOwnProperty('promoted') && tag.promoted;
|
||||
}
|
||||
|
||||
|
||||
handleSelection(event?: any) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.selection.emit(this.selected);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue