Release Polish (#4648)

This commit is contained in:
Fesaa
2026-04-26 15:46:04 +02:00
committed by GitHub
parent 2142766e3b
commit b59dc302d1
8 changed files with 117 additions and 11 deletions
@@ -124,4 +124,6 @@ export enum Action {
* A special action to just navigate somewhere
*/
Navigate = 38,
AddToDashboard = 39,
AddToSideNav = 40,
}
+2 -2
View File
@@ -17,8 +17,8 @@ export enum WikiLink {
Scanner = 'https://wiki.kavitareader.com/guides/scanner',
ScannerExclude = 'https://wiki.kavitareader.com/guides/admin-settings/libraries#exclude-patterns',
Library = 'https://wiki.kavitareader.com/guides/admin-settings/libraries',
UpdateNative = 'https://wiki.kavitareader.com/guides/updating/updating-native',
UpdateDocker = 'https://wiki.kavitareader.com/guides/updating/updating-docker',
UpdateNative = 'https://wiki.kavitareader.com/guides/installation/native/',
UpdateDocker = 'https://wiki.kavitareader.com/guides/installation/docker/',
OpdsClients = 'https://wiki.kavitareader.com/guides/features/opds/#opds-capable-clients',
Guides = 'https://wiki.kavitareader.com/guides',
ReadingProfiles = "https://wiki.kavitareader.com/guides/user-settings/reading-profiles/",
@@ -1211,6 +1211,40 @@ export class ActionFactoryService {
];
this.smartFilterActions = [
{
action: Action.Submenu,
title: 'add-to',
description: '',
callback: this.dummyCallback,
shouldRender: this.dummyShouldRender,
requiredRoles: [],
children: [
{
action: Action.AddToDashboard,
title: 'add-to-dashboard',
description: 'add-to-dashboard-tooltip',
callback: this.dummyCallback,
shouldRender: this.dummyShouldRender,
requiredRoles: [],
children: [],
},
{
action: Action.AddToSideNav,
title: 'add-to-side-nav',
description: 'add-to-side-nav-tooltip',
callback: this.dummyCallback,
shouldRender: this.dummyShouldRender,
requiredRoles: [],
children: [],
},
],
},
{
action: Action.Edit,
title: 'rename',
@@ -63,6 +63,7 @@ import {ModalResult} from "../_models/modal/modal-result";
import {addToModal, editModal} from "../_models/modal/modal-options";
import {ModalService, TypedModalRef} from "./modal.service";
import {FilterService} from "src/app/_services/filter.service";
import {DashboardService} from "./dashboard.service";
export type LibraryActionCallback = (library: Partial<Library>) => void;
@@ -101,6 +102,7 @@ export class ActionService {
private readonly annotationsService = inject(AnnotationService);
private readonly sideNavService = inject(NavService);
private readonly filterService = inject(FilterService);
private readonly dashboardService = inject(DashboardService);
private readingListModalRef: TypedModalRef<BulkSetReadingProfileModalComponent> | TypedModalRef<ListSelectModalComponent<ReadingList>> | null = null;
private collectionModalRef: TypedModalRef<ListSelectModalComponent<UserCollection>> | null = null;
@@ -821,6 +823,14 @@ export class ActionService {
*/
handleSmartFilterAction(action: ActionItem<SmartFilter>, smartFilter: SmartFilter, allFilters: SmartFilter[]) {
switch (action.action) {
case Action.AddToDashboard:
return this.dashboardService.createDashboardStream(smartFilter.id).pipe(
map(() => this.fromAction(action, smartFilter, 'none'))
);
case Action.AddToSideNav:
return this.sideNavService.createSideNavStream(smartFilter.id).pipe(
map(() => this.fromAction(action, smartFilter, 'none'))
);
case Action.Edit:
const ref = this.modalService.open(EditSmartFilterModalComponent, editModal());
ref.componentInstance.smartFilter = smartFilter;
@@ -302,7 +302,20 @@ export class DashboardComponent {
}
async handleFilterSectionClick(stream: DashboardStream) {
await this.router.navigateByUrl('all-series?' + stream.smartFilterEncoded);
switch (stream.entityType) {
case FilterEntityType.Series:
await this.router.navigateByUrl('all-series?' + stream.smartFilterEncoded);
break;
case FilterEntityType.ReadingList:
await this.router.navigateByUrl('lists?' + stream.smartFilterEncoded);
break;
case FilterEntityType.Annotation:
await this.router.navigateByUrl('browse/annotations?' + stream.smartFilterEncoded);
break;
case FilterEntityType.Person:
await this.router.navigateByUrl('browse/people?' + stream.smartFilterEncoded);
break;
}
}
// TODO: See if we can put this into the carousel and have a custom tokens (not in the original list) to forward to a callback handler
@@ -51,10 +51,10 @@
@let normalizedSearchTerm = searchTerm.toLowerCase().trim();
<span class="ms-1">
@if (item.name.toLowerCase().trim().indexOf(normalizedSearchTerm) >= 0) {
{{item.name}}
@if (item.localizedName.toLowerCase().trim().indexOf(normalizedSearchTerm) >= 0) {
{{item.localizedName}}
} @else {
<span [innerHTML]="item.localizedName"></span>
{{item.name}}
}
</span>
<div class="text-light fst-italic" style="font-size: 0.8rem;">in {{item.libraryName}}</div>
@@ -4,7 +4,7 @@ import {
computed,
DestroyRef,
inject,
input,
input, OnInit,
signal,
TemplateRef,
viewChild
@@ -17,7 +17,7 @@ import {APP_BASE_HREF, AsyncPipe} from "@angular/common";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {CarouselReelComponent} from "../../../carousel/_components/carousel-reel/carousel-reel.component";
import {SeriesCardComponent} from "../../../cards/series-card/series-card.component";
import {Observable, switchMap, tap} from "rxjs";
import {filter, Observable, switchMap, tap} from "rxjs";
import {SeriesService} from "../../../_services/series.service";
import {QueryContext} from "../../../_models/metadata/v2/query-context";
import {map, shareReplay} from "rxjs/operators";
@@ -34,6 +34,12 @@ import {CardConfigFactory} from "../../../_services/card-config-factory.service"
import {EntityCardComponent} from "../../../cards/entity-card/entity-card.component";
import {PromotedIconComponent} from "../../../shared/_components/promoted-icon/promoted-icon.component";
import {CardEntity, CardEntityFactory} from "../../../_models/card/card-entity";
import {Action} from "../../../_models/actionables/action";
import {User} from "../../../_models/user/user";
import {ActionItem} from "../../../_models/actionables/action-item";
import {EVENTS, MessageHubService} from "../../../_services/message-hub.service";
import {DashboardService} from "../../../_services/dashboard.service";
import {NavService} from "../../../_services/nav.service";
@Component({
selector: 'app-manage-smart-filters',
@@ -42,7 +48,7 @@ import {CardEntity, CardEntityFactory} from "../../../_models/card/card-entity";
styleUrls: ['./manage-smart-filters.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ManageSmartFiltersComponent {
export class ManageSmartFiltersComponent implements OnInit {
private readonly filterService = inject(FilterService);
private readonly filterUtilityService = inject(FilterUtilitiesService);
@@ -53,6 +59,9 @@ export class ManageSmartFiltersComponent {
private readonly actionFactoryService = inject(ActionFactoryService);
private readonly destroyRef = inject(DestroyRef);
private readonly cardConfigFactory = inject(CardConfigFactory);
private readonly messageHub = inject(MessageHubService);
private readonly dashboardService = inject(DashboardService);
private readonly sideNavService = inject(NavService);
protected readonly baseUrl = inject(APP_BASE_HREF);
target = input<'_self' | '_blank'>('_blank');
@@ -77,9 +86,11 @@ export class ManageSmartFiltersComponent {
'entityType': new FormControl<FilterEntityType>(FilterEntityType.Series, []),
});
protected readonly filterApiMap = signal<{ [key: number]: Observable<any> }>({});
protected readonly actions = computed(() => this.actionFactoryService.getSmartFilterActions(this.filters()));
protected readonly actions = computed(() => this.actionFactoryService.getSmartFilterActions(this.filters(), this.shouldRenderFunc.bind(this)));
protected readonly filterQuery = signal<string>('');
protected readonly filterEntityType = signal<FilterEntityType>(FilterEntityType.Series);
private readonly dashboardFilters = signal<Set<number>>(new Set<number>());
private readonly sideNavFilters = signal<Set<number>>(new Set<number>());
protected titleTemplateRef = viewChild<TemplateRef<{ $implicit: CardEntity }>>('title');
protected readonly readingListConfig = computed(() => this.cardConfigFactory.forReadingList({titleRef: this.titleTemplateRef(), overrides: {allowSelection: false, actionableFunc: () => []}}));
@@ -96,12 +107,44 @@ export class ManageSmartFiltersComponent {
tap(val => this.filterEntityType.set(parseInt(val + '', 10)))
).subscribe();
this.messageHub.messages$.pipe(
tap(msg => {
if (msg.event === EVENTS.SideNavUpdate) {
this.sideNavService.getSideNavStreams(true).subscribe(streams => {
this.sideNavFilters.set(new Set(streams.map(stream => stream.id)));
});
} else if (msg.event === EVENTS.DashboardUpdate) {
this.dashboardService.getDashboardStreams(true).subscribe(streams => {
this.dashboardFilters.set(new Set(streams.map(stream => stream.id)));
});
}
}),
).subscribe();
}
ngOnInit() {
this.sideNavService.getSideNavStreams(true).subscribe(streams => {
this.sideNavFilters.set(new Set(streams.map(stream => stream.smartFilterId)));
});
this.dashboardService.getDashboardStreams(true).subscribe(streams => {
this.dashboardFilters.set(new Set(streams.map(stream => stream.smartFilterId)));
});
}
getFilterLink(filter: SmartFilter) {
return this.baseUrl + FilterUtilitiesService.getFilterLink(filter.entityType, filter.filter);
}
shouldRenderFunc(action: ActionItem<SmartFilter>, item: SmartFilter, user: User) {
switch (action.action) {
case Action.AddToDashboard:
return !this.dashboardFilters().has(item.id);
case Action.AddToSideNav:
return !this.sideNavFilters().has(item.id);
}
return true;
}
loadData() {
this.filterService.getAllFilters().subscribe(filters => {
+5 -1
View File
@@ -3826,7 +3826,11 @@
"export-v2": "CBL v2",
"export-v2-tooltip": "Use CBL v2 (JSON)",
"cbl-manager": "CBL Manager",
"cbl-manager-tooltip": "Manage/Sync CBLs"
"cbl-manager-tooltip": "Manage/Sync CBLs",
"add-to-dashboard": "Add to Dashboard",
"add-to-dashboard-tooltip": "Adds the smart filter to the bottom of your Dashboard",
"add-to-side-nav": "Add to Side Nav",
"add-to-side-nav-tooltip": "Adds the smart filter to the bottom of your Side Nav"
},
"preferences": {