From c62e48f06a9a408669ba14b1711fb9f0fa3ef692 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Sat, 28 Oct 2023 15:11:45 -0500 Subject: [PATCH 1/8] Added a new API for Panels so they can sync with a dedicated api. Removed ability to save progress with Panels. --- API/Controllers/OPDSController.cs | 20 ++++++++----- API/Controllers/PanelsController.cs | 33 +++++++++++++++++++++ openapi.json | 46 ++++++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 API/Controllers/PanelsController.cs diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index 92597f903..b2bf04423 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -1102,14 +1102,20 @@ public class OpdsController : BaseApiController Response.AddCacheHeader(content); // Save progress for the user - await _readerService.SaveReadingProgress(new ProgressDto() + + var userAgent = Request.Headers["User-Agent"].ToString(); + if (!userAgent.Equals("panels", StringComparison.InvariantCultureIgnoreCase)) { - ChapterId = chapterId, - PageNum = pageNumber, - SeriesId = seriesId, - VolumeId = volumeId, - LibraryId =libraryId - }, await GetUser(apiKey)); + await _readerService.SaveReadingProgress(new ProgressDto() + { + ChapterId = chapterId, + PageNum = pageNumber, + SeriesId = seriesId, + VolumeId = volumeId, + LibraryId =libraryId + }, await GetUser(apiKey)); + } + return File(content, MimeTypeMap.GetMimeType(format)); } diff --git a/API/Controllers/PanelsController.cs b/API/Controllers/PanelsController.cs new file mode 100644 index 000000000..809b778f2 --- /dev/null +++ b/API/Controllers/PanelsController.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using API.Data; +using API.DTOs; +using API.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace API.Controllers; + +/// +/// For the Panels app explicitly +/// +[AllowAnonymous] +public class PanelsController : BaseApiController +{ + private readonly IReaderService _readerService; + private readonly IUnitOfWork _unitOfWork; + + public PanelsController(IReaderService readerService, IUnitOfWork unitOfWork) + { + _readerService = readerService; + _unitOfWork = unitOfWork; + } + + [HttpPost("save-progress")] + public async Task SaveProgress(ProgressDto dto, [FromQuery] string apiKey) + { + if (string.IsNullOrEmpty(apiKey)) return Unauthorized("ApiKey is required"); + var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey); + await _readerService.SaveReadingProgress(dto, userId); + return Ok(); + } +} diff --git a/openapi.json b/openapi.json index 4c339ed08..f8ff5c23c 100644 --- a/openapi.json +++ b/openapi.json @@ -7,7 +7,7 @@ "name": "GPL-3.0", "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" }, - "version": "0.7.9.4" + "version": "0.7.10.0" }, "servers": [ { @@ -4168,6 +4168,46 @@ } } }, + "/api/Panels/save-progress": { + "post": { + "tags": [ + "Panels" + ], + "parameters": [ + { + "name": "apiKey", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProgressDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProgressDto" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/ProgressDto" + } + } + } + }, + "responses": { + "200": { + "description": "Success" + } + } + } + }, "/api/Plugin/authenticate": { "post": { "tags": [ @@ -19687,6 +19727,10 @@ "name": "Image", "description": "Responsible for servicing up images stored in Kavita for entities" }, + { + "name": "Panels", + "description": "For the Panels app explicitly" + }, { "name": "Rating", "description": "Responsible for providing external ratings for Series" From f7c1e3769445ba973701c256bf5489019e1c7316 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Sat, 28 Oct 2023 15:12:25 -0500 Subject: [PATCH 2/8] For Robbie to test --- API/Controllers/OPDSController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index b2bf04423..95b7f77d5 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -1104,6 +1104,7 @@ public class OpdsController : BaseApiController // Save progress for the user var userAgent = Request.Headers["User-Agent"].ToString(); + Console.WriteLine("User Agent: " + userAgent); if (!userAgent.Equals("panels", StringComparison.InvariantCultureIgnoreCase)) { await _readerService.SaveReadingProgress(new ProgressDto() From 34282f322210bf7f7cd634d034902f4e421a2e9f Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Sat, 28 Oct 2023 15:57:10 -0500 Subject: [PATCH 3/8] Fixed smart filters encode/decode issues from last release. --- API/Helpers/SmartFilterHelper.cs | 2 +- .../metadata-filter.component.ts | 62 +++++++++---------- .../_services/filter-utilities.service.ts | 4 +- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/API/Helpers/SmartFilterHelper.cs b/API/Helpers/SmartFilterHelper.cs index 30b66c3ee..740b8cd4e 100644 --- a/API/Helpers/SmartFilterHelper.cs +++ b/API/Helpers/SmartFilterHelper.cs @@ -72,7 +72,7 @@ public static class SmartFilterHelper private static string EncodeSortOptions(SortOptions sortOptions) { - return Uri.EscapeDataString($"sortField={(int) sortOptions.SortField}&isAscending={sortOptions.IsAscending}"); + return Uri.EscapeDataString($"sortField={(int) sortOptions.SortField},isAscending={sortOptions.IsAscending}"); } private static string EncodeFilterStatementDtos(ICollection statements) diff --git a/UI/Web/src/app/metadata-filter/metadata-filter.component.ts b/UI/Web/src/app/metadata-filter/metadata-filter.component.ts index fe8f55275..c5107e4f2 100644 --- a/UI/Web/src/app/metadata-filter/metadata-filter.component.ts +++ b/UI/Web/src/app/metadata-filter/metadata-filter.component.ts @@ -121,37 +121,37 @@ export class MetadataFilterComponent implements OnInit { this.loadFromPresetsAndSetup(); } - loadSavedFilter(event: Select2UpdateEvent) { - // Load the filter from the backend and update the screen - if (event.value === undefined || typeof(event.value) === 'string') return; - const smartFilter = event.value as SmartFilter; - this.filterV2 = this.filterUtilitiesService.decodeSeriesFilter(smartFilter.filter); - this.cdRef.markForCheck(); - console.log('update event: ', event); - } - - createFilterValue(event: Select2AutoCreateEvent) { - // Create a new name and filter - if (!this.filterV2) return; - this.filterV2.name = event.value; - this.filterService.saveFilter(this.filterV2).subscribe(() => { - - const item = { - value: { - filter: this.filterUtilitiesService.encodeSeriesFilter(this.filterV2!), - name: event.value, - } as SmartFilter, - label: event.value - }; - this.smartFilters.push(item); - this.sortGroup.get('name')?.setValue(item); - this.cdRef.markForCheck(); - this.toastr.success(translate('toasts.smart-filter-updated')); - this.apply(); - }); - - console.log('create event: ', event); - } + // loadSavedFilter(event: Select2UpdateEvent) { + // // Load the filter from the backend and update the screen + // if (event.value === undefined || typeof(event.value) === 'string') return; + // const smartFilter = event.value as SmartFilter; + // this.filterV2 = this.filterUtilitiesService.decodeSeriesFilter(smartFilter.filter); + // this.cdRef.markForCheck(); + // console.log('update event: ', event); + // } + // + // createFilterValue(event: Select2AutoCreateEvent) { + // // Create a new name and filter + // if (!this.filterV2) return; + // this.filterV2.name = event.value; + // this.filterService.saveFilter(this.filterV2).subscribe(() => { + // + // const item = { + // value: { + // filter: this.filterUtilitiesService.encodeSeriesFilter(this.filterV2!), + // name: event.value, + // } as SmartFilter, + // label: event.value + // }; + // this.smartFilters.push(item); + // this.sortGroup.get('name')?.setValue(item); + // this.cdRef.markForCheck(); + // this.toastr.success(translate('toasts.smart-filter-updated')); + // this.apply(); + // }); + // + // console.log('create event: ', event); + // } close() { diff --git a/UI/Web/src/app/shared/_services/filter-utilities.service.ts b/UI/Web/src/app/shared/_services/filter-utilities.service.ts index 1dc62124d..2b5bb647d 100644 --- a/UI/Web/src/app/shared/_services/filter-utilities.service.ts +++ b/UI/Web/src/app/shared/_services/filter-utilities.service.ts @@ -209,9 +209,9 @@ export class FilterUtilitiesService { } decodeFilterStatements(encodedStatements: string): FilterStatement[] { - const statementStrings = decodeURIComponent(encodedStatements).split(','); + const statementStrings = decodeURIComponent(encodedStatements).split(',').map(s => decodeURIComponent(s)); return statementStrings.map(statementString => { - const parts = statementString.split('&'); + const parts = statementString.split(','); if (parts === null || parts.length < 3) return null; const comparisonStartToken = parts.find(part => part.startsWith('comparison=')); From 0e43dae3d19052f5e117ad395498f2b045fb26f8 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Sat, 28 Oct 2023 16:05:34 -0500 Subject: [PATCH 4/8] Fixed the UA check --- API/Controllers/OPDSController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index 95b7f77d5..14dab489e 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -1105,7 +1105,7 @@ public class OpdsController : BaseApiController var userAgent = Request.Headers["User-Agent"].ToString(); Console.WriteLine("User Agent: " + userAgent); - if (!userAgent.Equals("panels", StringComparison.InvariantCultureIgnoreCase)) + if (!userAgent.Contains("panels", StringComparison.InvariantCultureIgnoreCase)) { await _readerService.SaveReadingProgress(new ProgressDto() { From d328528e6eb9788c9aa1d5accd8bfa3769c354bf Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Sat, 28 Oct 2023 16:30:57 -0500 Subject: [PATCH 5/8] Added a fade in on intial load. Would love to have everything in the virtual scroller invisible because they we can avoid the page shift. Added a bar on the bottom of compainion-bar. --- UI/Web/src/app/app.component.html | 2 +- UI/Web/src/app/app.component.scss | 14 ++++++++++++++ UI/Web/src/app/app.component.ts | 13 +++++++++++++ .../side-nav-companion-bar.component.html | 2 +- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/UI/Web/src/app/app.component.html b/UI/Web/src/app/app.component.html index 96c8833f6..12a48e5f8 100644 --- a/UI/Web/src/app/app.component.html +++ b/UI/Web/src/app/app.component.html @@ -4,7 +4,7 @@ -
+
diff --git a/UI/Web/src/app/app.component.scss b/UI/Web/src/app/app.component.scss index f36dbb0c3..396539e99 100644 --- a/UI/Web/src/app/app.component.scss +++ b/UI/Web/src/app/app.component.scss @@ -38,3 +38,17 @@ width: auto; } } + + +@keyframes fadeInUp { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +.fadein { + animation: 2s fadeInUp; +} diff --git a/UI/Web/src/app/app.component.ts b/UI/Web/src/app/app.component.ts index a23635277..36fac866d 100644 --- a/UI/Web/src/app/app.component.ts +++ b/UI/Web/src/app/app.component.ts @@ -12,17 +12,30 @@ import {ThemeService} from "./_services/theme.service"; import { SideNavComponent } from './sidenav/_components/side-nav/side-nav.component'; import {NavHeaderComponent} from "./nav/_components/nav-header/nav-header.component"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; +import {animate, state, style, transition, trigger} from "@angular/animations"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], standalone: true, + animations: [ + trigger('fadeIn', [ + // the "out" style determines the "resting" state of the element when it is visible. + state('out', style({opacity: 0})), + // in state + state('in', style({opacity: 1})), + + transition('out => in', animate('700ms ease-in')), + transition('in => out', animate('700ms ease-in')), + ]) + ], imports: [NgClass, NgIf, SideNavComponent, RouterOutlet, AsyncPipe, NavHeaderComponent] }) export class AppComponent implements OnInit { transitionState$!: Observable; + fade = 'out'; private readonly destroyRef = inject(DestroyRef); private readonly offcanvas = inject(NgbOffcanvas); diff --git a/UI/Web/src/app/sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component.html b/UI/Web/src/app/sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component.html index 9f4714f3b..c58712b93 100644 --- a/UI/Web/src/app/sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component.html +++ b/UI/Web/src/app/sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component.html @@ -1,5 +1,5 @@ -
+
From b4a07d8058977ae9de312952fca0f7d3a7ccbdc1 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Sat, 28 Oct 2023 16:41:20 -0500 Subject: [PATCH 6/8] Trying somethign with the jump bar keys to give a little shadow. Need robbie's help. --- .../cards/card-detail-layout/card-detail-layout.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss index f9d45420e..ffc87dadc 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss @@ -57,6 +57,7 @@ .btn { text-decoration: none; + text-shadow: 1.3px 0.5px 1px rgba(255, 255, 255, 0.4); // TODO: perfect this color: hsla(0,0%,100%,.7); height: 25px; text-align: center; From e705aadaac9e607922380744c804ca2e6d704e9f Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Sat, 28 Oct 2023 17:08:19 -0500 Subject: [PATCH 7/8] Some crazy ideas, search one is half baked and metadata-filter.component too. --- .../metadata-filter.component.html | 2 +- .../metadata-filter.component.ts | 32 ++++++++++++++++++- .../grouped-typeahead.component.html | 11 +++++++ .../side-nav-companion-bar.component.html | 2 +- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/UI/Web/src/app/metadata-filter/metadata-filter.component.html b/UI/Web/src/app/metadata-filter/metadata-filter.component.html index fc314f8db..3361064bb 100644 --- a/UI/Web/src/app/metadata-filter/metadata-filter.component.html +++ b/UI/Web/src/app/metadata-filter/metadata-filter.component.html @@ -24,7 +24,7 @@ -
+
*', [ + style({ transform: 'translateY(-100%)' }), + animate(ANIMATION_SPEED) + ]), + transition('* => void', [ + animate(ANIMATION_SPEED, style({ transform: 'translateY(-100%)' })), + ]) + ]), + trigger('slideFromBottom', [ + state('in', style({ transform: 'translateY(0)' })), + transition('void => *', [ + style({ transform: 'translateY(100%)' }), + animate(ANIMATION_SPEED) + ]), + transition('* => void', [ + animate(ANIMATION_SPEED, style({ transform: 'translateY(100%)' })), + ]) + ]) + ], }) export class MetadataFilterComponent implements OnInit { @@ -89,6 +114,8 @@ export class MetadataFilterComponent implements OnInit { smartFilters!: Array; + isOpen = false; + private readonly cdRef = inject(ChangeDetectorRef); private readonly toastr = inject(ToastrService); @@ -114,10 +141,13 @@ export class MetadataFilterComponent implements OnInit { this.filterOpen.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(openState => { this.filteringCollapsed = !openState; this.toggleService.set(!this.filteringCollapsed); + this.isOpen = openState; this.cdRef.markForCheck(); }); } + + this.loadFromPresetsAndSetup(); } diff --git a/UI/Web/src/app/nav/_components/grouped-typeahead/grouped-typeahead.component.html b/UI/Web/src/app/nav/_components/grouped-typeahead/grouped-typeahead.component.html index 8a7847511..ceceab2b3 100644 --- a/UI/Web/src/app/nav/_components/grouped-typeahead/grouped-typeahead.component.html +++ b/UI/Web/src/app/nav/_components/grouped-typeahead/grouped-typeahead.component.html @@ -15,6 +15,17 @@