MAL Interest Stacks (#2932)
This commit is contained in:
parent
29eb65c783
commit
b23300b1a4
61 changed files with 4104 additions and 382 deletions
|
|
@ -11,15 +11,34 @@
|
|||
</div>
|
||||
|
||||
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" *ngIf="collectionTag !== undefined" #scrollingBlock>
|
||||
<div class="row mb-3" *ngIf="summary.length > 0">
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
|
||||
<app-image maxWidth="481px" [imageUrl]="imageService.getCollectionCoverImage(collectionTag.id)"></app-image>
|
||||
@if (summary.length > 0 || collectionTag.source !== ScrobbleProvider.Kavita) {
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
|
||||
<app-image [styles]="{'max-width': '481px'}" [imageUrl]="imageService.getCollectionCoverImage(collectionTag.id)"></app-image>
|
||||
@if (collectionTag.source !== ScrobbleProvider.Kavita && collectionTag.missingSeriesFromSource !== null
|
||||
&& series.length !== collectionTag.totalSourceCount && collectionTag.totalSourceCount > 0) {
|
||||
<div class="under-image">
|
||||
<app-image [imageUrl]="collectionTag.source | providerImage"
|
||||
width="16px" height="16px" [styles]="{'vertical-align': 'text-top'}"
|
||||
[ngbTooltip]="collectionTag.source | providerName" tabindex="0"></app-image>
|
||||
<span class="ms-2 me-2">{{t('sync-progress', {title: series.length + ' / ' + collectionTag.totalSourceCount})}}</span>
|
||||
<i class="fa-solid fa-question-circle" aria-hidden="true" [ngbTooltip]="t('last-sync', {date: collectionTag.lastSyncUtc | date: 'shortDate' | defaultDate })"></i>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-10 col-xs-8 col-sm-6 mt-2">
|
||||
@if (summary.length > 0) {
|
||||
<div class="mb-2">
|
||||
<app-read-more [text]="summary" [maxLength]="utilityService.getActiveBreakpoint() < Breakpoint.Tablet ? 250 : 600"></app-read-more>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
<div class="col-md-10 col-xs-8 col-sm-6 mt-2">
|
||||
<app-read-more [text]="summary" [maxLength]="250"></app-read-more>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||
|
||||
<app-card-detail-layout *ngIf="filter"
|
||||
|
|
|
|||
|
|
@ -41,3 +41,11 @@ h2 {
|
|||
margin-bottom: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.under-image {
|
||||
background-color: var(--breadcrumb-bg-color);
|
||||
color: white;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {DOCUMENT, NgIf, NgStyle} from '@angular/common';
|
||||
import {DatePipe, DOCUMENT, NgIf, NgStyle} from '@angular/common';
|
||||
import {
|
||||
AfterContentChecked,
|
||||
ChangeDetectionStrategy,
|
||||
|
|
@ -15,14 +15,14 @@ import {
|
|||
} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {NgbModal, NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {ToastrService} from 'ngx-toastr';
|
||||
import {debounceTime, take} from 'rxjs/operators';
|
||||
import {BulkSelectionService} from 'src/app/cards/bulk-selection.service';
|
||||
import {EditCollectionTagsComponent} from 'src/app/cards/_modals/edit-collection-tags/edit-collection-tags.component';
|
||||
import {FilterSettings} from 'src/app/metadata-filter/filter-settings';
|
||||
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
|
||||
import {KEY_CODES, UtilityService} from 'src/app/shared/_services/utility.service';
|
||||
import {Breakpoint, KEY_CODES, UtilityService} from 'src/app/shared/_services/utility.service';
|
||||
import {UserCollection} from 'src/app/_models/collection-tag';
|
||||
import {SeriesAddedToCollectionEvent} from 'src/app/_models/events/series-added-to-collection-event';
|
||||
import {JumpKey} from 'src/app/_models/jumpbar/jump-key';
|
||||
|
|
@ -54,6 +54,12 @@ import {FilterComparison} from "../../../_models/metadata/v2/filter-comparison";
|
|||
import {SeriesFilterV2} from "../../../_models/metadata/v2/series-filter-v2";
|
||||
import {AccountService} from "../../../_services/account.service";
|
||||
import {User} from "../../../_models/user";
|
||||
import {ScrobbleProvider} from "../../../_services/scrobbling.service";
|
||||
import {SafeHtmlPipe} from "../../../_pipes/safe-html.pipe";
|
||||
import {TranslocoDatePipe} from "@ngneat/transloco-locale";
|
||||
import {DefaultDatePipe} from "../../../_pipes/default-date.pipe";
|
||||
import {ProviderImagePipe} from "../../../_pipes/provider-image.pipe";
|
||||
import {ProviderNamePipe} from "../../../_pipes/provider-name.pipe";
|
||||
|
||||
@Component({
|
||||
selector: 'app-collection-detail',
|
||||
|
|
@ -61,7 +67,7 @@ import {User} from "../../../_models/user";
|
|||
styleUrls: ['./collection-detail.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [NgIf, SideNavCompanionBarComponent, CardActionablesComponent, NgStyle, ImageComponent, ReadMoreComponent, BulkOperationsComponent, CardDetailLayoutComponent, SeriesCardComponent, TranslocoDirective]
|
||||
imports: [NgIf, SideNavCompanionBarComponent, CardActionablesComponent, NgStyle, ImageComponent, ReadMoreComponent, BulkOperationsComponent, CardDetailLayoutComponent, SeriesCardComponent, TranslocoDirective, NgbTooltip, SafeHtmlPipe, TranslocoDatePipe, DatePipe, DefaultDatePipe, ProviderImagePipe, ProviderNamePipe]
|
||||
})
|
||||
export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
||||
|
||||
|
|
@ -82,7 +88,7 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
|||
private readonly actionService = inject(ActionService);
|
||||
private readonly messageHub = inject(MessageHubService);
|
||||
private readonly filterUtilityService = inject(FilterUtilitiesService);
|
||||
private readonly utilityService = inject(UtilityService);
|
||||
protected readonly utilityService = inject(UtilityService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly scrollService = inject(ScrollService);
|
||||
|
||||
|
|
@ -213,7 +219,7 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
|||
|
||||
|
||||
this.messageHub.messages$.pipe(takeUntilDestroyed(this.destroyRef), debounceTime(2000)).subscribe(event => {
|
||||
if (event.event == EVENTS.SeriesAddedToCollection) {
|
||||
if (event.event == EVENTS.CollectionUpdated) {
|
||||
const collectionEvent = event.payload as SeriesAddedToCollectionEvent;
|
||||
if (collectionEvent.tagId === this.collectionTag.id) {
|
||||
this.loadPage();
|
||||
|
|
@ -326,4 +332,7 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
|||
this.loadPage();
|
||||
});
|
||||
}
|
||||
|
||||
protected readonly ScrobbleProvider = ScrobbleProvider;
|
||||
protected readonly Breakpoint = Breakpoint;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,12 @@
|
|||
|
||||
<ul>
|
||||
@for(stack of stacks; track stack.url) {
|
||||
<li>
|
||||
<div><a [href]="stack.url" rel="noreferrer noopener" target="_blank">{{stack.title}}</a></div>
|
||||
<div>by {{stack.author}} • {{t('series-count', {num: stack.seriesCount})}} • <span><i class="fa-solid fa-layer-group me-1" aria-hidden="true"></i>{{t('restack-count', {num: stack.restackCount})}}</span></div>
|
||||
<li class="mb-2">
|
||||
<div>
|
||||
<a [href]="stack.url" rel="noreferrer noopener" target="_blank">{{stack.title}}</a>
|
||||
<button class="btn btn-primary float-end" [disabled]="collectionMap && collectionMap.hasOwnProperty(stack.url)" (click)="importStack(stack)">Track</button>
|
||||
</div>
|
||||
<div>by {{stack.author}} • {{t('series-count', {num: stack.seriesCount | number})}} • <span><i class="fa-solid fa-layer-group me-1" aria-hidden="true"></i>{{t('restack-count', {num: stack.restackCount | number})}}</span></div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
|
@ -31,6 +34,9 @@
|
|||
<!-- <div class="col-auto">-->
|
||||
<!-- <button type="button" class="btn btn-primary" (click)="nextStep()" [disabled]="!canMoveToNextStep()">{{t(NextButtonLabel)}}</button>-->
|
||||
<!-- </div>-->
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-secondary" (click)="ngbModal.dismiss()">{{t('close')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject} from '@angular/core';
|
||||
import {TranslocoDirective} from "@ngneat/transloco";
|
||||
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
||||
import {ReactiveFormsModule} from "@angular/forms";
|
||||
import {Select2Module} from "ng-select2-component";
|
||||
import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {CollectionTagService} from "../../../_services/collection-tag.service";
|
||||
import {MalStack} from "../../../_models/collection/mal-stack";
|
||||
import {UserCollection} from "../../../_models/collection-tag";
|
||||
import {ScrobbleProvider} from "../../../_services/scrobbling.service";
|
||||
import {forkJoin} from "rxjs";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {DecimalPipe} from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: 'app-import-mal-collection-modal',
|
||||
|
|
@ -12,7 +17,8 @@ import {MalStack} from "../../../_models/collection/mal-stack";
|
|||
imports: [
|
||||
TranslocoDirective,
|
||||
ReactiveFormsModule,
|
||||
Select2Module
|
||||
Select2Module,
|
||||
DecimalPipe
|
||||
],
|
||||
templateUrl: './import-mal-collection-modal.component.html',
|
||||
styleUrl: './import-mal-collection-modal.component.scss',
|
||||
|
|
@ -21,19 +27,41 @@ import {MalStack} from "../../../_models/collection/mal-stack";
|
|||
export class ImportMalCollectionModalComponent {
|
||||
|
||||
protected readonly ngbModal = inject(NgbActiveModal);
|
||||
protected readonly collectionService = inject(CollectionTagService);
|
||||
protected readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly collectionService = inject(CollectionTagService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
|
||||
stacks: Array<MalStack> = [];
|
||||
isLoading = true;
|
||||
collectionMap: {[key: string]: UserCollection | MalStack} = {};
|
||||
|
||||
constructor() {
|
||||
this.collectionService.getMalStacks().subscribe(stacks => {
|
||||
this.stacks = stacks;
|
||||
this.isLoading = false;
|
||||
this.cdRef.markForCheck();
|
||||
})
|
||||
|
||||
forkJoin({
|
||||
allCollections: this.collectionService.allCollections(true),
|
||||
malStacks: this.collectionService.getMalStacks()
|
||||
}).subscribe(res => {
|
||||
|
||||
// Create a map on sourceUrl from collections so that if there are non-null sourceUrl (and source is MAL) then we can disable buttons
|
||||
const collects = res.allCollections.filter(c => c.source === ScrobbleProvider.Mal && c.sourceUrl);
|
||||
for(let col of collects) {
|
||||
if (col.sourceUrl === null) continue;
|
||||
this.collectionMap[col.sourceUrl] = col;
|
||||
}
|
||||
|
||||
this.stacks = res.malStacks;
|
||||
this.isLoading = false;
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
importStack(stack: MalStack) {
|
||||
this.collectionService.importStack(stack).subscribe(() => {
|
||||
this.collectionMap[stack.url] = stack;
|
||||
this.cdRef.markForCheck();
|
||||
this.toastr.success(translate('toasts.stack-imported'));
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue