Backend Bugfixes and Enhanced Selections (#754)
* Updated some signatures to avoid a ToArray() within a loop. * Use UpdateSeries directly when adding new series, rather than a modified version for new series only. * Refactored some messages for scanner loop to reduce duplicate code and write messages more clear. Hooked in a RefreshMetadataProgress event (no UI changes). * Fixed a bug on docker where backup service was using different logic than non-docker, which isn't needed after config change last release. * Allow user to make more than 1 backup per day * Implemented a select all checkbox for library access modal
This commit is contained in:
parent
0aff08c9cd
commit
f6bfabde4c
14 changed files with 171 additions and 101 deletions
|
|
@ -16,6 +16,7 @@ export enum EVENTS {
|
|||
UpdateAvailable = 'UpdateAvailable',
|
||||
ScanSeries = 'ScanSeries',
|
||||
RefreshMetadata = 'RefreshMetadata',
|
||||
RefreshMetadataProgress = 'RefreshMetadataProgress',
|
||||
SeriesAdded = 'SeriesAdded',
|
||||
SeriesRemoved = 'SeriesRemoved',
|
||||
ScanLibraryProgress = 'ScanLibraryProgress',
|
||||
|
|
@ -89,6 +90,13 @@ export class MessageHubService {
|
|||
this.scanLibrary.emit(resp.body);
|
||||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.RefreshMetadataProgress, resp => {
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.RefreshMetadataProgress,
|
||||
payload: resp.body
|
||||
});
|
||||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.SeriesAddedToCollection, resp => {
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.SeriesAddedToCollection,
|
||||
|
|
|
|||
|
|
@ -6,17 +6,24 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="list-group">
|
||||
<li class="list-group-item" *ngFor="let library of selectedLibraries; let i = index">
|
||||
<div class="form-check">
|
||||
<input id="library-{{i}}" type="checkbox" attr.aria-label="Library {{library.data.name}}" class="form-check-input"
|
||||
[(ngModel)]="library.selected" name="library">
|
||||
<label attr.for="library-{{i}}" class="form-check-label">{{library.data.name}}</label>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item" *ngIf="selectedLibraries.length === 0">
|
||||
There are no libraries setup yet.
|
||||
</li>
|
||||
<div class="list-group" *ngIf="!isLoading">
|
||||
<div class="form-check">
|
||||
<input id="selectall" type="checkbox" class="form-check-input"
|
||||
[ngModel]="selectAll" (change)="toggleAll()" [indeterminate]="hasSomeSelected">
|
||||
<label for="selectall" class="form-check-label">{{selectAll ? 'Deselect' : 'Select'}} All</label>
|
||||
</div>
|
||||
<ul>
|
||||
<li class="list-group-item" *ngFor="let library of allLibraries; let i = index">
|
||||
<div class="form-check">
|
||||
<input id="library-{{i}}" type="checkbox" class="form-check-input" attr.aria-label="Library {{library.name}}"
|
||||
[ngModel]="selections.isSelected(library)" (change)="handleSelection(library)">
|
||||
<label attr.for="library-{{i}}" class="form-check-label">{{library.name}}</label>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item" *ngIf="allLibraries.length === 0">
|
||||
There are no libraries setup yet.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { SelectionModel } from 'src/app/typeahead/typeahead.component';
|
||||
import { Library } from 'src/app/_models/library';
|
||||
import { Member } from 'src/app/_models/member';
|
||||
import { LibraryService } from 'src/app/_services/library.service';
|
||||
|
|
@ -15,24 +16,21 @@ export class LibraryAccessModalComponent implements OnInit {
|
|||
@Input() member: Member | undefined;
|
||||
allLibraries: Library[] = [];
|
||||
selectedLibraries: Array<{selected: boolean, data: Library}> = [];
|
||||
selections!: SelectionModel<Library>;
|
||||
selectAll: boolean = false;
|
||||
isLoading: boolean = false;
|
||||
|
||||
get hasSomeSelected() {
|
||||
console.log(this.selections != null && this.selections.hasSomeSelected());
|
||||
return this.selections != null && this.selections.hasSomeSelected();
|
||||
}
|
||||
|
||||
constructor(public modal: NgbActiveModal, private libraryService: LibraryService, private fb: FormBuilder) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.libraryService.getLibraries().subscribe(libs => {
|
||||
this.allLibraries = libs;
|
||||
this.selectedLibraries = libs.map(item => {
|
||||
return {selected: false, data: item};
|
||||
});
|
||||
|
||||
if (this.member !== undefined) {
|
||||
this.member.libraries.forEach(lib => {
|
||||
const foundLibrary = this.selectedLibraries.filter(item => item.data.name === lib.name);
|
||||
if (foundLibrary.length > 0) {
|
||||
foundLibrary[0].selected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.setupSelections();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -45,25 +43,41 @@ export class LibraryAccessModalComponent implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
const selectedLibraries = this.selectedLibraries.filter(item => item.selected).map(item => item.data);
|
||||
const selectedLibraries = this.selections.selected();
|
||||
this.libraryService.updateLibrariesForMember(this.member?.username, selectedLibraries).subscribe(() => {
|
||||
this.modal.close(true);
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.selectedLibraries = this.allLibraries.map(item => {
|
||||
return {selected: false, data: item};
|
||||
});
|
||||
|
||||
|
||||
setupSelections() {
|
||||
this.selections = new SelectionModel<Library>(false, this.allLibraries);
|
||||
this.isLoading = false;
|
||||
|
||||
// If a member is passed in, then auto-select their libraries
|
||||
if (this.member !== undefined) {
|
||||
this.member.libraries.forEach(lib => {
|
||||
const foundLibrary = this.selectedLibraries.filter(item => item.data.name === lib.name);
|
||||
if (foundLibrary.length > 0) {
|
||||
foundLibrary[0].selected = true;
|
||||
}
|
||||
this.selections.toggle(lib, true, (a, b) => a.name === b.name);
|
||||
});
|
||||
this.selectAll = this.selections.selected().length === this.allLibraries.length;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.setupSelections();
|
||||
}
|
||||
|
||||
toggleAll() {
|
||||
this.selectAll = !this.selectAll;
|
||||
this.allLibraries.forEach(s => this.selections.toggle(s, this.selectAll));
|
||||
}
|
||||
|
||||
handleSelection(item: Library) {
|
||||
this.selections.toggle(item);
|
||||
const numberOfSelected = this.selections.selected().length;
|
||||
if (numberOfSelected == 0) {
|
||||
this.selectAll = false;
|
||||
} else if (numberOfSelected == this.selectedLibraries.length) {
|
||||
this.selectAll = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export class ManageLibraryComponent implements OnInit, OnDestroy {
|
|||
|
||||
// when a progress event comes in, show it on the UI next to library
|
||||
this.hubService.messages$.pipe(takeUntil(this.onDestroy)).subscribe((event) => {
|
||||
if (event.event != EVENTS.ScanLibraryProgress) return;
|
||||
if (event.event !== EVENTS.ScanLibraryProgress) return;
|
||||
|
||||
const scanEvent = event.payload as ScanLibraryProgressEvent;
|
||||
this.scanInProgress[scanEvent.libraryId] = {progress: scanEvent.progress !== 100};
|
||||
|
|
@ -55,6 +55,7 @@ export class ManageLibraryComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<h6>Applies to Series</h6>
|
||||
<div class="form-check">
|
||||
<input id="selectall" type="checkbox" class="form-check-input"
|
||||
[ngModel]="selectAll" (change)="toggleAll()" [indeterminate]="someSelected">
|
||||
[ngModel]="selectAll" (change)="toggleAll()" [indeterminate]="hasSomeSelected">
|
||||
<label for="selectall" class="form-check-label">{{selectAll ? 'Deselect' : 'Select'}} All</label>
|
||||
</div>
|
||||
<ul>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ export class EditCollectionTagsComponent implements OnInit {
|
|||
imageUrls: Array<string> = [];
|
||||
selectedCover: string = '';
|
||||
|
||||
get hasSomeSelected() {
|
||||
return this.selections != null && this.selections.hasSomeSelected();
|
||||
}
|
||||
|
||||
|
||||
constructor(public modal: NgbActiveModal, private seriesService: SeriesService,
|
||||
private collectionService: CollectionTagService, private toastr: ToastrService,
|
||||
private confirmSerivce: ConfirmService, private libraryService: LibraryService,
|
||||
|
|
@ -133,11 +138,6 @@ export class EditCollectionTagsComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
get someSelected() {
|
||||
const selected = this.selections.selected();
|
||||
return (selected.length !== this.series.length && selected.length !== 0);
|
||||
}
|
||||
|
||||
updateSelectedIndex(index: number) {
|
||||
this.collectionTagForm.patchValue({
|
||||
coverImageIndex: index
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { debounceTime, filter, map, shareReplay, switchMap, take, takeUntil, tap
|
|||
import { KEY_CODES } from '../shared/_services/utility.service';
|
||||
import { TypeaheadSettings } from './typeahead-settings';
|
||||
|
||||
export type SelectionCompareFn<T> = (a: T, b: T) => boolean;
|
||||
|
||||
/**
|
||||
* SelectionModel<T> is used for keeping track of multiple selections. Simple interface with ability to toggle.
|
||||
* @param selectedState Optional state to set selectedOptions to. If not passed, defaults to false.
|
||||
|
|
@ -30,10 +32,16 @@ export class SelectionModel<T> {
|
|||
/**
|
||||
* Will toggle if the data item is selected or not. If data option is not tracked, will add it and set state to true.
|
||||
* @param data Item to toggle
|
||||
* @param selectedState Force the state
|
||||
* @param compareFn An optional function to use for the lookup, else will use shallowEqual implementation
|
||||
*/
|
||||
toggle(data: T, selectedState?: boolean) {
|
||||
//const dataItem = this._data.filter(d => d.value == data);
|
||||
const dataItem = this._data.filter(d => this.shallowEqual(d.value, data));
|
||||
toggle(data: T, selectedState?: boolean, compareFn?: SelectionCompareFn<T>) {
|
||||
let lookupMethod = this.shallowEqual;
|
||||
if (compareFn != undefined || compareFn != null) {
|
||||
lookupMethod = compareFn;
|
||||
}
|
||||
|
||||
const dataItem = this._data.filter(d => lookupMethod(d.value, data));
|
||||
if (dataItem.length > 0) {
|
||||
if (selectedState != undefined) {
|
||||
dataItem[0].selected = selectedState;
|
||||
|
|
@ -45,6 +53,7 @@ export class SelectionModel<T> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the passed item selected
|
||||
* @param data item to check against
|
||||
|
|
@ -65,6 +74,15 @@ export class SelectionModel<T> {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns If some of the items are selected, but not all
|
||||
*/
|
||||
hasSomeSelected(): boolean {
|
||||
const selectedCount = this._data.filter(d => d.selected).length;
|
||||
return (selectedCount !== this._data.length && selectedCount !== 0)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns All Selected items
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue