diff --git a/API/Controllers/ReadingProfileController.cs b/API/Controllers/ReadingProfileController.cs index da6232836..596a00fef 100644 --- a/API/Controllers/ReadingProfileController.cs +++ b/API/Controllers/ReadingProfileController.cs @@ -42,22 +42,6 @@ public class ReadingProfileController(ILogger logger, return Ok(await readingProfileService.GetReadingProfileDtoForSeries(User.GetUserId(), seriesId)); } - /// - /// Updates the given reading profile, must belong to the current user - /// - /// - /// - /// - /// This does not update connected series, and libraries. - /// Deletes all implicit profiles for series linked to this profile - /// - [HttpPost] - public async Task UpdateReadingProfile([FromBody] UserReadingProfileDto dto) - { - await readingProfileService.UpdateReadingProfile(User.GetUserId(), dto); - return Ok(); - } - /// /// Creates a new reading profile for the current user /// @@ -69,6 +53,17 @@ public class ReadingProfileController(ILogger logger, return Ok(await readingProfileService.CreateReadingProfile(User.GetUserId(), dto)); } + /// + /// Promotes the implicit profile to a user profile. Removes the series from other profiles + /// + /// + /// + [HttpPost("promote")] + public async Task> PromoteImplicitReadingProfile([FromQuery] int profileId) + { + return Ok(await readingProfileService.PromoteImplicitProfile(User.GetUserId(), profileId)); + } + /// /// Update the implicit reading profile for a series, creates one if none exists /// @@ -76,10 +71,39 @@ public class ReadingProfileController(ILogger logger, /// /// [HttpPost("series")] - public async Task UpdateReadingProfileForSeries([FromBody] UserReadingProfileDto dto, [FromQuery] int seriesId) + public async Task> 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); + } + + /// + /// Updates the non-implicit reading profile for the given series, and removes implicit profiles + /// + /// + /// + /// + [HttpPost("update-parent")] + public async Task> UpdateParentProfileForSeries([FromBody] UserReadingProfileDto dto, [FromQuery] int seriesId) + { + var newParentProfile = await readingProfileService.UpdateParent(User.GetUserId(), seriesId, dto); + return Ok(newParentProfile); + } + + /// + /// Updates the given reading profile, must belong to the current user + /// + /// + /// + /// + /// This does not update connected series, and libraries. + /// Deletes all implicit profiles for series linked to this profile + /// + [HttpPost] + public async Task> UpdateReadingProfile([FromBody] UserReadingProfileDto dto) + { + var newProfile = await readingProfileService.UpdateReadingProfile(User.GetUserId(), dto); + return Ok(newProfile); } /// diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index 8e28d13f6..17ebc758e 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -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; diff --git a/API/I18N/en.json b/API/I18N/en.json index 5916bc63e..d3cd1ecd3 100644 --- a/API/I18N/en.json +++ b/API/I18N/en.json @@ -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}" } diff --git a/API/Services/ReadingProfileService.cs b/API/Services/ReadingProfileService.cs index 4d28ac1e1..46d7a6709 100644 --- a/API/Services/ReadingProfileService.cs +++ b/API/Services/ReadingProfileService.cs @@ -25,15 +25,6 @@ public interface IReadingProfileService /// Task GetReadingProfileDtoForSeries(int userId, int seriesId); - /// - /// Updates a given reading profile for a user, and deletes all implicit profiles - /// - /// - /// - /// - /// Does not update connected series and libraries - Task UpdateReadingProfile(int userId, UserReadingProfileDto dto); - /// /// Creates a new reading profile for a user. Name must be unique per user /// @@ -42,6 +33,14 @@ public interface IReadingProfileService /// Task CreateReadingProfile(int userId, UserReadingProfileDto dto); + /// + /// Promotes the implicit profile to a user profile. Removes the series from other profiles + /// + /// + /// + /// + Task PromoteImplicitProfile(int userId, int profileId); + /// /// Updates the implicit reading profile for a series, creates one if none exists /// @@ -49,7 +48,25 @@ public interface IReadingProfileService /// /// /// - Task UpdateImplicitReadingProfile(int userId, int seriesId, UserReadingProfileDto dto); + Task UpdateImplicitReadingProfile(int userId, int seriesId, UserReadingProfileDto dto); + + /// + /// Updates the non-implicit reading profile for the given series, and removes implicit profiles + /// + /// + /// + /// + /// + Task UpdateParent(int userId, int seriesId, UserReadingProfileDto dto); + + /// + /// Updates a given reading profile for a user, and deletes all implicit profiles + /// + /// + /// + /// + /// Does not update connected series and libraries + Task UpdateReadingProfile(int userId, UserReadingProfileDto dto); /// /// Deletes a given profile for a user @@ -111,13 +128,16 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService return mapper.Map(await GetReadingProfileForSeries(userId, seriesId)); } - public async Task GetReadingProfileForSeries(int userId, int seriesId) + public async Task 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 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(parentProfile); + } + + public async Task 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(profile); } public async Task CreateReadingProfile(int userId, UserReadingProfileDto dto) @@ -165,7 +199,30 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService return mapper.Map(newProfile); } - public async Task UpdateImplicitReadingProfile(int userId, int seriesId, UserReadingProfileDto dto) + public async Task 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(profile); + } + + public async Task 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(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(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 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()) diff --git a/UI/Web/src/app/_services/reading-profile.service.ts b/UI/Web/src/app/_services/reading-profile.service.ts index 49ef94817..68e7c1059 100644 --- a/UI/Web/src/app/_services/reading-profile.service.ts +++ b/UI/Web/src/app/_services/reading-profile.service.ts @@ -17,15 +17,23 @@ export class ReadingProfileService { } updateProfile(profile: ReadingProfile) { - return this.httpClient.post(this.baseUrl + "ReadingProfile", profile); + return this.httpClient.post(this.baseUrl + "ReadingProfile", profile); + } + + updateParentProfile(seriesId: number, profile: ReadingProfile) { + return this.httpClient.post(this.baseUrl + `ReadingProfile/update-parent?seriesId=${seriesId}`, profile); } createProfile(profile: ReadingProfile) { return this.httpClient.post(this.baseUrl + "ReadingProfile/create", profile); } + promoteProfile(profileId: number) { + return this.httpClient.post(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(this.baseUrl + "ReadingProfile/series?seriesId="+seriesId, profile); } all() { diff --git a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts index 0fb91ba4d..005c2bc3d 100644 --- a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts +++ b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts @@ -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(); }); diff --git a/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.html b/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.html index a80369543..44ad0dfdf 100644 --- a/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.html +++ b/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.html @@ -168,10 +168,21 @@ -
- +
+ +
+
diff --git a/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.ts b/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.ts index 646c38d39..cd8421069 100644 --- a/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.ts +++ b/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.ts @@ -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; } diff --git a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html index 1d93f36bc..678610c18 100644 --- a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html +++ b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html @@ -310,7 +310,8 @@
- + +
diff --git a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts index c8ca9f13d..50e78184a 100644 --- a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts +++ b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts @@ -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; } diff --git a/UI/Web/src/app/user-settings/manage-reading-profiles/manage-reading-profiles.component.html b/UI/Web/src/app/user-settings/manage-reading-profiles/manage-reading-profiles.component.html index 8d4ec5ccd..97bbde8fd 100644 --- a/UI/Web/src/app/user-settings/manage-reading-profiles/manage-reading-profiles.component.html +++ b/UI/Web/src/app/user-settings/manage-reading-profiles/manage-reading-profiles.component.html @@ -4,7 +4,7 @@ @if (!loading) {
-
@@ -55,7 +55,7 @@ @if (selectedProfile.id !== 0) {
-
diff --git a/UI/Web/src/app/user-settings/manage-reading-profiles/manage-reading-profiles.component.ts b/UI/Web/src/app/user-settings/manage-reading-profiles/manage-reading-profiles.component.ts index 4c2343d7a..689358425 100644 --- a/UI/Web/src/app/user-settings/manage-reading-profiles/manage-reading-profiles.component.ts +++ b/UI/Web/src/app/user-settings/manage-reading-profiles/manage-reading-profiles.component.ts @@ -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; diff --git a/UI/Web/src/assets/langs/en.json b/UI/Web/src/assets/langs/en.json index 2a576b180..ffe093372 100644 --- a/UI/Web/src/assets/langs/en.json +++ b/UI/Web/src/assets/langs/en.json @@ -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",