Sort by Average Rating and Big Want to Read fix (#2672)
This commit is contained in:
parent
03e7d38482
commit
1fd72ada36
42 changed files with 3552 additions and 105 deletions
|
|
@ -21,6 +21,10 @@ export enum SortField {
|
|||
TimeToRead = 5,
|
||||
ReleaseYear = 6,
|
||||
ReadProgress = 7,
|
||||
/**
|
||||
* Kavita+ only
|
||||
*/
|
||||
AverageRating = 8
|
||||
}
|
||||
|
||||
export const allSortFields = Object.keys(SortField)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ export class SortFieldPipe implements PipeTransform {
|
|||
return this.translocoService.translate('sort-field-pipe.release-year');
|
||||
case SortField.ReadProgress:
|
||||
return this.translocoService.translate('sort-field-pipe.read-progress');
|
||||
case SortField.AverageRating:
|
||||
return this.translocoService.translate('sort-field-pipe.average-rating');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,11 +39,16 @@ export class ServerService {
|
|||
}
|
||||
|
||||
checkForUpdate() {
|
||||
return this.http.get<UpdateVersionEvent>(this.baseUrl + 'server/check-update', {});
|
||||
return this.http.get<UpdateVersionEvent | null>(this.baseUrl + 'server/check-update');
|
||||
}
|
||||
|
||||
checkHowOutOfDate() {
|
||||
return this.http.get<string>(this.baseUrl + 'server/checkHowOutOfDate', TextResonse)
|
||||
.pipe(map(r => parseInt(r, 10)));
|
||||
}
|
||||
|
||||
checkForUpdates() {
|
||||
return this.http.get(this.baseUrl + 'server/check-for-updates', {});
|
||||
return this.http.get<UpdateVersionEvent>(this.baseUrl + 'server/check-for-updates', {});
|
||||
}
|
||||
|
||||
getChangelog() {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export class ManageEmailSettingsComponent implements OnInit {
|
|||
ngOnInit(): void {
|
||||
this.settingsService.getServerSettings().pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
this.settingsForm.addControl('hostName', new FormControl(this.serverSettings.hostName, []));
|
||||
this.settingsForm.addControl('hostName', new FormControl(this.serverSettings.hostName, [Validators.pattern(/^(http:|https:)+[^\s]+[\w]$/)]));
|
||||
|
||||
this.settingsForm.addControl('host', new FormControl(this.serverSettings.smtpConfig.host, []));
|
||||
this.settingsForm.addControl('port', new FormControl(this.serverSettings.smtpConfig.port, []));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,21 @@
|
|||
<strong>{{t('notice')}}</strong> {{t('restart-required')}}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="settings-hostname" class="form-label">{{t('host-name-label')}}</label><i class="fa fa-info-circle ms-1" placement="right" [ngbTooltip]="hostNameTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #hostNameTooltip>{{t('host-name-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-hostname-help">
|
||||
<ng-container [ngTemplateOutlet]="hostNameTooltip"></ng-container>
|
||||
</span>
|
||||
<input id="settings-hostname" aria-describedby="settings-hostname-help" class="form-control" formControlName="hostName" type="text"
|
||||
[class.is-invalid]="settingsForm.get('hostName')?.invalid && settingsForm.get('hostName')?.touched">
|
||||
<div id="hostname-validations" class="invalid-feedback" *ngIf="settingsForm.dirty || settingsForm.touched">
|
||||
<div *ngIf="settingsForm.get('hostName')?.errors?.pattern">
|
||||
{{t('host-name-validation')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="settings-baseurl" class="form-label">{{t('base-url-label')}}</label><i class="fa fa-info-circle ms-1" placement="right" [ngbTooltip]="baseUrlTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #baseUrlTooltip>{{t('base-url-tooltip')}}</ng-template>
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ export class ManageSettingsComponent implements OnInit {
|
|||
modelSettings.smtpConfig = this.serverSettings.smtpConfig;
|
||||
|
||||
|
||||
|
||||
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
this.resetForm();
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ export class ChangelogComponent implements OnInit {
|
|||
constructor(private serverService: ServerService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.serverService.getChangelog().subscribe(updates => {
|
||||
this.updates = updates;
|
||||
this.isLoading = false;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
<ng-container *transloco="let t; read:'out-of-date-modal'">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{t('title')}}</h4>
|
||||
<button type="button" class="btn-close" [attr.aria-label]="t('close')" (click)="close()"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<p><strong>{{t('subtitle', {count: versionsOutOfDate})}}</strong></p>
|
||||
<p>{{t('description-1')}}</p>
|
||||
<p [innerHTML]="t('description-2') | safeHtml"></p>
|
||||
<p>{{t('description-3')}}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-link me-1" href="https://discord.gg/b52wT37kt7" target="_blank" rel="noreferrer noopener">Discord</a>
|
||||
<button type="button" class="btn btn-primary" (click)="close()">{{t('close')}}</button>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import {Component, DestroyRef, inject, Input} from '@angular/core';
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {AsyncPipe, NgForOf, NgIf} from "@angular/common";
|
||||
import {NgbActiveModal, NgbHighlight, NgbModal, NgbTypeahead} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {TranslocoDirective} from "@ngneat/transloco";
|
||||
import {ServerService} from "../../../_services/server.service";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {map} from "rxjs/operators";
|
||||
import {ChangelogComponent} from "../changelog/changelog.component";
|
||||
import {SafeHtmlPipe} from "../../../_pipes/safe-html.pipe";
|
||||
|
||||
@Component({
|
||||
selector: 'app-out-of-date-modal',
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule,
|
||||
NgForOf,
|
||||
NgIf,
|
||||
NgbHighlight,
|
||||
NgbTypeahead,
|
||||
TranslocoDirective,
|
||||
AsyncPipe,
|
||||
ChangelogComponent,
|
||||
SafeHtmlPipe
|
||||
],
|
||||
templateUrl: './out-of-date-modal.component.html',
|
||||
styleUrl: './out-of-date-modal.component.scss'
|
||||
})
|
||||
export class OutOfDateModalComponent {
|
||||
|
||||
private readonly ngbModal = inject(NgbActiveModal);
|
||||
|
||||
@Input({required: true}) versionsOutOfDate: number = 0;
|
||||
|
||||
close() {
|
||||
this.ngbModal.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ import { SideNavComponent } from './sidenav/_components/side-nav/side-nav.compon
|
|||
import {NavHeaderComponent} from "./nav/_components/nav-header/nav-header.component";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {ServerService} from "./_services/server.service";
|
||||
import {ImportCblModalComponent} from "./reading-list/_modals/import-cbl-modal/import-cbl-modal.component";
|
||||
import {OutOfDateModalComponent} from "./announcements/_components/out-of-date-modal/out-of-date-modal.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
|
|
@ -67,15 +69,6 @@ export class AppComponent implements OnInit {
|
|||
|
||||
});
|
||||
|
||||
// Every hour, have the UI check for an update. People seriously stay out of date
|
||||
// interval(60 * 60 * 1000) // 60 minutes in milliseconds
|
||||
// .pipe(
|
||||
// switchMap(() => this.accountService.currentUser$),
|
||||
// filter(u => u !== undefined && this.accountService.hasAdminRole(u)),
|
||||
// switchMap(_ => this.serverService.checkForUpdates())
|
||||
// )
|
||||
// .subscribe();
|
||||
|
||||
|
||||
this.transitionState$ = this.accountService.currentUser$.pipe(
|
||||
tap(user => {
|
||||
|
|
@ -111,11 +104,21 @@ export class AppComponent implements OnInit {
|
|||
// On load, make an initial call for valid license
|
||||
this.accountService.hasValidLicense().subscribe();
|
||||
|
||||
interval(4 * 60 * 60 * 1000) // 4 hours in milliseconds
|
||||
// Every hour, have the UI check for an update. People seriously stay out of date
|
||||
interval(2* 60 * 60 * 1000) // 2 hours in milliseconds
|
||||
.pipe(
|
||||
switchMap(() => this.accountService.currentUser$),
|
||||
filter(u => this.accountService.hasAdminRole(u!)),
|
||||
switchMap(_ => this.serverService.checkForUpdates())
|
||||
filter(u => u !== undefined && this.accountService.hasAdminRole(u)),
|
||||
switchMap(_ => this.serverService.checkHowOutOfDate()),
|
||||
filter(versionOutOfDate => {
|
||||
return !isNaN(versionOutOfDate) && versionOutOfDate > 2;
|
||||
}),
|
||||
tap(versionOutOfDate => {
|
||||
if (!this.ngbModal.hasOpenModals()) {
|
||||
const ref = this.ngbModal.open(OutOfDateModalComponent, {size: 'xl', fullscreen: 'md'});
|
||||
ref.componentInstance.versionsOutOfDate = 3;
|
||||
}
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
if (this.activeTabId === TabID.Chapters) chapterArray = this.chapters;
|
||||
|
||||
// We must augment chapter indices as Bulk Selection assumes all on one page, but Storyline has mixed
|
||||
const chapterIndexModifier = this.activeTabId === TabID.Storyline ? this.volumes.length + 1 : 0;
|
||||
const chapterIndexModifier = this.activeTabId === TabID.Storyline ? this.volumes.length : 0;
|
||||
const selectedChapterIds = chapterArray.filter((_chapter, index: number) => {
|
||||
const mappedIndex = index + chapterIndexModifier;
|
||||
return selectedChapterIndexes.includes(mappedIndex + '');
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ import { SentenceCasePipe } from '../../_pipes/sentence-case.pipe';
|
|||
import { NgIf, NgFor } from '@angular/common';
|
||||
import { EditDeviceComponent } from '../edit-device/edit-device.component';
|
||||
import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {TranslocoDirective} from "@ngneat/transloco";
|
||||
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
||||
import {SettingsService} from "../../admin/settings.service";
|
||||
import {ConfirmService} from "../../shared/confirm.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-devices',
|
||||
|
|
@ -28,6 +29,7 @@ export class ManageDevicesComponent implements OnInit {
|
|||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly deviceService = inject(DeviceService);
|
||||
private readonly settingsService = inject(SettingsService);
|
||||
private readonly confirmService = inject(ConfirmService);
|
||||
|
||||
devices: Array<Device> = [];
|
||||
addDeviceIsCollapsed: boolean = true;
|
||||
|
|
@ -53,7 +55,8 @@ export class ManageDevicesComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
deleteDevice(device: Device) {
|
||||
async deleteDevice(device: Device) {
|
||||
if (!await this.confirmService.confirm(translate('toasts.delete-device'))) return;
|
||||
this.deviceService.deleteDevice(device.id).subscribe(() => {
|
||||
const index = this.devices.indexOf(device);
|
||||
this.devices.splice(index, 1);
|
||||
|
|
|
|||
|
|
@ -156,7 +156,10 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
|
||||
this.accountService.hasValidLicense$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(res => {
|
||||
if (res) {
|
||||
this.tabs.push({title: 'scrobbling-tab', fragment: FragmentID.Scrobbling});
|
||||
if (this.tabs.filter(t => t.fragment == FragmentID.Scrobbling).length === 0) {
|
||||
this.tabs.push({title: 'scrobbling-tab', fragment: FragmentID.Scrobbling});
|
||||
}
|
||||
|
||||
this.hasActiveLicense = true;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue