Update some flows
- Promote/Create new from implicit profile - Fix save to parent updating implicit instead - Remove skip in the manga reader as the resolver has made it redundant - Add confirm to delete profile flow
This commit is contained in:
parent
9109988514
commit
45a44480e1
13 changed files with 289 additions and 106 deletions
|
|
@ -42,22 +42,6 @@ public class ReadingProfileController(ILogger<ReadingProfileController> logger,
|
|||
return Ok(await readingProfileService.GetReadingProfileDtoForSeries(User.GetUserId(), seriesId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the given reading profile, must belong to the current user
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This does not update connected series, and libraries.
|
||||
/// Deletes all implicit profiles for series linked to this profile
|
||||
/// </remarks>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> UpdateReadingProfile([FromBody] UserReadingProfileDto dto)
|
||||
{
|
||||
await readingProfileService.UpdateReadingProfile(User.GetUserId(), dto);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new reading profile for the current user
|
||||
/// </summary>
|
||||
|
|
@ -69,6 +53,17 @@ public class ReadingProfileController(ILogger<ReadingProfileController> logger,
|
|||
return Ok(await readingProfileService.CreateReadingProfile(User.GetUserId(), dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Promotes the implicit profile to a user profile. Removes the series from other profiles
|
||||
/// </summary>
|
||||
/// <param name="profileId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("promote")]
|
||||
public async Task<ActionResult<UserReadingProfileDto>> PromoteImplicitReadingProfile([FromQuery] int profileId)
|
||||
{
|
||||
return Ok(await readingProfileService.PromoteImplicitProfile(User.GetUserId(), profileId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the implicit reading profile for a series, creates one if none exists
|
||||
/// </summary>
|
||||
|
|
@ -76,10 +71,39 @@ public class ReadingProfileController(ILogger<ReadingProfileController> logger,
|
|||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("series")]
|
||||
public async Task<ActionResult> UpdateReadingProfileForSeries([FromBody] UserReadingProfileDto dto, [FromQuery] int seriesId)
|
||||
public async Task<ActionResult<UserReadingProfileDto>> UpdateReadingProfileForSeries([FromBody] UserReadingProfileDto dto, [FromQuery] int seriesId)
|
||||
{
|
||||
await readingProfileService.UpdateImplicitReadingProfile(User.GetUserId(), seriesId, dto);
|
||||
return Ok();
|
||||
var updatedProfile = await readingProfileService.UpdateImplicitReadingProfile(User.GetUserId(), seriesId, dto);
|
||||
return Ok(updatedProfile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the non-implicit reading profile for the given series, and removes implicit profiles
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("update-parent")]
|
||||
public async Task<ActionResult<UserReadingProfileDto>> UpdateParentProfileForSeries([FromBody] UserReadingProfileDto dto, [FromQuery] int seriesId)
|
||||
{
|
||||
var newParentProfile = await readingProfileService.UpdateParent(User.GetUserId(), seriesId, dto);
|
||||
return Ok(newParentProfile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the given reading profile, must belong to the current user
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This does not update connected series, and libraries.
|
||||
/// Deletes all implicit profiles for series linked to this profile
|
||||
/// </remarks>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<UserReadingProfileDto>> UpdateReadingProfile([FromBody] UserReadingProfileDto dto)
|
||||
{
|
||||
var newProfile = await readingProfileService.UpdateReadingProfile(User.GetUserId(), dto);
|
||||
return Ok(newProfile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -107,8 +107,6 @@ public class UsersController : BaseApiController
|
|||
existingPreferences.BlurUnreadSummaries = preferencesDto.BlurUnreadSummaries;
|
||||
existingPreferences.PromptForDownloadSize = preferencesDto.PromptForDownloadSize;
|
||||
existingPreferences.NoTransitions = preferencesDto.NoTransitions;
|
||||
existingPreferences.SwipeToPaginate = preferencesDto.SwipeToPaginate;
|
||||
existingPreferences.AllowAutomaticWebtoonReaderDetection = preferencesDto.AllowAutomaticWebtoonReaderDetection;
|
||||
existingPreferences.CollapseSeriesRelationships = preferencesDto.CollapseSeriesRelationships;
|
||||
existingPreferences.ShareReviews = preferencesDto.ShareReviews;
|
||||
|
||||
|
|
|
|||
|
|
@ -230,6 +230,8 @@
|
|||
"scan-libraries": "Scan Libraries",
|
||||
"kavita+-data-refresh": "Kavita+ Data Refresh",
|
||||
"backup": "Backup",
|
||||
"update-yearly-stats": "Update Yearly Stats"
|
||||
"update-yearly-stats": "Update Yearly Stats",
|
||||
|
||||
"generated-reading-profile-name": "Generated from {0}"
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,15 +25,6 @@ public interface IReadingProfileService
|
|||
/// <returns></returns>
|
||||
Task<UserReadingProfileDto> GetReadingProfileDtoForSeries(int userId, int seriesId);
|
||||
|
||||
/// <summary>
|
||||
/// Updates a given reading profile for a user, and deletes all implicit profiles
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>Does not update connected series and libraries</remarks>
|
||||
Task UpdateReadingProfile(int userId, UserReadingProfileDto dto);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new reading profile for a user. Name must be unique per user
|
||||
/// </summary>
|
||||
|
|
@ -42,6 +33,14 @@ public interface IReadingProfileService
|
|||
/// <returns></returns>
|
||||
Task<UserReadingProfileDto> CreateReadingProfile(int userId, UserReadingProfileDto dto);
|
||||
|
||||
/// <summary>
|
||||
/// Promotes the implicit profile to a user profile. Removes the series from other profiles
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="profileId"></param>
|
||||
/// <returns></returns>
|
||||
Task<UserReadingProfileDto> PromoteImplicitProfile(int userId, int profileId);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the implicit reading profile for a series, creates one if none exists
|
||||
/// </summary>
|
||||
|
|
@ -49,7 +48,25 @@ public interface IReadingProfileService
|
|||
/// <param name="seriesId"></param>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
Task UpdateImplicitReadingProfile(int userId, int seriesId, UserReadingProfileDto dto);
|
||||
Task<UserReadingProfileDto> UpdateImplicitReadingProfile(int userId, int seriesId, UserReadingProfileDto dto);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the non-implicit reading profile for the given series, and removes implicit profiles
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
Task<UserReadingProfileDto> UpdateParent(int userId, int seriesId, UserReadingProfileDto dto);
|
||||
|
||||
/// <summary>
|
||||
/// Updates a given reading profile for a user, and deletes all implicit profiles
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>Does not update connected series and libraries</remarks>
|
||||
Task<UserReadingProfileDto> UpdateReadingProfile(int userId, UserReadingProfileDto dto);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a given profile for a user
|
||||
|
|
@ -111,13 +128,16 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
|||
return mapper.Map<UserReadingProfileDto>(await GetReadingProfileForSeries(userId, seriesId));
|
||||
}
|
||||
|
||||
public async Task<AppUserReadingProfile> GetReadingProfileForSeries(int userId, int seriesId)
|
||||
public async Task<AppUserReadingProfile> GetReadingProfileForSeries(int userId, int seriesId, bool skipImplicit = false)
|
||||
{
|
||||
var profiles = await unitOfWork.AppUserReadingProfileRepository.GetProfilesForUser(userId);
|
||||
|
||||
var implicitSeriesProfile = profiles
|
||||
.FirstOrDefault(p => p.SeriesIds.Contains(seriesId) && p.Kind == ReadingProfileKind.Implicit);
|
||||
if (implicitSeriesProfile != null) return implicitSeriesProfile;
|
||||
if (!skipImplicit)
|
||||
{
|
||||
var implicitSeriesProfile = profiles
|
||||
.FirstOrDefault(p => p.SeriesIds.Contains(seriesId) && p.Kind == ReadingProfileKind.Implicit);
|
||||
if (implicitSeriesProfile != null) return implicitSeriesProfile;
|
||||
}
|
||||
|
||||
var seriesProfile = profiles
|
||||
.FirstOrDefault(p => p.SeriesIds.Contains(seriesId) && p.Kind != ReadingProfileKind.Implicit);
|
||||
|
|
@ -134,7 +154,20 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
|||
return profiles.First(p => p.Kind == ReadingProfileKind.Default);
|
||||
}
|
||||
|
||||
public async Task UpdateReadingProfile(int userId, UserReadingProfileDto dto)
|
||||
public async Task<UserReadingProfileDto> UpdateParent(int userId, int seriesId, UserReadingProfileDto dto)
|
||||
{
|
||||
var parentProfile = await GetReadingProfileForSeries(userId, seriesId, true);
|
||||
|
||||
UpdateReaderProfileFields(parentProfile, dto, false);
|
||||
unitOfWork.AppUserReadingProfileRepository.Update(parentProfile);
|
||||
|
||||
await DeleteImplicateReadingProfilesForSeries(userId, [seriesId]);
|
||||
|
||||
await unitOfWork.CommitAsync();
|
||||
return mapper.Map<UserReadingProfileDto>(parentProfile);
|
||||
}
|
||||
|
||||
public async Task<UserReadingProfileDto> UpdateReadingProfile(int userId, UserReadingProfileDto dto)
|
||||
{
|
||||
var profile = await unitOfWork.AppUserReadingProfileRepository.GetUserProfile(userId, dto.Id);
|
||||
if (profile == null) throw new KavitaException("profile-does-not-exist");
|
||||
|
|
@ -145,6 +178,7 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
|||
await DeleteImplicateReadingProfilesForSeries(userId, profile.SeriesIds);
|
||||
|
||||
await unitOfWork.CommitAsync();
|
||||
return mapper.Map<UserReadingProfileDto>(profile);
|
||||
}
|
||||
|
||||
public async Task<UserReadingProfileDto> CreateReadingProfile(int userId, UserReadingProfileDto dto)
|
||||
|
|
@ -165,7 +199,30 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
|||
return mapper.Map<UserReadingProfileDto>(newProfile);
|
||||
}
|
||||
|
||||
public async Task UpdateImplicitReadingProfile(int userId, int seriesId, UserReadingProfileDto dto)
|
||||
public async Task<UserReadingProfileDto> PromoteImplicitProfile(int userId, int profileId)
|
||||
{
|
||||
var profile = await unitOfWork.AppUserReadingProfileRepository.GetUserProfile(userId, profileId);
|
||||
if (profile == null) throw new KavitaException("profile-does-not-exist");
|
||||
|
||||
if (profile.Kind != ReadingProfileKind.Implicit) throw new KavitaException("cannot-promote-non-implicit-profile");
|
||||
|
||||
// Implicit profiles are only bound to one profile
|
||||
var series = await unitOfWork.SeriesRepository.GetSeriesByIdAsync(profile.SeriesIds[0]);
|
||||
if (series == null) throw new KavitaException("series-doesnt-exist"); // Shouldn't happen
|
||||
|
||||
await RemoveSeriesFromUserProfiles(userId, [series.Id]);
|
||||
|
||||
profile.Kind = ReadingProfileKind.User;
|
||||
profile.Name = await localizationService.Translate(userId, "generated-reading-profile-name", series.Name);
|
||||
profile.NormalizedName = profile.Name.ToNormalized();
|
||||
|
||||
unitOfWork.AppUserReadingProfileRepository.Update(profile);
|
||||
await unitOfWork.CommitAsync();
|
||||
|
||||
return mapper.Map<UserReadingProfileDto>(profile);
|
||||
}
|
||||
|
||||
public async Task<UserReadingProfileDto> UpdateImplicitReadingProfile(int userId, int seriesId, UserReadingProfileDto dto)
|
||||
{
|
||||
var user = await unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.UserPreferences);
|
||||
if (user == null) throw new UnauthorizedAccessException();
|
||||
|
|
@ -179,7 +236,8 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
|||
UpdateReaderProfileFields(existingProfile, dto, false);
|
||||
unitOfWork.AppUserReadingProfileRepository.Update(existingProfile);
|
||||
await unitOfWork.CommitAsync();
|
||||
return;
|
||||
|
||||
return mapper.Map<UserReadingProfileDto>(existingProfile);
|
||||
}
|
||||
|
||||
var series = await unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId) ?? throw new KeyNotFoundException();
|
||||
|
|
@ -195,6 +253,8 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
|||
|
||||
user.ReadingProfiles.Add(newProfile);
|
||||
await unitOfWork.CommitAsync();
|
||||
|
||||
return mapper.Map<UserReadingProfileDto>(newProfile);
|
||||
}
|
||||
|
||||
public async Task DeleteReadingProfile(int userId, int profileId)
|
||||
|
|
@ -300,6 +360,16 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
|||
unitOfWork.AppUserReadingProfileRepository.RemoveRange(implicitProfiles);
|
||||
}
|
||||
|
||||
private async Task RemoveSeriesFromUserProfiles(int userId, IList<int> seriesIds)
|
||||
{
|
||||
var profiles = await unitOfWork.AppUserReadingProfileRepository.GetProfilesForUser(userId);
|
||||
var userProfiles = profiles
|
||||
.Where(rp => rp.SeriesIds.Intersect(seriesIds).Any())
|
||||
.Where(rp => rp.Kind == ReadingProfileKind.User)
|
||||
.ToList();
|
||||
unitOfWork.AppUserReadingProfileRepository.RemoveRange(userProfiles);
|
||||
}
|
||||
|
||||
public static void UpdateReaderProfileFields(AppUserReadingProfile existingProfile, UserReadingProfileDto dto, bool updateName = true)
|
||||
{
|
||||
if (updateName && !string.IsNullOrEmpty(dto.Name) && existingProfile.NormalizedName != dto.Name.ToNormalized())
|
||||
|
|
|
|||
|
|
@ -17,15 +17,23 @@ export class ReadingProfileService {
|
|||
}
|
||||
|
||||
updateProfile(profile: ReadingProfile) {
|
||||
return this.httpClient.post(this.baseUrl + "ReadingProfile", profile);
|
||||
return this.httpClient.post<ReadingProfile>(this.baseUrl + "ReadingProfile", profile);
|
||||
}
|
||||
|
||||
updateParentProfile(seriesId: number, profile: ReadingProfile) {
|
||||
return this.httpClient.post<ReadingProfile>(this.baseUrl + `ReadingProfile/update-parent?seriesId=${seriesId}`, profile);
|
||||
}
|
||||
|
||||
createProfile(profile: ReadingProfile) {
|
||||
return this.httpClient.post<ReadingProfile>(this.baseUrl + "ReadingProfile/create", profile);
|
||||
}
|
||||
|
||||
promoteProfile(profileId: number) {
|
||||
return this.httpClient.post<ReadingProfile>(this.baseUrl + "ReadingProfile/promote?profileId=" + profileId, {});
|
||||
}
|
||||
|
||||
updateImplicit(profile: ReadingProfile, seriesId: number) {
|
||||
return this.httpClient.post(this.baseUrl + "ReadingProfile/series?seriesId="+seriesId, profile);
|
||||
return this.httpClient.post<ReadingProfile>(this.baseUrl + "ReadingProfile/series?seriesId="+seriesId, profile);
|
||||
}
|
||||
|
||||
all() {
|
||||
|
|
|
|||
|
|
@ -618,7 +618,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.router.navigateByUrl('/home');
|
||||
return;
|
||||
}
|
||||
//this.setupReaderSettings(); // TODO: Implement this Amelia
|
||||
//this.setupReaderSettings(); // TODO: Implement this Amelia, it works without am I missing something?
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -168,10 +168,21 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 justify-content-between mt-2">
|
||||
<button (click)="savePref()" class="btn btn-primary col">{{t('save-global')}}</button>
|
||||
<div class="d-flex gap-2 mt-2 flex-wrap mb-2">
|
||||
<button class="btn btn-primary"
|
||||
[disabled]="readingProfile.kind !== ReadingProfileKind.Implicit"
|
||||
(click)="updateParentPref()">
|
||||
{{ t('update-parent') }}
|
||||
</button>
|
||||
<button class="btn btn-primary"
|
||||
[ngbTooltip]="t('create-new-tooltip')"
|
||||
[disabled]="readingProfile.kind !== ReadingProfileKind.Implicit"
|
||||
(click)="createNewProfileFromImplicit()">
|
||||
{{ t('create-new') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -36,10 +36,11 @@ import {
|
|||
NgbAccordionItem,
|
||||
NgbTooltip
|
||||
} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
||||
import {ReadingProfileService} from "../../../_services/reading-profile.service";
|
||||
import {ReadingProfile} from "../../../_models/preferences/reading-profiles";
|
||||
import {ReadingProfile, ReadingProfileKind} from "../../../_models/preferences/reading-profiles";
|
||||
import {debounceTime, distinctUntilChanged, tap} from "rxjs/operators";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
/**
|
||||
* Used for book reader. Do not use for other components
|
||||
|
|
@ -185,7 +186,8 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
|
||||
constructor(private bookService: BookService, private accountService: AccountService,
|
||||
@Inject(DOCUMENT) private document: Document, private themeService: ThemeService,
|
||||
private readonly cdRef: ChangeDetectorRef, private readingProfileService: ReadingProfileService) {}
|
||||
private readonly cdRef: ChangeDetectorRef, private readingProfileService: ReadingProfileService,
|
||||
private toastr: ToastrService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
|
|
@ -226,14 +228,13 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
this.layoutModeUpdate.emit(this.readingProfile.bookReaderLayoutMode);
|
||||
this.immersiveMode.emit(this.readingProfile.bookReaderImmersiveMode);
|
||||
|
||||
this.resetSettings();
|
||||
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
if (user) {
|
||||
this.user = user;
|
||||
} else {
|
||||
this.resetSettings();
|
||||
}
|
||||
|
||||
// User needs to be loaded before we call this
|
||||
this.resetSettings();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -393,8 +394,29 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
this.fullscreen.emit();
|
||||
}
|
||||
|
||||
savePref() {
|
||||
this.readingProfileService.updateProfile(this.packReadingProfile()).subscribe()
|
||||
// menu only code
|
||||
updateParentPref() {
|
||||
if (this.readingProfile.kind !== ReadingProfileKind.Implicit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.readingProfileService.updateParentProfile(this.seriesId, this.packReadingProfile()).subscribe(newProfile => {
|
||||
this.readingProfile = newProfile;
|
||||
this.toastr.success(translate('manga-reader.reading-profile-updated'));
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
createNewProfileFromImplicit() {
|
||||
if (this.readingProfile.kind !== ReadingProfileKind.Implicit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.readingProfileService.promoteProfile(this.readingProfile.id).subscribe(newProfile => {
|
||||
this.readingProfile = newProfile;
|
||||
this.toastr.success(translate("manga-reader.reading-profile-promoted"));
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
private packReadingProfile(): ReadingProfile {
|
||||
|
|
@ -417,4 +439,5 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
return data;
|
||||
}
|
||||
|
||||
protected readonly ReadingProfileKind = ReadingProfileKind;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -310,7 +310,8 @@
|
|||
|
||||
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<button class="btn btn-primary" (click)="savePref()">{{t('save-globally')}}</button>
|
||||
<button class="btn btn-primary" [disabled]="readingProfile.kind !== ReadingProfileKind.Implicit" (click)="updateParentPref()">{{t('update-parent')}}</button>
|
||||
<button class="btn btn-primary ms-2" [ngbTooltip]="t('create-new-tooltip')" [disabled]="readingProfile.kind !== ReadingProfileKind.Implicit" (click)="createNewProfileFromImplicit()">{{t('create-new')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import {
|
|||
merge,
|
||||
Observable,
|
||||
ReplaySubject,
|
||||
skip,
|
||||
Subject,
|
||||
take,
|
||||
tap
|
||||
|
|
@ -33,7 +32,7 @@ import {
|
|||
import {ChangeContext, LabelType, NgxSliderModule, Options} from '@angular-slider/ngx-slider';
|
||||
import {animate, state, style, transition, trigger} from '@angular/animations';
|
||||
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {NgbModal, NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {ToastrService} from 'ngx-toastr';
|
||||
import {ShortcutsModalComponent} from 'src/app/reader-shared/_modals/shortcuts-modal/shortcuts-modal.component';
|
||||
import {Stack} from 'src/app/shared/data-structures/stack';
|
||||
|
|
@ -70,7 +69,12 @@ import {LoadingComponent} from '../../../shared/loading/loading.component';
|
|||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
||||
import {shareReplay} from "rxjs/operators";
|
||||
import {DblClickDirective} from "../../../_directives/dbl-click.directive";
|
||||
import {layoutModes, pageSplitOptions, ReadingProfile} from "../../../_models/preferences/reading-profiles";
|
||||
import {
|
||||
layoutModes,
|
||||
pageSplitOptions,
|
||||
ReadingProfile,
|
||||
ReadingProfileKind
|
||||
} from "../../../_models/preferences/reading-profiles";
|
||||
import {ReadingProfileService} from "../../../_services/reading-profile.service";
|
||||
import {ConfirmService} from "../../../shared/confirm.service";
|
||||
|
||||
|
|
@ -125,10 +129,10 @@ enum KeyDirection {
|
|||
])
|
||||
])
|
||||
],
|
||||
imports: [NgStyle, LoadingComponent, SwipeDirective, CanvasRendererComponent, SingleRendererComponent,
|
||||
DoubleRendererComponent, DoubleReverseRendererComponent, DoubleNoCoverRendererComponent, InfiniteScrollerComponent,
|
||||
NgxSliderModule, ReactiveFormsModule, FittingIconPipe, ReaderModeIconPipe,
|
||||
FullscreenIconPipe, TranslocoDirective, PercentPipe, NgClass, AsyncPipe, DblClickDirective]
|
||||
imports: [NgStyle, LoadingComponent, SwipeDirective, CanvasRendererComponent, SingleRendererComponent,
|
||||
DoubleRendererComponent, DoubleReverseRendererComponent, DoubleNoCoverRendererComponent, InfiniteScrollerComponent,
|
||||
NgxSliderModule, ReactiveFormsModule, FittingIconPipe, ReaderModeIconPipe,
|
||||
FullscreenIconPipe, TranslocoDirective, PercentPipe, NgClass, AsyncPipe, DblClickDirective, NgbTooltip]
|
||||
})
|
||||
export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
|
|
@ -537,14 +541,13 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.generalSettingsForm.valueChanges.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
skip(1), // Skip the initial creation of the form, we do not want an implicit profile of this snapshot
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map(_ => this.packReadingProfile()),
|
||||
distinctUntilChanged(),
|
||||
tap(newProfile => {
|
||||
this.readingProfileService.updateImplicit(newProfile, this.seriesId).subscribe({
|
||||
next: () => {
|
||||
this.readingProfile = newProfile;
|
||||
next: updatedProfile => {
|
||||
this.readingProfile = updatedProfile;
|
||||
},
|
||||
error: err => {
|
||||
console.error(err);
|
||||
|
|
@ -1783,10 +1786,28 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
// menu only code
|
||||
savePref() {
|
||||
this.readingProfileService.updateProfile(this.packReadingProfile()).subscribe(_ => {
|
||||
updateParentPref() {
|
||||
if (this.readingProfile.kind !== ReadingProfileKind.Implicit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.readingProfileService.updateParentProfile(this.seriesId, this.packReadingProfile()).subscribe(newProfile => {
|
||||
this.readingProfile = newProfile;
|
||||
this.toastr.success(translate('manga-reader.reading-profile-updated'));
|
||||
})
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
createNewProfileFromImplicit() {
|
||||
if (this.readingProfile.kind !== ReadingProfileKind.Implicit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.readingProfileService.promoteProfile(this.readingProfile.id).subscribe(newProfile => {
|
||||
this.readingProfile = newProfile;
|
||||
this.toastr.success(translate("manga-reader.reading-profile-promoted"));
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
translatePrefOptions(o: {text: string, value: any}) {
|
||||
|
|
@ -1811,4 +1832,5 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
return data;
|
||||
}
|
||||
|
||||
protected readonly ReadingProfileKind = ReadingProfileKind;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
@if (!loading) {
|
||||
<div class="position-relative">
|
||||
<button class="btn btn-outline-primary position-absolute custom-position" (click)="addNew()" [title]="t('add')">
|
||||
<button class="btn btn-outline-primary position-absolute custom-position" [ngbTooltip]="t('add-tooltip')" (click)="addNew()" [title]="t('add')">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden ms-1">{{t('add')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
|
||||
@if (selectedProfile.id !== 0) {
|
||||
<div class="d-flex justify-content-between">
|
||||
<button class="btn btn-danger" (click)="delete(selectedProfile!.id)" [disabled]="selectedProfile.kind === ReadingProfileKind.Default">
|
||||
<button class="btn btn-danger" (click)="delete(selectedProfile!)" [disabled]="selectedProfile.kind === ReadingProfileKind.Default">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
ReadingProfileKind,
|
||||
scalingOptions
|
||||
} from "../../_models/preferences/reading-profiles";
|
||||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
||||
import {translate, TranslocoDirective, TranslocoService} from "@jsverse/transloco";
|
||||
import {NgStyle, NgTemplateOutlet, TitleCasePipe} from "@angular/common";
|
||||
import {VirtualScrollerModule} from "@iharbeck/ngx-virtual-scroller";
|
||||
import {User} from "../../_models/user";
|
||||
|
|
@ -41,11 +41,12 @@ import {SettingItemComponent} from "../../settings/_components/setting-item/sett
|
|||
import {SettingSwitchComponent} from "../../settings/_components/setting-switch/setting-switch.component";
|
||||
import {WritingStylePipe} from "../../_pipes/writing-style.pipe";
|
||||
import {ColorPickerDirective} from "ngx-color-picker";
|
||||
import {NgbNav, NgbNavContent, NgbNavItem, NgbNavLinkBase, NgbNavOutlet} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {NgbNav, NgbNavContent, NgbNavItem, NgbNavLinkBase, NgbNavOutlet, NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {filter} from "rxjs";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {LoadingComponent} from "../../shared/loading/loading.component";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {ConfirmService} from "../../shared/confirm.service";
|
||||
|
||||
enum TabId {
|
||||
ImageReader = "image-reader",
|
||||
|
|
@ -83,6 +84,7 @@ enum TabId {
|
|||
NgbNavContent,
|
||||
NgbNavOutlet,
|
||||
LoadingComponent,
|
||||
NgbTooltip,
|
||||
],
|
||||
templateUrl: './manage-reading-profiles.component.html',
|
||||
styleUrl: './manage-reading-profiles.component.scss',
|
||||
|
|
@ -113,6 +115,8 @@ export class ManageReadingProfilesComponent implements OnInit {
|
|||
private bookService: BookService,
|
||||
private destroyRef: DestroyRef,
|
||||
private toastr: ToastrService,
|
||||
private confirmService: ConfirmService,
|
||||
private transLoco: TranslocoService,
|
||||
) {
|
||||
this.fontFamilies = this.bookService.getFontFamilies().map(f => f.title);
|
||||
this.cdRef.markForCheck();
|
||||
|
|
@ -129,15 +133,24 @@ export class ManageReadingProfilesComponent implements OnInit {
|
|||
this.readingProfiles = profiles;
|
||||
this.loading = false;
|
||||
this.setupForm();
|
||||
|
||||
const defaultProfile = this.readingProfiles.find(rp => rp.kind === ReadingProfileKind.Default);
|
||||
this.selectProfile(defaultProfile);
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
delete(id: number) {
|
||||
this.readingProfileService.delete(id).subscribe(() => {
|
||||
async delete(readingProfile: ReadingProfile) {
|
||||
if (!await this.confirmService.confirm(this.transLoco.translate("manage-reading-profiles.confirm", {name: readingProfile.name}))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.readingProfileService.delete(readingProfile.id).subscribe(() => {
|
||||
this.selectProfile(undefined);
|
||||
this.readingProfiles = this.readingProfiles.filter(o => o.id !== id);
|
||||
this.readingProfiles = this.readingProfiles.filter(o => o.id !== readingProfile.id);
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
|
@ -204,39 +217,41 @@ export class ManageReadingProfilesComponent implements OnInit {
|
|||
distinctUntilChanged(),
|
||||
filter(_ => this.readingProfileForm!.valid),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
tap(_ => {
|
||||
if (this.selectedProfile!.id == 0) {
|
||||
this.readingProfileService.createProfile(this.packData()).subscribe({
|
||||
next: createdProfile => {
|
||||
this.selectedProfile = createdProfile;
|
||||
this.readingProfiles.push(createdProfile);
|
||||
this.cdRef.markForCheck();
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
this.toastr.error(err.message);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const profile = this.packData();
|
||||
this.readingProfileService.updateProfile(profile).subscribe({
|
||||
next: _ => {
|
||||
this.readingProfiles = this.readingProfiles.map(p => {
|
||||
if (p.id !== profile.id) return p;
|
||||
return profile;
|
||||
});
|
||||
this.cdRef.markForCheck();
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
this.toastr.error(err.message);
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
tap(_ => this.autoSave()),
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
private autoSave() {
|
||||
if (this.selectedProfile!.id == 0) {
|
||||
this.readingProfileService.createProfile(this.packData()).subscribe({
|
||||
next: createdProfile => {
|
||||
this.selectedProfile = createdProfile;
|
||||
this.readingProfiles.push(createdProfile);
|
||||
this.cdRef.markForCheck();
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
this.toastr.error(err.message);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const profile = this.packData();
|
||||
this.readingProfileService.updateProfile(profile).subscribe({
|
||||
next: _ => {
|
||||
this.readingProfiles = this.readingProfiles.map(p => {
|
||||
if (p.id !== profile.id) return p;
|
||||
return profile;
|
||||
});
|
||||
this.cdRef.markForCheck();
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
this.toastr.error(err.message);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private packData(): ReadingProfile {
|
||||
const data: ReadingProfile = this.readingProfileForm!.getRawValue();
|
||||
data.id = this.selectedProfile!.id;
|
||||
|
|
|
|||
|
|
@ -1132,7 +1132,11 @@
|
|||
"line-spacing-label": "{{manage-reading-profiles.line-height-book-label}}",
|
||||
"margin-label": "{{manage-reading-profiles.margin-book-label}}",
|
||||
"reset-to-defaults": "Reset to Defaults",
|
||||
"save-global": "Save to your reading profile",
|
||||
"update-parent": "Update parent profile",
|
||||
"create-new": "Promote profile",
|
||||
"create-new-tooltip": "Create a new manageable profile from your current implicit one",
|
||||
"reading-profile-updated": "Reading profile updated",
|
||||
"reading-profile-promoted": "Reading profile promoted",
|
||||
"reader-settings-title": "Reader Settings",
|
||||
"reading-direction-label": "{{manage-reading-profiles.reading-direction-book-label}}",
|
||||
"right-to-left": "Right to Left",
|
||||
|
|
@ -1948,7 +1952,9 @@
|
|||
|
||||
"manga-reader": {
|
||||
"back": "Back",
|
||||
"save-globally": "Save Globally",
|
||||
"update-parent": "Update parent profile",
|
||||
"create-new": "Promote profile",
|
||||
"create-new-tooltip": "Create a new manageable profile from your current implicit one",
|
||||
"incognito-alt": "Incognito mode is on. Toggle to turn off.",
|
||||
"incognito-title": "Incognito Mode:",
|
||||
"shortcuts-menu-alt": "Keyboard Shortcuts Modal",
|
||||
|
|
@ -1985,6 +1991,7 @@
|
|||
"no-next-chapter": "No Next Chapter",
|
||||
"no-prev-chapter": "No Previous Chapter",
|
||||
"reading-profile-updated": "Reading profile updated",
|
||||
"reading-profile-promoted": "Reading profile promoted",
|
||||
"emulate-comic-book-label": "{{manage-reading-profiles.emulate-comic-book-label}}",
|
||||
"series-progress": "Series Progress: {{percentage}}"
|
||||
},
|
||||
|
|
@ -2813,8 +2820,10 @@
|
|||
"profiles-title": "Your reading profiles",
|
||||
"default-profile": "Default",
|
||||
"add": "{{common.add}}",
|
||||
"add-tooltip": "Your new profile will be saved after making a change to it",
|
||||
"make-default": "Set as default",
|
||||
"no-selected": "No profile selected",
|
||||
"confirm": "Are you sure you want to delete the reading profile {{name}}?",
|
||||
"selection-tip": "Select a profile from the list, or create a new one at the top right",
|
||||
|
||||
"image-reader-settings-title": "Image Reader",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue