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();
|
||||
}
|
||||
|
|
|
@ -546,6 +546,15 @@
|
|||
"title": "Announcements"
|
||||
},
|
||||
|
||||
"out-of-date-modal": {
|
||||
"title": "Don't fall behind!",
|
||||
"close": "{{common.close}}",
|
||||
"subtitle": "It seems your install is more than {{count}} versions behind!",
|
||||
"description-1": "Please consider upgrading so that you're running the latest version of Kavita.",
|
||||
"description-2": "Take a look at our <a href='https://wiki.kavitareader.com/guides/updating/' target='_blank' rel='noreferrer noopener'>wiki</a> for instructions on how to update.",
|
||||
"description-3": "If there is a specific reason you haven't updated yet we would love to know what is keeping you on an outdated version! Stop by our discord and let us know what is blocking your upgrade path."
|
||||
},
|
||||
|
||||
"changelog": {
|
||||
"installed": "Installed",
|
||||
"download": "Download",
|
||||
|
@ -1190,6 +1199,9 @@
|
|||
"folder-watching-label": "Folder Watching",
|
||||
"folder-watching-tooltip": "Allows Kavita to monitor Library Folders to detect changes and invoke scanning on those changes. This allows content to be updated without manually invoking scans or waiting for nightly scans. Will always wait 10 minutes before triggering scan.",
|
||||
"enable-folder-watching": "Enable Folder Watching",
|
||||
"host-name-label": "{{manage-email-settings.host-name-label}}",
|
||||
"host-name-tooltip": "{{manage-email-settings.host-name-tooltip}}",
|
||||
"host-name-validation": "{{manage-email-settings.host-name-validation}}",
|
||||
|
||||
|
||||
"reset-to-default": "{{common.reset-to-default}}",
|
||||
|
@ -1643,7 +1655,8 @@
|
|||
"last-chapter-added": "Item Added",
|
||||
"time-to-read": "Time to Read",
|
||||
"release-year": "Release Year",
|
||||
"read-progress": "Last Read"
|
||||
"read-progress": "Last Read",
|
||||
"average-rating": "Average Rating"
|
||||
},
|
||||
|
||||
"edit-series-modal": {
|
||||
|
@ -2029,6 +2042,7 @@
|
|||
"change-email-no-email": "Email has been updated",
|
||||
"device-updated": "Device updated",
|
||||
"device-created": "Device created",
|
||||
"delete-device": "Are you sure you want to delete this device?",
|
||||
"confirm-regen-covers": "Refresh covers will force all cover images to be recalculated. This is a heavy operation. Are you sure you don't want to perform a Scan instead?",
|
||||
"alert-long-running": "This is a long running process. Please give it the time to complete before invoking again.",
|
||||
"confirm-delete-multiple-series": "Are you sure you want to delete {{count}} series? It will not modify files on disk.",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue