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:
parent
c62e594792
commit
bd19b282d5
63 changed files with 2186 additions and 239 deletions
|
@ -10,4 +10,5 @@ export interface Member {
|
|||
roles: string[];
|
||||
libraries: Library[];
|
||||
ageRestriction: AgeRestriction;
|
||||
isPending: boolean;
|
||||
}
|
|
@ -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}];
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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"> 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"> 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">
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -53,3 +53,7 @@
|
|||
.info-container {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
::ng-deep .progress {
|
||||
border-radius: 0px;
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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});
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue