Another round of bugfixes (#3707)
This commit is contained in:
parent
cbb97208b8
commit
93dc6534fc
32 changed files with 412 additions and 335 deletions
|
@ -1,23 +1,27 @@
|
|||
import {inject, Injectable} from '@angular/core';
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { BulkAddToCollectionComponent } from '../cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component';
|
||||
import { AddToListModalComponent, ADD_FLOW } from '../reading-list/_modals/add-to-list-modal/add-to-list-modal.component';
|
||||
import { EditReadingListModalComponent } from '../reading-list/_modals/edit-reading-list-modal/edit-reading-list-modal.component';
|
||||
import { ConfirmService } from '../shared/confirm.service';
|
||||
import { LibrarySettingsModalComponent } from '../sidenav/_modals/library-settings-modal/library-settings-modal.component';
|
||||
import { Chapter } from '../_models/chapter';
|
||||
import { Device } from '../_models/device/device';
|
||||
import { Library } from '../_models/library/library';
|
||||
import { ReadingList } from '../_models/reading-list';
|
||||
import { Series } from '../_models/series';
|
||||
import { Volume } from '../_models/volume';
|
||||
import { DeviceService } from './device.service';
|
||||
import { LibraryService } from './library.service';
|
||||
import { MemberService } from './member.service';
|
||||
import { ReaderService } from './reader.service';
|
||||
import { SeriesService } from './series.service';
|
||||
import {NgbModal, NgbModalRef} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {ToastrService} from 'ngx-toastr';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {BulkAddToCollectionComponent} from '../cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component';
|
||||
import {ADD_FLOW, AddToListModalComponent} from '../reading-list/_modals/add-to-list-modal/add-to-list-modal.component';
|
||||
import {
|
||||
EditReadingListModalComponent
|
||||
} from '../reading-list/_modals/edit-reading-list-modal/edit-reading-list-modal.component';
|
||||
import {ConfirmService} from '../shared/confirm.service';
|
||||
import {
|
||||
LibrarySettingsModalComponent
|
||||
} from '../sidenav/_modals/library-settings-modal/library-settings-modal.component';
|
||||
import {Chapter} from '../_models/chapter';
|
||||
import {Device} from '../_models/device/device';
|
||||
import {Library} from '../_models/library/library';
|
||||
import {ReadingList} from '../_models/reading-list';
|
||||
import {Series} from '../_models/series';
|
||||
import {Volume} from '../_models/volume';
|
||||
import {DeviceService} from './device.service';
|
||||
import {LibraryService} from './library.service';
|
||||
import {MemberService} from './member.service';
|
||||
import {ReaderService} from './reader.service';
|
||||
import {SeriesService} from './series.service';
|
||||
import {translate} from "@jsverse/transloco";
|
||||
import {UserCollection} from "../_models/collection-tag";
|
||||
import {CollectionTagService} from "./collection-tag.service";
|
||||
|
@ -652,7 +656,7 @@ export class ActionService {
|
|||
}
|
||||
|
||||
editReadingList(readingList: ReadingList, callback?: ReadingListActionCallback) {
|
||||
const readingListModalRef = this.modalService.open(EditReadingListModalComponent, { scrollable: true, size: 'lg', fullscreen: 'md' });
|
||||
const readingListModalRef = this.modalService.open(EditReadingListModalComponent, DefaultModalOptions);
|
||||
readingListModalRef.componentInstance.readingList = readingList;
|
||||
readingListModalRef.closed.pipe(take(1)).subscribe((list) => {
|
||||
if (callback && list !== undefined) {
|
||||
|
@ -773,7 +777,7 @@ export class ActionService {
|
|||
}
|
||||
|
||||
matchSeries(series: Series, callback?: BooleanActionCallback) {
|
||||
const ref = this.modalService.open(MatchSeriesModalComponent, {size: 'lg'});
|
||||
const ref = this.modalService.open(MatchSeriesModalComponent, DefaultModalOptions);
|
||||
ref.componentInstance.series = series;
|
||||
ref.closed.subscribe(saved => {
|
||||
if (callback) {
|
||||
|
|
|
@ -47,30 +47,33 @@
|
|||
}
|
||||
|
||||
|
||||
<div class="setting-section-break" aria-hidden="true"></div>
|
||||
@if (!suppressEmptyGenres || genres.length > 0) {
|
||||
<div class="setting-section-break" aria-hidden="true"></div>
|
||||
|
||||
|
||||
<div class="mb-3 ms-1">
|
||||
<h4 class="header">{{t('genres-title')}}</h4>
|
||||
<div class="ms-3">
|
||||
<app-badge-expander [includeComma]="true" [items]="genres" [itemsTillExpander]="3" [defaultExpanded]="true">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openGeneric(FilterField.Genres, item.id)">{{item.title}}</a>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
<div class="mb-3 ms-1">
|
||||
<h4 class="header">{{t('genres-title')}}</h4>
|
||||
<div class="ms-3">
|
||||
<app-badge-expander [includeComma]="true" [items]="genres" [itemsTillExpander]="3" [defaultExpanded]="true">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openGeneric(FilterField.Genres, item.id)">{{item.title}}</a>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-3 ms-1">
|
||||
<h4 class="header">{{t('tags-title')}}</h4>
|
||||
<div class="ms-3">
|
||||
<app-badge-expander [includeComma]="true" [items]="tags" [itemsTillExpander]="3" [defaultExpanded]="true">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openGeneric(FilterField.Tags, item.id)">{{item.title}}</a>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
@if (!suppressEmptyTags || tags.length > 0) {
|
||||
<div class="mb-3 ms-1">
|
||||
<h4 class="header">{{t('tags-title')}}</h4>
|
||||
<div class="ms-3">
|
||||
<app-badge-expander [includeComma]="true" [items]="tags" [itemsTillExpander]="3" [defaultExpanded]="true">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openGeneric(FilterField.Tags, item.id)">{{item.title}}</a>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-3">
|
||||
<app-carousel-reel [items]="webLinks" [title]="t('weblinks-title')">
|
||||
|
|
|
@ -61,6 +61,8 @@ export class DetailsTabComponent {
|
|||
@Input() genres: Array<Genre> = [];
|
||||
@Input() tags: Array<Tag> = [];
|
||||
@Input() webLinks: Array<string> = [];
|
||||
@Input() suppressEmptyGenres: boolean = false;
|
||||
@Input() suppressEmptyTags: boolean = false;
|
||||
|
||||
|
||||
openGeneric(queryParamName: FilterField, filter: string | number) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core';
|
||||
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {ToastrService} from 'ngx-toastr';
|
||||
import {debounceTime, distinctUntilChanged, filter, switchMap, take, tap} from 'rxjs';
|
||||
import {debounceTime, distinctUntilChanged, filter, switchMap, tap} from 'rxjs';
|
||||
import {SettingsService} from '../settings.service';
|
||||
import {ServerSettings} from '../_models/server-settings';
|
||||
import {translate, TranslocoModule} from "@jsverse/transloco";
|
||||
|
@ -30,7 +30,7 @@ export class ManageEmailSettingsComponent implements OnInit {
|
|||
settingsForm: FormGroup = new FormGroup({});
|
||||
|
||||
ngOnInit(): void {
|
||||
this.settingsService.getServerSettings().pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.settingsService.getServerSettings().subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
this.settingsForm.addControl('hostName', new FormControl(this.serverSettings.hostName, [Validators.pattern(/^(http:|https:)+[^\s]+[\w]$/)]));
|
||||
|
||||
|
@ -100,6 +100,8 @@ export class ManageEmailSettingsComponent implements OnInit {
|
|||
|
||||
packData() {
|
||||
const modelSettings = Object.assign({}, this.serverSettings);
|
||||
|
||||
|
||||
modelSettings.emailServiceUrl = this.settingsForm.get('emailServiceUrl')?.value;
|
||||
modelSettings.hostName = this.settingsForm.get('hostName')?.value;
|
||||
|
||||
|
|
|
@ -47,15 +47,15 @@ export class ManageSettingsComponent implements OnInit {
|
|||
translate('manage-settings.allow-stats-tooltip-part-2');
|
||||
|
||||
ngOnInit(): void {
|
||||
this.settingsService.getTaskFrequencies().pipe(take(1)).subscribe(frequencies => {
|
||||
this.settingsService.getTaskFrequencies().subscribe(frequencies => {
|
||||
this.taskFrequencies = frequencies;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
this.settingsService.getLoggingLevels().pipe(take(1)).subscribe(levels => {
|
||||
this.settingsService.getLoggingLevels().subscribe(levels => {
|
||||
this.logLevels = levels;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
this.settingsService.getServerSettings().pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.settingsService.getServerSettings().subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
this.settingsForm.addControl('cacheDirectory', new FormControl(this.serverSettings.cacheDirectory, [Validators.required]));
|
||||
this.settingsForm.addControl('taskScan', new FormControl(this.serverSettings.taskScan, [Validators.required]));
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
</td>
|
||||
<td>
|
||||
@if (member.libraries.length > 0) {
|
||||
@if (hasAdminRole(member)) {
|
||||
@if (hasAdminRole(member) || member.libraries.length === libraryCount) {
|
||||
{{t('all-libraries')}}
|
||||
} @else {
|
||||
@if (member.libraries.length > 5) {
|
||||
|
|
|
@ -13,7 +13,7 @@ import {EditUserComponent} from '../edit-user/edit-user.component';
|
|||
import {Router} from '@angular/router';
|
||||
import {TagBadgeComponent} from '../../shared/tag-badge/tag-badge.component';
|
||||
import {AsyncPipe, NgClass, TitleCasePipe} from '@angular/common';
|
||||
import {translate, TranslocoModule, TranslocoService} from "@jsverse/transloco";
|
||||
import {TranslocoModule, TranslocoService} from "@jsverse/transloco";
|
||||
import {DefaultDatePipe} from "../../_pipes/default-date.pipe";
|
||||
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
|
||||
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
|
||||
|
@ -50,6 +50,7 @@ export class ManageUsersComponent implements OnInit {
|
|||
members: Member[] = [];
|
||||
loggedInUsername = '';
|
||||
loadingMembers = false;
|
||||
libraryCount: number = 0;
|
||||
|
||||
|
||||
constructor() {
|
||||
|
@ -81,7 +82,11 @@ export class ManageUsersComponent implements OnInit {
|
|||
if (nameA < nameB) return -1;
|
||||
if (nameA > nameB) return 1;
|
||||
return 0;
|
||||
})
|
||||
});
|
||||
|
||||
// Get the admin and get their library count
|
||||
this.libraryCount = this.members.filter(m => this.hasAdminRole(m))[0].libraries.length;
|
||||
|
||||
this.loadingMembers = false;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
@ -142,16 +147,8 @@ export class ManageUsersComponent implements OnInit {
|
|||
modalRef.componentInstance.member = member;
|
||||
}
|
||||
|
||||
formatLibraries(member: Member) {
|
||||
if (member.libraries.length === 0) {
|
||||
return translate('manage-users.none');
|
||||
}
|
||||
|
||||
return member.libraries.map(item => item.name).join(', ');
|
||||
}
|
||||
|
||||
hasAdminRole(member: Member) {
|
||||
return member.roles.indexOf('Admin') >= 0;
|
||||
return member.roles.indexOf(Role.Admin) >= 0;
|
||||
}
|
||||
|
||||
getRoles(member: Member) {
|
||||
|
|
|
@ -722,6 +722,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
// Update the window Height
|
||||
this.updateWidthAndHeightCalcs();
|
||||
this.updateImageSizes();
|
||||
|
||||
const resumeElement = this.getFirstVisibleElementXPath();
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default && resumeElement !== null && resumeElement !== undefined) {
|
||||
this.scrollTo(resumeElement); // This works pretty well, but not perfect
|
||||
|
@ -944,7 +945,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
this.bookService.getBookPage(this.chapterId, this.pageNum).pipe(take(1)).subscribe(content => {
|
||||
this.isSingleImagePage = this.checkSingleImagePage(content) // This needs be performed before we set this.page to avoid image jumping
|
||||
this.updateSingleImagePageStyles()
|
||||
this.updateSingleImagePageStyles();
|
||||
this.page = this.domSanitizer.bypassSecurityTrustHtml(content); // PERF: Potential optimization to prefetch next/prev page and store in localStorage
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
|
|
|
@ -52,12 +52,15 @@
|
|||
}
|
||||
</div>
|
||||
|
||||
@if (libraryType === LibraryType.LightNovel || libraryType === LibraryType.Book) {
|
||||
@if (libraryType !== LibraryType.Images) {
|
||||
<div class="card-body meta-title">
|
||||
<span class="card-format">
|
||||
</span>
|
||||
<span class="card-format"></span>
|
||||
<div class="card-content d-flex justify-content-center align-items-center text-center" style="width:100%;min-height:58px;">
|
||||
{{volume.name}}
|
||||
@if (libraryType === LibraryType.LightNovel || libraryType === LibraryType.Book) {
|
||||
{{volume.name}}
|
||||
} @else {
|
||||
{{volume.chapters[0].titleName}}
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (actions && actions.length > 0) {
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
@if (items.length > virtualizeAfter) {
|
||||
<div class="example-list list-group-flush">
|
||||
<virtual-scroller #scroll [items]="items" [bufferAmount]="BufferAmount" [parentScroll]="parentScroll">
|
||||
<div class="example-box" *ngFor="let item of scroll.viewPortItems; index as i; trackBy: trackByIdentity">
|
||||
|
||||
<div class="d-flex list-container">
|
||||
<ng-container [ngTemplateOutlet]="handle" [ngTemplateOutletContext]="{ $implicit: item, idx: i, isVirtualized: true }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
||||
|
||||
<ng-container [ngTemplateOutlet]="removeBtn" [ngTemplateOutletContext]="{$implicit: item, idx: i}"></ng-container>
|
||||
@for (item of scroll.viewPortItems; track trackByIdentity(i, item); let i = $index) {
|
||||
<div class="example-box">
|
||||
<div class="d-flex list-container">
|
||||
<ng-container [ngTemplateOutlet]="handle" [ngTemplateOutletContext]="{ $implicit: item, idx: i, isVirtualized: true }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="removeBtn" [ngTemplateOutletContext]="{$implicit: item, idx: i}"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</virtual-scroller>
|
||||
</div>
|
||||
} @else {
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
TrackByFunction
|
||||
} from '@angular/core';
|
||||
import {VirtualScrollerModule} from '@iharbeck/ngx-virtual-scroller';
|
||||
import {NgClass, NgFor, NgTemplateOutlet} from '@angular/common';
|
||||
import {NgClass, NgTemplateOutlet} from '@angular/common';
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {BulkSelectionService} from "../../../cards/bulk-selection.service";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
|
@ -36,13 +36,14 @@ export interface ItemRemoveEvent {
|
|||
templateUrl: './draggable-ordered-list.component.html',
|
||||
styleUrls: ['./draggable-ordered-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [VirtualScrollerModule, NgFor, NgTemplateOutlet, CdkDropList, CdkDrag,
|
||||
imports: [VirtualScrollerModule, NgTemplateOutlet, CdkDropList, CdkDrag,
|
||||
CdkDragHandle, TranslocoDirective, NgClass, FormsModule]
|
||||
})
|
||||
export class DraggableOrderedListComponent {
|
||||
|
||||
protected readonly bulkSelectionService = inject(BulkSelectionService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -84,7 +85,7 @@ export class DraggableOrderedListComponent {
|
|||
return Math.min(this.items.length / 20, 20);
|
||||
}
|
||||
|
||||
constructor(private readonly cdRef: ChangeDetectorRef) {
|
||||
constructor() {
|
||||
this.bulkSelectionService.selections$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe((s) => {
|
||||
|
|
|
@ -252,6 +252,8 @@
|
|||
@defer (when activeTabId === TabID.Details; prefetch on idle) {
|
||||
<app-details-tab [metadata]="castInfo"
|
||||
[readingTime]="rlInfo"
|
||||
[suppressEmptyGenres]="true"
|
||||
[suppressEmptyTags]="true"
|
||||
[ageRating]="readingList.ageRating"/>
|
||||
}
|
||||
</ng-template>
|
||||
|
|
|
@ -636,7 +636,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
}
|
||||
}
|
||||
|
||||
handleChapterActionCallback(action: ActionItem<Chapter>, chapter: Chapter) {
|
||||
async handleChapterActionCallback(action: ActionItem<Chapter>, chapter: Chapter) {
|
||||
switch (action.action) {
|
||||
case(Action.MarkAsRead):
|
||||
this.markChapterAsRead(chapter);
|
||||
|
@ -657,6 +657,14 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
const device = (action._extra!.data as Device);
|
||||
this.actionService.sendToDevice([chapter.id], device);
|
||||
break;
|
||||
case (Action.Delete):
|
||||
await this.actionService.deleteChapter(chapter.id, (success) => {
|
||||
if (!success) return;
|
||||
|
||||
this.chapters = this.chapters.filter(c => c.id != chapter.id);
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.text-muted {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,69 +18,69 @@
|
|||
|
||||
<ng-template #tooltip>{{t('format-tooltip')}}</ng-template>
|
||||
|
||||
@let files = files$ | async;
|
||||
|
||||
<ng-container *ngIf="files$ | async as files">
|
||||
<ng-container *ngIf="formControl.value; else tableLayout">
|
||||
<ngx-charts-advanced-pie-chart [results]="vizData2$ | async"></ngx-charts-advanced-pie-chart>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #tableLayout>
|
||||
@if (formControl.value) {
|
||||
<ngx-charts-advanced-pie-chart [results]="vizData2$ | async" />
|
||||
} @else {
|
||||
<div style="height: 242px; overflow-y: auto;">
|
||||
<table class="table table-striped table-striped table-sm scrollable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" sortable="extension" direction="asc" (sort)="onSort($event)">
|
||||
{{t('extension-header')}}
|
||||
</th>
|
||||
<th scope="col" sortable="format" (sort)="onSort($event)">
|
||||
{{t('format-header')}}
|
||||
</th>
|
||||
<th scope="col" sortable="totalSize" (sort)="onSort($event)">
|
||||
{{t('total-size-header')}}
|
||||
</th>
|
||||
<th scope="col" sortable="totalFiles" (sort)="onSort($event)">
|
||||
{{t('total-files-header')}}
|
||||
</th>
|
||||
<th scope="col">{{t('download-file-for-extension-header')}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col" sortable="extension" direction="asc" (sort)="onSort($event)">
|
||||
{{t('extension-header')}}
|
||||
</th>
|
||||
<th scope="col" sortable="format" (sort)="onSort($event)">
|
||||
{{t('format-header')}}
|
||||
</th>
|
||||
<th scope="col" sortable="totalSize" (sort)="onSort($event)">
|
||||
{{t('total-size-header')}}
|
||||
</th>
|
||||
<th scope="col" sortable="totalFiles" (sort)="onSort($event)">
|
||||
{{t('total-files-header')}}
|
||||
</th>
|
||||
<th scope="col">{{t('download-file-for-extension-header')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of files; let idx = index;">
|
||||
<td id="adhoctask--{{idx}}">
|
||||
{{item.extension || t('not-classified')}}
|
||||
</td>
|
||||
<td>
|
||||
{{item.format | mangaFormat}}
|
||||
</td>
|
||||
<td>
|
||||
{{item.totalSize | bytes}}
|
||||
</td>
|
||||
<td>
|
||||
{{item.totalFiles | number:'1.0-0'}}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-icon" style="color: var(--primary-color)" (click)="export(item.extension)" [disabled]="downloadInProgress[item.extension]">
|
||||
@if (downloadInProgress[item.extension]) {
|
||||
<div class="spinner-border spinner-border-sm" aria-hidden="true"></div>
|
||||
} @else {
|
||||
<i class="fa-solid fa-file-arrow-down" aria-hidden="true"></i>
|
||||
}
|
||||
<span class="visually-hidden">{{t('download-file-for-extension-alt"', {extension: item.extension})}}</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@for (item of files; track item.extension; let idx = $index) {
|
||||
<tr>
|
||||
<td id="adhoctask--{{idx}}">
|
||||
{{item.extension || t('not-classified')}}
|
||||
</td>
|
||||
<td>
|
||||
{{item.format | mangaFormat}}
|
||||
</td>
|
||||
<td>
|
||||
{{item.totalSize | bytes}}
|
||||
</td>
|
||||
<td>
|
||||
{{item.totalFiles | number:'1.0-0'}}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-icon" style="color: var(--primary-color)" (click)="export(item.extension)" [disabled]="downloadInProgress[item.extension]">
|
||||
@if (downloadInProgress[item.extension]) {
|
||||
<div class="spinner-border spinner-border-sm" aria-hidden="true"></div>
|
||||
} @else {
|
||||
<i class="fa-solid fa-file-arrow-down" aria-hidden="true"></i>
|
||||
}
|
||||
<span class="visually-hidden">{{t('download-file-for-extension-alt', {extension: item.extension})}}</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>{{t('total-file-size-title')}}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{{((rawData$ | async)?.totalFileSize || 0) | bytes}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{t('total-file-size-title')}}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{{((rawData$ | async)?.totalFileSize || 0) | bytes}}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
@ -9,4 +9,8 @@
|
|||
display: flex;
|
||||
flex-flow: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
tfoot {
|
||||
color: var(--bs-body);
|
||||
}
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
import {
|
||||
ChangeDetectionStrategy, ChangeDetectorRef,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
DestroyRef,
|
||||
inject,
|
||||
QueryList, TemplateRef, ViewChild,
|
||||
QueryList,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
ViewChildren
|
||||
} from '@angular/core';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { PieChartModule } from '@swimlane/ngx-charts';
|
||||
import {Observable, BehaviorSubject, combineLatest, map, shareReplay, switchMap} from 'rxjs';
|
||||
import { StatisticsService } from 'src/app/_services/statistics.service';
|
||||
import { SortableHeader, SortEvent, compare } from 'src/app/_single-module/table/_directives/sortable-header.directive';
|
||||
import { FileExtension, FileExtensionBreakdown } from '../../_models/file-breakdown';
|
||||
import { PieDataItem } from '../../_models/pie-data-item';
|
||||
import {FormControl, ReactiveFormsModule} from '@angular/forms';
|
||||
import {PieChartModule} from '@swimlane/ngx-charts';
|
||||
import {BehaviorSubject, combineLatest, map, Observable, shareReplay} from 'rxjs';
|
||||
import {StatisticsService} from 'src/app/_services/statistics.service';
|
||||
import {compare, SortableHeader, SortEvent} from 'src/app/_single-module/table/_directives/sortable-header.directive';
|
||||
import {FileExtension, FileExtensionBreakdown} from '../../_models/file-breakdown';
|
||||
import {PieDataItem} from '../../_models/pie-data-item';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import { MangaFormatPipe } from '../../../_pipes/manga-format.pipe';
|
||||
import { BytesPipe } from '../../../_pipes/bytes.pipe';
|
||||
import { NgIf, NgFor, AsyncPipe, DecimalPipe } from '@angular/common';
|
||||
import {translate, TranslocoDirective, TranslocoService} from "@jsverse/transloco";
|
||||
import {Pagination} from "../../../_models/pagination";
|
||||
import {DownloadService} from "../../../shared/_services/download.service";
|
||||
import {MangaFormatPipe} from '../../../_pipes/manga-format.pipe';
|
||||
import {BytesPipe} from '../../../_pipes/bytes.pipe';
|
||||
import {AsyncPipe, DecimalPipe, NgFor, NgIf} from '@angular/common';
|
||||
import {TranslocoDirective, TranslocoService} from "@jsverse/transloco";
|
||||
import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {ColumnMode, NgxDatatableModule} from "@siemens/ngx-datatable";
|
||||
import {UtcToLocalTimePipe} from "../../../_pipes/utc-to-local-time.pipe";
|
||||
|
||||
export interface StackedBarChartDataItem {
|
||||
name: string,
|
||||
|
@ -32,7 +35,7 @@ export interface StackedBarChartDataItem {
|
|||
templateUrl: './file-breakdown-stats.component.html',
|
||||
styleUrls: ['./file-breakdown-stats.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [NgbTooltip, ReactiveFormsModule, NgIf, PieChartModule, NgFor, AsyncPipe, DecimalPipe, BytesPipe, MangaFormatPipe, TranslocoDirective, SortableHeader]
|
||||
imports: [NgbTooltip, ReactiveFormsModule, NgIf, PieChartModule, NgFor, AsyncPipe, DecimalPipe, BytesPipe, MangaFormatPipe, TranslocoDirective, SortableHeader, NgxDatatableModule, UtcToLocalTimePipe]
|
||||
})
|
||||
export class FileBreakdownStatsComponent {
|
||||
|
||||
|
@ -103,4 +106,5 @@ export class FileBreakdownStatsComponent {
|
|||
});
|
||||
}
|
||||
|
||||
protected readonly ColumnMode = ColumnMode;
|
||||
}
|
||||
|
|
|
@ -16,16 +16,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@let statuses = publicationStatues$ | async;
|
||||
|
||||
|
||||
<ng-container *ngIf="publicationStatues$ | async as statuses">
|
||||
<ng-container *ngIf="formControl.value; else tableLayout">
|
||||
<ngx-charts-advanced-pie-chart
|
||||
[results]="statuses"
|
||||
>
|
||||
</ngx-charts-advanced-pie-chart>
|
||||
</ng-container>
|
||||
<ng-template #tableLayout>
|
||||
@if (formControl.value) {
|
||||
<ngx-charts-advanced-pie-chart [results]="statuses" />
|
||||
} @else {
|
||||
<div style="height: 242px; overflow-y: auto;">
|
||||
<table class="table table-striped table-striped table-sm scrollable">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -48,8 +44,8 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -53,7 +53,6 @@
|
|||
<li (click)="handleOptionClick(option)"
|
||||
class="list-group-item" role="option" [attr.data-index]="index"
|
||||
(mouseenter)="focusedIndex = index + (showAddItem ? 1 : 0); updateHighlight();">
|
||||
{{settings.trackByIdentityFn(index, option)}}
|
||||
<ng-container [ngTemplateOutlet]="optionTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index, value: typeaheadControl.value }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue