Misc Fixes + Enhancements (#1875)

* Moved Collapse Series with relationships into a user preference rather than library setting.

* Fixed bookmarks not converting to webp after initial save

* Fixed a bug where when merging we'd print out a duplicate series error when we shouldn't have

* Fixed a bug where clicking on a genre or tag from server stats wouldn't load all-series page in a filtered state.

* Implemented the ability to have Login role and thus disable accounts.

* Ensure first time flow gets the Login role

* Refactored user management screen so that pending users can be edited or deleted before the end user accepts the invite. A side effect is old legacy users that were here before email was required can now be deleted.

* Show a progress bar under the main series image on larger viewports to show whole series progress.

* Removed code no longer needed

* Cleanup tags, people, collections without connections after editing series metadata.

* Moved the Entity Builders to the main project
This commit is contained in:
Joe Milazzo 2023-03-10 19:09:38 -06:00 committed by GitHub
parent c62e594792
commit bd19b282d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 2186 additions and 239 deletions

View file

@ -10,4 +10,5 @@ export interface Member {
roles: string[];
libraries: Library[];
ageRestriction: AgeRestriction;
isPending: boolean;
}

View file

@ -40,6 +40,7 @@ export interface Preferences {
blurUnreadSummaries: boolean;
promptForDownloadSize: boolean;
noTransitions: boolean;
collapseSeriesRelationships: boolean;
}
export const readingDirections = [{text: 'Left to Right', value: ReadingDirection.LeftToRight}, {text: 'Right to Left', value: ReadingDirection.RightToLeft}];

View file

@ -12,8 +12,8 @@ export class MemberService {
constructor(private httpClient: HttpClient) { }
getMembers() {
return this.httpClient.get<Member[]>(this.baseUrl + 'users');
getMembers(includePending: boolean = false) {
return this.httpClient.get<Member[]>(this.baseUrl + 'users?includePending=' + includePending);
}
getMemberNames() {

View file

@ -1,40 +1,11 @@
<div class="container-fluid">
<ng-container>
<div class="row mb-2">
<div class="col-8"><h3>Pending Invites</h3></div>
<div class="col-4"><button class="btn btn-primary float-end" (click)="inviteUser()"><i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden">&nbsp;Invite</span></button></div>
</div>
<ul class="list-group">
<li class="list-group-item no-hover" *ngFor="let invite of pendingInvites; let idx = index;">
<div>
<h4>
<span id="member-name--{{idx}}">{{invite.username | titlecase}} </span>
<div class="float-end">
<button class="btn btn-danger btn-sm me-2" (click)="deleteUser(invite)">Cancel</button>
<button class="btn btn-secondary btn-sm me-2" (click)="resendEmail(invite)">Resend</button>
<button class="btn btn-secondary btn-sm" (click)="setup(invite)">Setup</button>
</div>
</h4>
<div>Invited: {{invite.created | date: 'short'}}</div>
</div>
</li>
<li *ngIf="loadingMembers" class="list-group-item no-hover">
<div class="spinner-border text-secondary" role="status">
<span class="invisible">Loading...</span>
</div>
</li>
<li class="list-group-item no-hover" *ngIf="pendingInvites.length === 0 && !loadingMembers">
There are no invited Users
</li>
</ul>
</ng-container>
<h3 class="mt-3">Active Users</h3>
<div class="row mb-2">
<div class="col-8"><h3>Active Users</h3></div>
<div class="col-4"><button class="btn btn-primary float-end" (click)="inviteUser()"><i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden">&nbsp;Invite</span></button></div>
</div>
<ul class="list-group">
<li *ngFor="let member of members; let idx = index;" class="list-group-item no-hover">
<div>
@ -45,10 +16,14 @@
<i class="fas fa-star" aria-hidden="true"></i>
<span class="visually-hidden">(You)</span>
</span>
<span class="badge bg-secondary text-dark" *ngIf="member.isPending">Pending</span>
<div class="float-end" *ngIf="canEditMember(member)">
<button class="btn btn-danger btn-sm me-2" (click)="deleteUser(member)" placement="top" ngbTooltip="Delete User" attr.aria-label="Delete User {{member.username | titlecase}}"><i class="fa fa-trash" aria-hidden="true"></i></button>
<button class="btn btn-secondary btn-sm me-2" (click)="updatePassword(member)" placement="top" ngbTooltip="Change Password" attr.aria-label="Change Password for {{member.username | titlecase}}"><i class="fa fa-key" aria-hidden="true"></i></button>
<button class="btn btn-primary btn-sm" (click)="openEditUser(member)" placement="top" ngbTooltip="Edit" attr.aria-label="Edit {{member.username | titlecase}}"><i class="fa fa-pen" aria-hidden="true"></i></button>
<button class="btn btn-primary btn-sm me-2" (click)="openEditUser(member)" placement="top" ngbTooltip="Edit" attr.aria-label="Edit {{member.username | titlecase}}"><i class="fa fa-pen" aria-hidden="true"></i></button>
<button *ngIf="member.isPending" class="btn btn-secondary btn-sm me-2" (click)="resendEmail(member)" placement="top" ngbTooltip="Resend Invite" attr.aria-label="Delete Invite {{member.username | titlecase}}">Resend</button>
<button *ngIf="member.isPending" class="btn btn-secondary btn-sm me-2" (click)="setup(member)" placement="top" ngbTooltip="Setup User" attr.aria-label="Setup User {{member.username | titlecase}}">Setup</button>
<button *ngIf="!member.isPending" class="btn btn-secondary btn-sm" (click)="updatePassword(member)" placement="top" ngbTooltip="Change Password" attr.aria-label="Change Password for {{member.username | titlecase}}"><i class="fa fa-key" aria-hidden="true"></i></button>
</div>
</h4>
<div class="user-info">

View file

@ -58,7 +58,7 @@ export class ManageUsersComponent implements OnInit, OnDestroy {
loadMembers() {
this.loadingMembers = true;
this.memberService.getMembers().subscribe(members => {
this.memberService.getMembers(true).subscribe(members => {
this.members = members;
// Show logged in user at the top of the list
this.members.sort((a: Member, b: Member) => {

View file

@ -53,8 +53,15 @@ export class RoleSelectorComponent implements OnInit {
foundRole[0].selected = true;
}
});
this.cdRef.markForCheck();
} else {
// For new users, preselect LoginRole
this.selectedRoles.forEach(role => {
if (role.data == 'Login') {
role.selected = true;
}
});
}
this.cdRef.markForCheck();
}
handleModelUpdate() {

View file

@ -63,6 +63,9 @@
<app-tag-badge [selectionMode]="TagBadgeCursor.NotAllowed" fillStyle="filled">{{unreadCount}}</app-tag-badge>
</div>
<app-image height="100%" maxHeight="400px" objectFit="contain" background="none" [imageUrl]="seriesImage"></app-image>
<div class="progress-banner" *ngIf="series.pagesRead < series.pages && hasReadingProgress && currentlyReadingChapter && !currentlyReadingChapter.isSpecial">
<ngb-progressbar type="primary" height="5px" [value]="series.pagesRead" [max]="series.pages"></ngb-progressbar>
</div>
<div class="under-image" *ngIf="series.pagesRead < series.pages && hasReadingProgress && currentlyReadingChapter && !currentlyReadingChapter.isSpecial">
Continue {{ContinuePointTitle}}
</div>

View file

@ -53,3 +53,7 @@
.info-container {
align-items: flex-start;
}
::ng-deep .progress {
border-radius: 0px;
}

View file

@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SeriesDetailRoutingModule } from './series-detail-routing.module';
import { NgbCollapseModule, NgbNavModule, NgbRatingModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
import { NgbCollapseModule, NgbNavModule, NgbProgressbarModule, NgbRatingModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
import { SeriesMetadataDetailComponent } from './_components/series-metadata-detail/series-metadata-detail.component';
import { ReviewSeriesModalComponent } from './_modals/review-series-modal/review-series-modal.component';
import { SharedModule } from '../shared/shared.module';
@ -26,6 +26,7 @@ import { SeriesDetailComponent } from './_components/series-detail/series-detail
NgbNavModule,
NgbRatingModule,
NgbTooltipModule, // Series Detail, Extras Drawer
NgbProgressbarModule,
TypeaheadModule,
PipeModule,

View file

@ -105,21 +105,7 @@
</p>
</div>
</div>
<div class="row">
<div class="col-md-12 col-sm-12 pe-2 mb-2">
<div class="mb-3 mt-1">
<div class="form-check form-switch">
<input type="checkbox" id="collapse-relationships" role="switch" formControlName="collapseSeriesRelationships" class="form-check-input" aria-labelledby="auto-close-label">
<label class="form-check-label" for="collapse-relationships">Collapse Series Relationships</label>
</div>
</div>
<p class="accent">
Experiemental: Should Kavita show Series that have no relationships or is the parent/prequel
</p>
</div>
</div>
<div class="row">
<div class="col-md-12 col-sm-12 pe-2 mb-2">
<div class="mb-3 mt-1">

View file

@ -101,7 +101,7 @@ export class ServerStatsComponent implements OnDestroy {
ref.componentInstance.title = 'Genres';
ref.componentInstance.clicked = (item: string) => {
const params: any = {};
params[FilterQueryParam.Genres] = item;
params[FilterQueryParam.Genres] = genres.filter(g => g.title === item)[0].id;
params[FilterQueryParam.Page] = 1;
this.router.navigate(['all-series'], {queryParams: params});
};
@ -115,7 +115,7 @@ export class ServerStatsComponent implements OnDestroy {
ref.componentInstance.title = 'Tags';
ref.componentInstance.clicked = (item: string) => {
const params: any = {};
params[FilterQueryParam.Tags] = item;
params[FilterQueryParam.Tags] = tags.filter(g => g.title === item)[0].id;
params[FilterQueryParam.Page] = 1;
this.router.navigate(['all-series'], {queryParams: params});
};

View file

@ -77,6 +77,19 @@
</div>
</div>
<div class="row g-0">
<div class="col-md-12 col-sm-12 pe-2 mb-2">
<div class="mb-3 mt-1">
<div class="form-check form-switch">
<input type="checkbox" id="collapse-relationships" role="switch" formControlName="collapseSeriesRelationships" aria-describedby="settings-collapse-relationships-help" class="form-check-input" aria-labelledby="auto-close-label">
<label class="form-check-label" for="collapse-relationships">Collapse Series Relationships</label>
</div>
</div>
<ng-template #noTransitionsTooltip>Experiemental: Should Kavita show Series that have no relationships or is the parent/prequel</ng-template>
<span class="visually-hidden" id="settings-collapse-relationships-help">Experiemental: Should Kavita show Series that have no relationships or is the parent/prequel</span>
</div>
</div>
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()" aria-describedby="reading-panel">Reset</button>
<button type="submit" class="flex-fill btn btn-primary" (click)="save()" aria-describedby="reading-panel" [disabled]="!settingsForm.dirty">Save</button>

View file

@ -156,6 +156,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
this.settingsForm.addControl('blurUnreadSummaries', new FormControl(this.user.preferences.blurUnreadSummaries, []));
this.settingsForm.addControl('promptForDownloadSize', new FormControl(this.user.preferences.promptForDownloadSize, []));
this.settingsForm.addControl('noTransitions', new FormControl(this.user.preferences.noTransitions, []));
this.settingsForm.addControl('collapseSeriesRelationships', new FormControl(this.user.preferences.collapseSeriesRelationships, []));
this.cdRef.markForCheck();
});
@ -202,6 +203,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
this.settingsForm.get('noTransitions')?.setValue(this.user.preferences.noTransitions);
this.settingsForm.get('emulateBook')?.setValue(this.user.preferences.emulateBook);
this.settingsForm.get('swipeToPaginate')?.setValue(this.user.preferences.swipeToPaginate);
this.settingsForm.get('collapseSeriesRelationships')?.setValue(this.user.preferences.collapseSeriesRelationships);
this.cdRef.markForCheck();
this.settingsForm.markAsPristine();
}
@ -234,7 +236,8 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
promptForDownloadSize: modelSettings.promptForDownloadSize,
noTransitions: modelSettings.noTransitions,
emulateBook: modelSettings.emulateBook,
swipeToPaginate: modelSettings.swipeToPaginate
swipeToPaginate: modelSettings.swipeToPaginate,
collapseSeriesRelationships: modelSettings.collapseSeriesRelationships
};
this.observableHandles.push(this.accountService.updatePreferences(data).subscribe((updatedPrefs) => {