More Polish (#2320)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Joe Milazzo 2023-10-17 11:05:14 -05:00 committed by GitHub
parent cd3a15fa3b
commit 5f11973696
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 337 additions and 161 deletions

View file

@ -22,6 +22,7 @@ export class AuthGuard implements CanActivate {
if (user) {
return true;
}
// TODO: Remove the error message stuff here and just redirect them. Don't need to tell them
const errorMessage = this.translocoService.translate('toasts.unauthorized-1');
const errorMessage2 = this.translocoService.translate('toasts.unauthorized-2');
if (this.toastr.toasts.filter(toast => toast.message === errorMessage2 || toast.message === errorMessage).length === 0) {

View file

@ -229,7 +229,8 @@ export class SeriesService {
return this.httpClient.post(this.baseUrl + 'series/remove-from-on-deck?seriesId=' + seriesId, {});
}
getExternalSeriesDetails(aniListId?: number, malId?: number) {
return this.httpClient.get<ExternalSeriesDetail>(this.baseUrl + 'series/external-series-detail?aniListId=' + (aniListId || 0) + '&malId=' + (malId || 0));
getExternalSeriesDetails(aniListId?: number, malId?: number, seriesId?: number) {
return this.httpClient.get<ExternalSeriesDetail>(this.baseUrl + 'series/external-series-detail?aniListId=' + (aniListId || 0) + '&malId=' + (malId || 0) + '&seriesId=' + (seriesId || 0));
}
}

View file

@ -1,19 +1,22 @@
<ng-container *transloco="let t">
<div class="offcanvas-header">
<h5 class="offcanvas-title">
<ng-container *ngIf="CoverUrl as coverUrl">
<app-image *ngIf="coverUrl" height="230px" width="160px" maxHeight="230px" objectFit="contain" [imageUrl]="coverUrl"></app-image>
<div class="">
{{name}}
</div>
</ng-container>
{{name}}
</h5>
<button type="button" class="btn-close text-reset" [attr.aria-label]="t('common.close')" (click)="close()"></button>
</div>
<div class="offcanvas-body">
<ng-container *ngIf="CoverUrl as coverUrl">
<div style="width: 160px" class="mx-auto mb-3">
<app-image *ngIf="coverUrl" height="230px" width="160px" maxHeight="230px" objectFit="contain" [imageUrl]="coverUrl"></app-image>
</div>
</ng-container>
<ng-container *ngIf="externalSeries; else localSeriesBody">
<span *ngIf="(externalSeries.volumeCount || 0) > 0 || (externalSeries.chapterCount || 0) > 0" class="text-muted" style="font-size: 14px; color: lightgrey">{{t('series-preview-drawer.vols-and-chapters', {volCount: externalSeries.volumeCount, chpCount: externalSeries.chapterCount})}}</span>
<div *ngIf="(externalSeries.volumeCount || 0) > 0 || (externalSeries.chapterCount || 0) > 0" class="text-muted muted mb-2">
{{t('series-preview-drawer.vols-and-chapters', {volCount: externalSeries.volumeCount, chpCount: externalSeries.chapterCount})}}
</div>
<app-read-more *ngIf="externalSeries.summary" [maxLength]="300" [text]="externalSeries.summary"></app-read-more>
<div class="mt-3">
@ -64,7 +67,14 @@
<ng-template #localSeriesBody>
<ng-container *ngIf="localSeries">
<span class="text-muted" style="font-size: 14px; color: lightgrey">{{localSeries.publicationStatus | publicationStatus}}</span>
<div class="d-inline-block mb-2" style="width: 100%">
<span class="text-muted muted">{{localSeries.publicationStatus | publicationStatus}}</span>
<button class="btn btn-secondary btn-sm float-end me-3"
(click)="toggleWantToRead()"
ngbTooltip="{{wantToRead ? t('series-preview-drawer.remove-from-want-to-read') : t('series-preview-drawer.add-to-want-to-read')}}">
<i class="{{wantToRead ? 'fa-solid' : 'fa-regular'}} fa-star" aria-hidden="true"></i>
</button>
</div>
<app-read-more [maxLength]="300" [text]="localSeries.summary"></app-read-more>
<div class="mt-3">

View file

@ -8,3 +8,7 @@
::ng-deep .person-img {
margin-top: 24px; margin-left: 24px;
}
.muted {
font-size: 14px;
}

View file

@ -1,7 +1,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input, OnInit} from '@angular/core';
import {CommonModule} from '@angular/common';
import {TranslocoDirective} from "@ngneat/transloco";
import {NgbActiveOffcanvas} from "@ng-bootstrap/ng-bootstrap";
import {NgbActiveOffcanvas, NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {ExternalSeriesDetail, SeriesStaff} from "../../_models/series-detail/external-series-detail";
import {SeriesService} from "../../_services/series.service";
import {ImageComponent} from "../../shared/image/image.component";
@ -15,11 +15,12 @@ import {ImageService} from "../../_services/image.service";
import {PublicationStatusPipe} from "../../pipe/publication-status.pipe";
import {SeriesMetadata} from "../../_models/metadata/series-metadata";
import {ReadMoreComponent} from "../../shared/read-more/read-more.component";
import {ActionService} from "../../_services/action.service";
@Component({
selector: 'app-series-preview-drawer',
standalone: true,
imports: [CommonModule, TranslocoDirective, ImageComponent, LoadingComponent, SafeHtmlPipe, A11yClickDirective, MetadataDetailComponent, PersonBadgeComponent, TagBadgeComponent, PublicationStatusPipe, ReadMoreComponent],
imports: [CommonModule, TranslocoDirective, ImageComponent, LoadingComponent, SafeHtmlPipe, A11yClickDirective, MetadataDetailComponent, PersonBadgeComponent, TagBadgeComponent, PublicationStatusPipe, ReadMoreComponent, NgbTooltip],
templateUrl: './series-preview-drawer.component.html',
styleUrls: ['./series-preview-drawer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@ -37,10 +38,12 @@ export class SeriesPreviewDrawerComponent implements OnInit {
externalSeries: ExternalSeriesDetail | undefined;
localSeries: SeriesMetadata | undefined;
url: string = '';
wantToRead: boolean = false;
private readonly activeOffcanvas = inject(NgbActiveOffcanvas);
private readonly seriesService = inject(SeriesService);
private readonly imageService = inject(ImageService);
private readonly actionService = inject(ActionService);
private readonly cdRef = inject(ChangeDetectorRef);
get CoverUrl() {
@ -56,29 +59,52 @@ export class SeriesPreviewDrawerComponent implements OnInit {
if (this.isExternalSeries) {
this.seriesService.getExternalSeriesDetails(this.aniListId, this.malId).subscribe(externalSeries => {
this.externalSeries = externalSeries;
this.isLoading = false;
if (this.externalSeries.siteUrl) {
this.url = this.externalSeries.siteUrl;
}
console.log('External Series Detail: ', this.externalSeries);
this.cdRef.markForCheck();
});
} else {
this.seriesService.getMetadata(this.seriesId!).subscribe(data => {
this.localSeries = data;
// Consider the localSeries has no metadata, try to merge the external Series metadata
if (this.localSeries.summary === '' && this.localSeries.genres.length === 0) {
this.seriesService.getExternalSeriesDetails(0, 0, this.seriesId).subscribe(externalSeriesData => {
this.isExternalSeries = true;
this.externalSeries = externalSeriesData;
this.cdRef.markForCheck();
})
}
this.seriesService.isWantToRead(this.seriesId!).subscribe(wantToRead => {
this.wantToRead = wantToRead;
this.cdRef.markForCheck();
});
this.isLoading = false;
this.url = 'library/' + this.libraryId + '/series/' + this.seriesId;
this.localStaff = data.writers.map(p => {
return {name: p.name, role: 'Story & Art'} as SeriesStaff;
});
this.cdRef.markForCheck();
})
});
}
}
toggleWantToRead() {
if (this.wantToRead) {
this.actionService.removeMultipleSeriesFromWantToReadList([this.seriesId!]);
} else {
this.actionService.addMultipleSeriesToWantToReadList([this.seriesId!]);
}
this.wantToRead = !this.wantToRead;
this.cdRef.markForCheck();
}
close() {

View file

@ -3,21 +3,21 @@
<div *ngIf="selectionCount > 0" class="bulk-select mb-3 {{modalMode ? '' : 'fixed-top}}" [ngStyle]="{'margin-top': topOffset + 'px'}">
<div class="d-flex justify-content-around align-items-center">
<span class="highlight">
<i class="fa fa-check me-1" aria-hidden="true"></i>
{{t('items-selected',{num: selectionCount | number})}}
</span>
<span class="highlight">
<i class="fa fa-check me-1" aria-hidden="true"></i>
{{t('items-selected',{num: selectionCount | number})}}
</span>
<span>
<button *ngIf="hasMarkAsUnread" class="btn btn-icon" (click)="executeAction(Action.MarkAsUnread)" [ngbTooltip]="t('mark-as-unread')" placement="bottom">
<i class="fa-regular fa-circle-check" aria-hidden="true"></i>
<span class="visually-hidden">{{t('mark-as-unread')}}</span>
</button>
<button *ngIf="hasMarkAsRead" class="btn btn-icon" (click)="executeAction(Action.MarkAsRead)" [ngbTooltip]="t('mark-as-read')" placement="bottom">
<i class="fa-solid fa-circle-check" aria-hidden="true"></i>
<span class="visually-hidden">{{t('mark-as-read')}}</span>
</button>
<app-card-actionables [actions]="actions" labelBy="bulk-actions-header" iconClass="fa-ellipsis-h" (actionHandler)="performAction($event)"></app-card-actionables>
<button *ngIf="hasMarkAsUnread" class="btn btn-icon" (click)="executeAction(Action.MarkAsUnread)" [ngbTooltip]="t('mark-as-unread')" placement="bottom">
<i class="fa-regular fa-circle-check" aria-hidden="true"></i>
<span class="visually-hidden">{{t('mark-as-unread')}}</span>
</button>
<button *ngIf="hasMarkAsRead" class="btn btn-icon" (click)="executeAction(Action.MarkAsRead)" [ngbTooltip]="t('mark-as-read')" placement="bottom">
<i class="fa-solid fa-circle-check" aria-hidden="true"></i>
<span class="visually-hidden">{{t('mark-as-read')}}</span>
</button>
<app-card-actionables [actions]="actions" labelBy="bulk-actions-header" iconClass="fa-ellipsis-h" (actionHandler)="performAction($event)"></app-card-actionables>
</span>
<span id="bulk-actions-header" class="visually-hidden">Bulk Actions</span>

View file

@ -40,14 +40,13 @@ export class BulkOperationsComponent implements OnInit {
hasMarkAsRead: boolean = false;
hasMarkAsUnread: boolean = false;
actions: Array<ActionItem<any>> = [];
private readonly destroyRef = inject(DestroyRef);
private readonly cdRef = inject(ChangeDetectorRef);
private readonly actionFactoryService = inject(ActionFactoryService);
public readonly bulkSelectionService = inject(BulkSelectionService);
protected readonly Action = Action;
constructor() { }
ngOnInit(): void {
@ -60,10 +59,6 @@ export class BulkOperationsComponent implements OnInit {
});
}
handleActionCallback(action: ActionItem<any>, data: any) {
this.actionCallback(action, data);
}
performAction(action: ActionItem<any>) {
this.actionCallback(action, null);
}

View file

@ -47,7 +47,7 @@ export class BulkSelectionService {
if (this.isShiftDown) {
if (dataSource === this.prevDataSource) {
this.debugLog('Selecting ' + dataSource + ' cards from ' + this.prevIndex + ' to ' + index);
this.debugLog('Selecting ' + dataSource + ' cards from ' + this.prevIndex + ' to ' + index + ' as ' + !wasSelected);
this.selectCards(dataSource, this.prevIndex, index, !wasSelected);
} else {
const isForwardSelection = index > this.prevIndex;
@ -128,7 +128,7 @@ export class BulkSelectionService {
getSelectedCardsForSource(dataSource: DataSource) {
if (!this.selectedCards.hasOwnProperty(dataSource)) return [];
let ret = [];
const ret = [];
for(let k in this.selectedCards[dataSource]) {
if (this.selectedCards[dataSource][k]) {
ret.push(k);

View file

@ -11,7 +11,9 @@ export class FilterPipe implements PipeTransform {
if (!items || !callback) {
return items;
}
return items.filter(item => callback(item));
const ret = items.filter(item => callback(item));
if (ret.length === items.length) return items; // This will prevent a re-render
return ret;
}
}

View file

@ -2,11 +2,19 @@
<ng-container *ngIf="items.length > virtualizeAfter; else dragList">
<div class="example-list list-group-flush">
<!-- <li-virtual-scroll [items]="items" [itemHeight]="itemHeight" [viewCache]="BufferAmount">-->
<!-- <div *liVirtualItem="let item; let i=index" 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>-->
<!-- </li-virtual-scroll>-->
<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>
@ -16,13 +24,13 @@
</virtual-scroller>
</div>
</ng-container>
<ng-template #dragList>
<div cdkDropList class="{{items.length > 0 ? 'example-list list-group-flush' : ''}}" (cdkDropListDropped)="drop($event)">
<div class="example-box" *ngFor="let item of items; index as i" cdkDrag
<div class="example-box" *ngFor="let item of items; index as i;" cdkDrag
[cdkDragData]="item" cdkDragBoundary=".example-list"
[cdkDragDisabled]="accessibilityMode || disabled || bulkMode" cdkDragPreviewContainer="parent">
<div class="d-flex list-container">
<ng-container [ngTemplateOutlet]="handle" [ngTemplateOutletContext]="{ $implicit: item, idx: i, isVirtualized: false }"></ng-container>
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
@ -32,13 +40,6 @@
</div>
</ng-template>
<ng-template #removeBtn let-item let-idx>
<button class="btn btn-icon float-end" (click)="removeItem(item, idx)" *ngIf="showRemoveButton" [disabled]="disabled">
<i class="fa fa-times" aria-hidden="true"></i>
<span class="visually-hidden" attr.aria-labelledby="item.id--{{idx}}">{{t('remove-item-alt')}}</span>
</button>
</ng-template>
<ng-template #handle let-item let-idx="idx" let-isVirtualized="isVirtualized">
<div class="me-3 align-middle">
<div class="align-middle" [ngClass]="{'accessibility-padding': accessibilityMode, 'bulk-padding': bulkMode}" *ngIf="accessibilityMode || bulkMode">
@ -50,8 +51,8 @@
</ng-container>
<ng-container *ngIf="bulkMode">
<label for="select-{{idx}}" class="form-label visually-hidden">{{t('bulk-select-label')}}</label>
<input id="select-{{idx}}" class="form-check-input mt-0" type="checkbox" (change)="selectItem($event, item, idx)"
[value]="bulkSelectionService.isCardSelected('sideNavStream', idx)">
<input id="select-{{idx}}" class="form-check-input mt-0" type="checkbox" (change)="selectItem($event, idx)"
[ngModel]="bulkSelectionService.isCardSelected('sideNavStream', idx)" [ngModelOptions]="{standalone: true}">
</ng-container>
@ -60,6 +61,13 @@
</div>
</ng-template>
<ng-template #removeBtn let-item let-idx>
<button class="btn btn-icon float-end" (click)="removeItem(item, idx)" *ngIf="showRemoveButton" [disabled]="disabled">
<i class="fa fa-times" aria-hidden="true"></i>
<span class="visually-hidden" attr.aria-labelledby="item.id--{{idx}}">{{t('remove-item-alt')}}</span>
</button>
</ng-template>
<p class="visually-hidden" id="instructions">
{{t('instructions-alt')}}
</p>

View file

@ -3,7 +3,7 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ContentChild, DestroyRef,
EventEmitter,
inject,
Input,
@ -16,7 +16,9 @@ import {NgIf, NgFor, NgTemplateOutlet, NgClass} from '@angular/common';
import {TranslocoDirective} from "@ngneat/transloco";
import {BulkSelectionService} from "../../../cards/bulk-selection.service";
import {SeriesCardComponent} from "../../../cards/series-card/series-card.component";
import {SideNavStream} from "../../../_models/sidenav/sidenav-stream";
import {FormsModule} from "@angular/forms";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {NgxVirtualScrollModule} from "@lithiumjs/ngx-virtual-scroll";
export interface IndexUpdateEvent {
fromPosition: number;
@ -36,7 +38,9 @@ export interface ItemRemoveEvent {
styleUrls: ['./draggable-ordered-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [NgIf, VirtualScrollerModule, NgFor, NgTemplateOutlet, CdkDropList, CdkDrag, CdkDragHandle, TranslocoDirective, NgClass, SeriesCardComponent]
imports: [NgIf, VirtualScrollerModule, NgFor, NgTemplateOutlet, CdkDropList, CdkDrag,
CdkDragHandle, TranslocoDirective, NgClass, SeriesCardComponent, FormsModule,
NgxVirtualScrollModule, NgxVirtualScrollModule]
})
export class DraggableOrderedListComponent {
@ -62,26 +66,37 @@ export class DraggableOrderedListComponent {
* When enabled, draggability is disabled and a checkbox renders instead of order box or drag handle
*/
@Input() bulkMode: boolean = false;
@Input({required: true}) itemHeight: number = 60;
@Input() trackByIdentity: TrackByFunction<any> = (index: number, item: any) => `${item.id}_${item.order}_${item.title}`;
@Output() orderUpdated: EventEmitter<IndexUpdateEvent> = new EventEmitter<IndexUpdateEvent>();
@Output() itemRemove: EventEmitter<ItemRemoveEvent> = new EventEmitter<ItemRemoveEvent>();
@ContentChild('draggableItem') itemTemplate!: TemplateRef<any>;
public readonly bulkSelectionService = inject(BulkSelectionService);
public readonly destroyRef = inject(DestroyRef);
get BufferAmount() {
return Math.min(this.items.length / 20, 20);
}
constructor(private readonly cdRef: ChangeDetectorRef) { }
log(a: any, b: any) {console.log('item: ', a, 'index', b)}
constructor(private readonly cdRef: ChangeDetectorRef) {
this.bulkSelectionService.selections$.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe((s) => {
this.cdRef.markForCheck()
});
}
drop(event: CdkDragDrop<string[]>) {
if (event.previousIndex === event.currentIndex) return;
if (event.previousIndex === event.currentIndex) return;
moveItemInArray(this.items, event.previousIndex, event.currentIndex);
this.orderUpdated.emit({
fromPosition: event.previousIndex,
toPosition: event.currentIndex,
item: this.items[event.currentIndex],
item: event.item.data,
fromAccessibilityMode: false
});
this.cdRef.markForCheck();
@ -110,7 +125,7 @@ export class DraggableOrderedListComponent {
this.cdRef.markForCheck();
}
selectItem(updatedVal: Event, item: SideNavStream, index: number) {
selectItem(updatedVal: Event, index: number) {
const boolVal = (updatedVal.target as HTMLInputElement).value == 'true';
this.bulkSelectionService.handleCardSelection('sideNavStream', index, this.items.length, boolVal);

View file

@ -127,7 +127,7 @@
</ng-template>
<app-draggable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" [accessibilityMode]="accessibilityMode"
[showRemoveButton]="false">
[showRemoveButton]="false" [itemHeight]="148" [virtualizeAfter]="10">
<ng-template #draggableItem let-item let-position="idx">
<app-reading-list-item [ngClass]="{'content-container': items.length < 100, 'non-virtualized-container': items.length >= 100}" [item]="item" [position]="position" [libraryTypes]="libraryTypes"
[promoted]="item.promoted" (read)="readChapter($event)" (remove)="itemRemoved($event, position)"></app-reading-list-item>

View file

@ -1,4 +1,4 @@
<div class="row g-0 mb-1" *ngIf="tags.length > 0">
<div class="row g-0 mb-1" *ngIf="tags && tags.length > 0">
<div class="col-lg-3 col-md-4 col-sm-12">
<h5>{{heading}}</h5>
</div>

View file

@ -5,17 +5,11 @@ import {TranslocoDirective} from "@ngneat/transloco";
import {NgbActiveModal, NgbNav, NgbNavContent, NgbNavItem, NgbNavLink, NgbNavOutlet} from "@ng-bootstrap/ng-bootstrap";
import {
DraggableOrderedListComponent,
IndexUpdateEvent
} from "../../../reading-list/_components/draggable-ordered-list/draggable-ordered-list.component";
import {
ReadingListItemComponent
} from "../../../reading-list/_components/reading-list-item/reading-list-item.component";
import {forkJoin} from "rxjs";
import {FilterService} from "../../../_services/filter.service";
import {DashboardStreamListItemComponent} from "../dashboard-stream-list-item/dashboard-stream-list-item.component";
import {SmartFilter} from "../../../_models/metadata/v2/smart-filter";
import {DashboardService} from "../../../_services/dashboard.service";
import {DashboardStream} from "../../../_models/dashboard/dashboard-stream";
import {Breakpoint, UtilityService} from "../../../shared/_services/utility.service";
import {CustomizeDashboardStreamsComponent} from "../customize-dashboard-streams/customize-dashboard-streams.component";
import {CustomizeSidenavStreamsComponent} from "../customize-sidenav-streams/customize-sidenav-streams.component";
@ -40,7 +34,7 @@ enum TabID {
})
export class CustomizeDashboardModalComponent {
activeTab = TabID.Dashboard;
activeTab = TabID.SideNav;
private readonly cdRef = inject(ChangeDetectorRef);
public readonly utilityService = inject(UtilityService);

View file

@ -1,6 +1,6 @@
<ng-container *transloco="let t; read: 'customize-dashboard-streams'">
<app-draggable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" [accessibilityMode]="accessibilityMode"
[showRemoveButton]="false">
[showRemoveButton]="false" [itemHeight]="60">
<ng-template #draggableItem let-position="idx" let-item>
<app-dashboard-stream-list-item [item]="item" [position]="position" (hide)="updateVisibility($event, position)"></app-dashboard-stream-list-item>
</ng-template>

View file

@ -22,3 +22,10 @@ app-dashboard-stream-list-item {
background-color: var(--list-group-hover-bg-color);
}
}
.virtual-scroller, virtual-scroller {
width: 100%;
height: calc(100vh - 85px);
max-height: calc(var(--vh)*100 - 170px);
}

View file

@ -11,7 +11,6 @@ import {FilterService} from "../../../_services/filter.service";
import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap";
import {forkJoin} from "rxjs";
import {TranslocoDirective} from "@ngneat/transloco";
import {CommonStream} from "../../../_models/common-stream";
import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms";
import {FilterPipe} from "../../../pipe/filter.pipe";
@ -76,8 +75,8 @@ export class CustomizeDashboardStreamsComponent {
updateVisibility(item: DashboardStream, position: number) {
this.items[position].visible = !this.items[position].visible;
this.dashboardService.updateDashboardStream(this.items[position]).subscribe();
this.cdRef.markForCheck();
this.dashboardService.updateDashboardStream(this.items[position]).subscribe();
}
}

View file

@ -23,18 +23,21 @@
</div>
</div>
<app-bulk-operations [modalMode]="true" [topOffset]="0" [actionCallback]="bulkActionCallback"></app-bulk-operations>
<app-draggable-ordered-list [items]="items | filter: filterSideNavStreams" (orderUpdated)="orderUpdated($event)"
[accessibilityMode]="pageOperationsForm.get('accessibilityMode')!.value"
[showRemoveButton]="false" [disabled]="listForm.get('filterSideNavStream')?.value"
[bulkMode]="pageOperationsForm.get('bulkMode')!.value"
[virtualizeAfter]="100"
>
<ng-template #draggableItem let-position="idx" let-item>
<app-sidenav-stream-list-item [item]="item" [position]="position" (hide)="updateVisibility($event, position)"></app-sidenav-stream-list-item>
</ng-template>
</app-draggable-ordered-list>
<div style="max-height: 500px; overflow-y: auto">
<app-draggable-ordered-list [items]="items | filter: filterSideNavStreams" (orderUpdated)="orderUpdated($event)"
[accessibilityMode]="pageOperationsForm.get('accessibilityMode')!.value"
[showRemoveButton]="false" [disabled]="listForm.get('filterSideNavStream')!.value"
[bulkMode]="pageOperationsForm.get('bulkMode')!.value"
[virtualizeAfter]="virtualizeAfter"
[itemHeight]="60"
>
<ng-template #draggableItem let-position="idx" let-item>
<app-sidenav-stream-list-item [item]="item" [position]="position" (hide)="updateVisibility($event, position)"></app-sidenav-stream-list-item>
</ng-template>
</app-draggable-ordered-list>
</div>
<h5>{{t('smart-filters-title')}}</h5>
<h5 class="mt-3">{{t('smart-filters-title')}}</h5>
<div class="mb-3" *ngIf="smartFilters.length >= 6">
<label for="smart-filter-filter" class="form-label">{{t('filter')}}</label>
<div class="input-group">

View file

@ -1,4 +1,12 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnDestroy} from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef, EventEmitter,
HostListener,
inject,
OnDestroy
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {SmartFilter} from "../../../_models/metadata/v2/smart-filter";
import {FilterService} from "../../../_services/filter.service";
@ -23,6 +31,7 @@ import {Action, ActionItem} from "../../../_services/action-factory.service";
import {BulkSelectionService} from "../../../cards/bulk-selection.service";
import {filter, tap} from "rxjs/operators";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {KEY_CODES} from "../../../shared/_services/utility.service";
@Component({
selector: 'app-customize-sidenav-streams',
@ -38,6 +47,7 @@ export class CustomizeSidenavStreamsComponent implements OnDestroy {
items: SideNavStream[] = [];
smartFilters: SmartFilter[] = [];
externalSources: ExternalSource[] = [];
virtualizeAfter = 100;
listForm: FormGroup = new FormGroup({
'filterSideNavStream': new FormControl('', []),
@ -65,7 +75,9 @@ export class CustomizeSidenavStreamsComponent implements OnDestroy {
}
bulkActionCallback = (action: ActionItem<SideNavStream>, data: SideNavStream) => {
const streams = this.bulkSelectionService.getSelectedCardsForSource('sideNavStream').map(index => this.items[parseInt(index, 10)]);
const selectedItems = this.bulkSelectionService.getSelectedCardsForSource('sideNavStream');
const streams = selectedItems
.map(index => this.items[parseInt(index, 10)]);
let visibleState = false;
switch (action.action) {
case Action.MarkAsVisible:
@ -76,13 +88,17 @@ export class CustomizeSidenavStreamsComponent implements OnDestroy {
break;
}
for(let index of this.bulkSelectionService.getSelectedCardsForSource('sideNavStream').map(s => parseInt(s, 10))) {
for(let index of selectedItems.map(s => parseInt(s, 10))) {
this.items[index].visible = visibleState;
this.items[index] = {...this.items[index]};
}
this.cdRef.markForCheck();
// Make bulk call
this.sideNavService.bulkToggleSideNavStreamVisibility(streams.map(s => s.id), visibleState).subscribe(() => this.bulkSelectionService.deselectAll());
this.sideNavService.bulkToggleSideNavStreamVisibility(streams.map(s => s.id), visibleState)
.subscribe(() => {
this.bulkSelectionService.deselectAll();
this.cdRef.markForCheck();
});
}
@ -91,7 +107,22 @@ export class CustomizeSidenavStreamsComponent implements OnDestroy {
private readonly externalSourceService = inject(ExternalSourceService);
private readonly cdRef = inject(ChangeDetectorRef);
private readonly destroyRef = inject(DestroyRef);
private readonly bulkSelectionService = inject(BulkSelectionService);
public readonly bulkSelectionService = inject(BulkSelectionService);
@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;
this.cdRef.markForCheck();
}
}
constructor(public modal: NgbActiveModal) {
@ -140,8 +171,8 @@ export class CustomizeSidenavStreamsComponent implements OnDestroy {
]).subscribe(results => {
this.items = results[0];
// After 100 items, drag and drop is disabled to use virtualization
if (this.items.length > 100) {
// After X items, drag and drop is disabled to use virtualization
if (this.items.length > this.virtualizeAfter) {
this.pageOperationsForm.get('accessibilityMode')?.setValue(true);
}
@ -192,12 +223,10 @@ export class CustomizeSidenavStreamsComponent implements OnDestroy {
orderUpdated(event: IndexUpdateEvent) {
this.sideNavService.updateSideNavStreamPosition(event.item.name, event.item.id, event.fromPosition, event.toPosition).subscribe(() => {
if (event.fromAccessibilityMode) {
this.sideNavService.getSideNavStreams(false).subscribe((data) => {
this.items = [...data];
this.cdRef.markForCheck();
})
}
this.sideNavService.getSideNavStreams(false).subscribe((data) => {
this.items = [...data];
this.cdRef.markForCheck();
});
});
}

View file

@ -686,8 +686,8 @@
"continue-incognito": "Continue Incognito",
"read-options-alt": "Read options",
"incognito": "Incognito",
"remove-from-want-to-read": "Remove from Want to Read",
"add-to-want-to-read": "Add to Want to Read",
"remove-from-want-to-read": "{{actionable.remove-from-want-to-read}}",
"add-to-want-to-read": "{{actionable.add-to-want-to-read}}",
"edit-series-alt": "Edit Series Information",
"download-series--tooltip": "Download Series",
"downloading-status": "Downloading…",
@ -1686,7 +1686,9 @@
"tags-label": "{{filter-field-pipe.tags}}",
"genres-label": "{{filter-field-pipe.genres}}",
"view-series": "View Series",
"vols-and-chapters": "{{volCount}} Volumes / {{chpCount}} Chapters"
"vols-and-chapters": "{{volCount}} Volumes / {{chpCount}} Chapters",
"remove-from-want-to-read": "{{actionable.remove-from-want-to-read}}",
"add-to-want-to-read": "{{actionable.add-to-want-to-read}}"
},
"server-stats": {

View file

@ -11,6 +11,7 @@
--body-text-color: #efefef;
--btn-icon-filter: invert(1) grayscale(100%) brightness(200%);
--primary-color-scrollbar: rgba(74,198,148,0.75);
--text-muted-color: lightgrey;
/* Meta and Globals */
--theme-color: #000000;