Send To Device Support (#1557)
* Tweaked the logging output * Started implementing some basic idea for devices * Updated Email Service with new API routes * Implemented basic DB structure and some APIs to prep for the UI and flows. * Added an abstract class to make Unit testing easier. * Removed dependency we don't need * Updated the UI to be able to show devices and add new devices. Email field will update the platform if the user hasn't interacted with it already. * Added ability to delete a device as well * Basic ability to send files to devices works * Refactored Action code to pass ActionItem back and allow for dynamic children based on an Observable (api). Hooked in ability to send a chapter to a device. There is no logic in the FE to validate type. * Fixed a broken unit test * Implemented the ability to edit a device * Code cleanup * Fixed a bad success message * Fixed broken unit test from updating mock layer
This commit is contained in:
parent
ab0f13ef74
commit
9d7476a367
79 changed files with 3026 additions and 157 deletions
|
|
@ -11,7 +11,7 @@ import { BulkSelectionService } from '../bulk-selection.service';
|
|||
})
|
||||
export class BulkOperationsComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input() actionCallback!: (action: Action, data: any) => void;
|
||||
@Input() actionCallback!: (action: ActionItem<any>, data: any) => void;
|
||||
|
||||
topOffset: number = 56;
|
||||
hasMarkAsRead: boolean = false;
|
||||
|
|
@ -41,13 +41,13 @@ export class BulkOperationsComponent implements OnInit, OnDestroy {
|
|||
this.onDestory.complete();
|
||||
}
|
||||
|
||||
handleActionCallback(action: Action, data: any) {
|
||||
handleActionCallback(action: ActionItem<any>, data: any) {
|
||||
this.actionCallback(action, data);
|
||||
}
|
||||
|
||||
performAction(action: ActionItem<any>) {
|
||||
if (typeof action.callback === 'function') {
|
||||
action.callback(action.action, null);
|
||||
action.callback(action, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ export class BulkSelectionService {
|
|||
return ret;
|
||||
}
|
||||
|
||||
getActions(callback: (action: Action, data: any) => void) {
|
||||
getActions(callback: (action: ActionItem<any>, data: any) => void) {
|
||||
// checks if series is present. If so, returns only series actions
|
||||
// else returns volume/chapter items
|
||||
const allowedActions = [Action.AddToReadingList, Action.MarkAsRead, Action.MarkAsUnread, Action.AddToCollection, Action.Delete, Action.AddToWantToReadList, Action.RemoveFromWantToReadList];
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { DownloadService } from 'src/app/shared/_services/download.service';
|
|||
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { Chapter } from 'src/app/_models/chapter';
|
||||
import { ChapterMetadata } from 'src/app/_models/chapter-metadata';
|
||||
import { Device } from 'src/app/_models/device/device';
|
||||
import { LibraryType } from 'src/app/_models/library';
|
||||
import { MangaFile } from 'src/app/_models/manga-file';
|
||||
import { MangaFormat } from 'src/app/_models/manga-format';
|
||||
|
|
@ -16,6 +17,7 @@ import { Volume } from 'src/app/_models/volume';
|
|||
import { AccountService } from 'src/app/_services/account.service';
|
||||
import { ActionItem, ActionFactoryService, Action } from 'src/app/_services/action-factory.service';
|
||||
import { ActionService } from 'src/app/_services/action.service';
|
||||
import { DeviceService } from 'src/app/_services/device.service';
|
||||
import { ImageService } from 'src/app/_services/image.service';
|
||||
import { LibraryService } from 'src/app/_services/library.service';
|
||||
import { MetadataService } from 'src/app/_services/metadata.service';
|
||||
|
|
@ -100,7 +102,8 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
|
|||
private accountService: AccountService, private actionFactoryService: ActionFactoryService,
|
||||
private actionService: ActionService, private router: Router, private libraryService: LibraryService,
|
||||
private seriesService: SeriesService, private readerService: ReaderService, public metadataService: MetadataService,
|
||||
public activeOffcanvas: NgbActiveOffcanvas, private downloadService: DownloadService, private readonly cdRef: ChangeDetectorRef) {
|
||||
public activeOffcanvas: NgbActiveOffcanvas, private downloadService: DownloadService, private readonly cdRef: ChangeDetectorRef,
|
||||
private deviceSerivce: DeviceService) {
|
||||
this.isAdmin$ = this.accountService.currentUser$.pipe(
|
||||
takeUntil(this.onDestroy),
|
||||
map(user => (user && this.accountService.hasAdminRole(user)) || false),
|
||||
|
|
@ -166,7 +169,7 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
|
|||
|
||||
performAction(action: ActionItem<any>, chapter: Chapter) {
|
||||
if (typeof action.callback === 'function') {
|
||||
action.callback(action.action, chapter);
|
||||
action.callback(action, chapter);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -196,8 +199,8 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
|
|||
this.actionService.markChapterAsUnread(this.seriesId, chapter, () => { this.cdRef.markForCheck(); });
|
||||
}
|
||||
|
||||
handleChapterActionCallback(action: Action, chapter: Chapter) {
|
||||
switch (action) {
|
||||
handleChapterActionCallback(action: ActionItem<Chapter>, chapter: Chapter) {
|
||||
switch (action.action) {
|
||||
case(Action.MarkAsRead):
|
||||
this.markChapterAsRead(chapter);
|
||||
break;
|
||||
|
|
@ -216,6 +219,14 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
|
|||
case (Action.Read):
|
||||
this.readChapter(chapter, false);
|
||||
break;
|
||||
case (Action.SendTo):
|
||||
{
|
||||
const device = (action._extra.data as Device);
|
||||
this.deviceSerivce.sendTo(chapter.id, device.id).subscribe(() => {
|
||||
this.toastr.success('File emailed to ' + device.name);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges {
|
|||
|
||||
performAction(action: ActionItem<any>) {
|
||||
if (typeof action.callback === 'function') {
|
||||
action.callback(action.action, undefined);
|
||||
action.callback(action, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<ng-container *ngIf="actions.length > 0">
|
||||
<div ngbDropdown container="body" class="d-inline-block">
|
||||
<button [disabled]="disabled" class="btn {{btnClass}}" id="actions-{{labelBy}}" ngbDropdownToggle (click)="preventClick($event)"><i class="fa {{iconClass}}" aria-hidden="true"></i></button>
|
||||
<button [disabled]="disabled" class="btn {{btnClass}}" id="actions-{{labelBy}}" ngbDropdownToggle (click)="preventEvent($event)"><i class="fa {{iconClass}}" aria-hidden="true"></i></button>
|
||||
<div ngbDropdownMenu attr.aria-labelledby="actions-{{labelBy}}">
|
||||
<ng-container *ngTemplateOutlet="submenu; context: { list: actions }"></ng-container>
|
||||
</div>
|
||||
|
|
@ -8,10 +8,17 @@
|
|||
<ng-template #submenu let-list="list">
|
||||
<ng-container *ngFor="let action of list">
|
||||
<ng-container *ngIf="action.children === undefined || action?.children?.length === 0 else submenuDropdown">
|
||||
<button ngbDropdownItem *ngIf="willRenderAction(action)" (click)="performAction($event, action)">{{action.title}}</button>
|
||||
<ng-container *ngIf="action.dynamicList != undefined; else justItem">
|
||||
<ng-container *ngFor="let dynamicItem of (action.dynamicList | async)">
|
||||
<button ngbDropdownItem (click)="performDynamicClick($event, action, dynamicItem)">{{dynamicItem.title}}</button>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-template #justItem>
|
||||
<button ngbDropdownItem *ngIf="willRenderAction(action)" (click)="performAction($event, action)">{{action.title}}</button>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #submenuDropdown>
|
||||
<div ngbDropdown #subMenuHover="ngbDropdown" placement="right" (mouseover)="preventClick($event); openSubmenu(action.title, subMenuHover)" (mouseleave)="preventClick($event)">
|
||||
<div ngbDropdown #subMenuHover="ngbDropdown" placement="right" (mouseover)="preventEvent($event); openSubmenu(action.title, subMenuHover)" (mouseleave)="preventEvent($event)">
|
||||
<button id="actions-{{action.title}}" class="submenu-toggle" ngbDropdownToggle>{{action.title}} <i class="fa-solid fa-angle-right submenu-icon"></i></button>
|
||||
<div ngbDropdownMenu attr.aria-labelledby="actions-{{action.title}}">
|
||||
<ng-container *ngTemplateOutlet="submenu; context: { list: action.children }"></ng-container>
|
||||
|
|
|
|||
|
|
@ -34,13 +34,13 @@ export class CardActionablesComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
preventClick(event: any) {
|
||||
preventEvent(event: any) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
performAction(event: any, action: ActionItem<any>) {
|
||||
this.preventClick(event);
|
||||
this.preventEvent(event);
|
||||
|
||||
if (typeof action.callback === 'function') {
|
||||
this.actionHandler.emit(action);
|
||||
|
|
@ -66,4 +66,9 @@ export class CardActionablesComponent implements OnInit {
|
|||
subMenu.open();
|
||||
}
|
||||
|
||||
performDynamicClick(event: any, action: ActionItem<any>, dynamicItem: any) {
|
||||
action._extra = dynamicItem;
|
||||
this.performAction(event, action);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,12 +280,6 @@ export class CardItemComponent implements OnInit, OnDestroy {
|
|||
|
||||
performAction(action: ActionItem<any>) {
|
||||
if (action.action == Action.Download) {
|
||||
|
||||
// if (this.download$ !== null) {
|
||||
// this.toastr.info('Download is already in progress. Please wait.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (this.utilityService.isVolume(this.entity)) {
|
||||
const volume = this.utilityService.asVolume(this.entity);
|
||||
this.downloadService.download('volume', volume);
|
||||
|
|
@ -300,7 +294,7 @@ export class CardItemComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
if (typeof action.callback === 'function') {
|
||||
action.callback(action.action, this.entity);
|
||||
action.callback(action, this.entity);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ export class ListItemComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
if (typeof action.callback === 'function') {
|
||||
action.callback(action.action, this.entity);
|
||||
action.callback(action, this.entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
ngOnChanges(changes: any) {
|
||||
if (this.data) {
|
||||
this.actions = this.actionFactoryService.getSeriesActions((action: Action, series: Series) => this.handleSeriesActionCallback(action, series));
|
||||
this.actions = this.actionFactoryService.getSeriesActions((action: ActionItem<Series>, series: Series) => this.handleSeriesActionCallback(action, series));
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
|
@ -74,8 +74,8 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
handleSeriesActionCallback(action: Action, series: Series) {
|
||||
switch (action) {
|
||||
handleSeriesActionCallback(action: ActionItem<Series>, series: Series) {
|
||||
switch (action.action) {
|
||||
case(Action.MarkAsRead):
|
||||
this.markAsRead(series);
|
||||
break;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue