Construct mobile menu and nav items dropdown from the same list #3856

This commit is contained in:
Amelia 2025-06-19 11:33:56 +02:00
parent b6d004614a
commit 46d5bf2a83
5 changed files with 76 additions and 32 deletions

View file

@ -9,6 +9,24 @@ import {AccountService} from "./account.service";
import {map} from "rxjs/operators"; import {map} from "rxjs/operators";
import {NavigationEnd, Router} from "@angular/router"; import {NavigationEnd, Router} from "@angular/router";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {SettingsTabId} from "../sidenav/preference-nav/preference-nav.component";
import {WikiLink} from "../_models/wiki";
/**
* NavItem used to construct the dropdown or NavLinkModal on mobile
* Priority construction
* @param routerLink A link to a page on the web app, takes priority
* @param fragment Optional fragment for routerLink
* @param href A link to an external page, must set noopener noreferrer
* @param click Callback, lowest priority. Should only be used if routerLink and href or not set
*/
interface NavItem {
transLocoKey: string;
href?: string;
fragment?: string;
routerLink?: string;
click?: () => void;
}
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -21,6 +39,33 @@ export class NavService {
public localStorageSideNavKey = 'kavita--sidenav--expanded'; public localStorageSideNavKey = 'kavita--sidenav--expanded';
public navItems: NavItem[] = [
{
transLocoKey: 'all-filters',
routerLink: '/all-filters/',
},
{
transLocoKey: 'browse-genres',
routerLink: '/browse/genres',
},
{
transLocoKey: 'browse-tags',
routerLink: '/browse/tags',
},
{
transLocoKey: 'announcements',
routerLink: '/announcements/',
},
{
transLocoKey: 'help',
href: WikiLink.Guides,
},
{
transLocoKey: 'logout',
click: () => this.logout(),
}
]
private navbarVisibleSource = new ReplaySubject<boolean>(1); private navbarVisibleSource = new ReplaySubject<boolean>(1);
/** /**
* If the top Nav bar is rendered or not * If the top Nav bar is rendered or not
@ -127,6 +172,13 @@ export class NavService {
}, 10); }, 10);
} }
logout() {
this.accountService.logout();
this.hideNavBar();
this.hideSideNav();
this.router.navigateByUrl('/login');
}
/** /**
* Shows the side nav. When being visible, the side nav will automatically return to previous collapsed state. * Shows the side nav. When being visible, the side nav will automatically return to previous collapsed state.
*/ */

View file

@ -205,12 +205,15 @@
<span class="d-none d-xs-none d-sm-none d-md-inline-block fw-bold">{{user.username | sentenceCase}}</span> <span class="d-none d-xs-none d-sm-none d-md-inline-block fw-bold">{{user.username | sentenceCase}}</span>
</button> </button>
<div ngbDropdownMenu> <div ngbDropdownMenu>
<a ngbDropdownItem routerLink="/all-filters/">{{t('all-filters')}}</a> @for (navItem of navService.navItems; track $index) {
<a ngbDropdownItem routerLink="/browse/genres">{{t('browse-genres')}}</a> @if (navItem.routerLink) {
<a ngbDropdownItem routerLink="/browse/tags">{{t('browse-tags')}}</a> <a ngbDropdownItem [routerLink]="navItem.routerLink" [fragment]="navItem.fragment">{{t(navItem.transLocoKey)}}</a>
<a ngbDropdownItem routerLink="/announcements/">{{t('announcements')}}</a> } @else if (navItem.href) {
<a ngbDropdownItem [href]="WikiLink.Guides" rel="noopener noreferrer" target="_blank">{{t('help')}}</a> <a ngbDropdownItem [href]="navItem.href" rel="noopener noreferrer" target="_blank">{{t(navItem.transLocoKey)}}</a>
<a ngbDropdownItem (click)="logout()">{{t('logout')}}</a> } @else if (navItem.click) {
<a ngbDropdownItem (click)="navItem.click()">{{t(navItem.transLocoKey)}}</a>
}
}
</div> </div>
</div> </div>
} }

View file

@ -134,13 +134,6 @@ export class NavHeaderComponent implements OnInit {
this.cdRef.markForCheck(); this.cdRef.markForCheck();
} }
logout() {
this.accountService.logout();
this.navService.hideNavBar();
this.navService.hideSideNav();
this.router.navigateByUrl('/login');
}
moveFocus() { moveFocus() {
this.document.getElementById('content')?.focus(); this.document.getElementById('content')?.focus();
} }
@ -253,7 +246,6 @@ export class NavHeaderComponent implements OnInit {
openLinkSelectionMenu() { openLinkSelectionMenu() {
const ref = this.modalService.open(NavLinkModalComponent, {fullscreen: 'sm'}); const ref = this.modalService.open(NavLinkModalComponent, {fullscreen: 'sm'});
ref.componentInstance.logoutFn = this.logout.bind(this);
} }
} }

View file

@ -6,21 +6,22 @@
<button type="button" class="btn-close" [attr.aria-label]="t('close')" (click)="close()"></button> <button type="button" class="btn-close" [attr.aria-label]="t('close')" (click)="close()"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="mb-3"> <div class="mb-3">
<a routerLink="/settings" [fragment]="SettingsTabId.Preferences" (click)="closeIfOnSettings()" [title]="t('settings')">{{t('settings')}}</a> <a routerLink="/settings" [fragment]="SettingsTabId.Preferences" (click)="closeIfOnSettings()" [title]="t('settings')">{{t('settings')}}</a>
</div> </div>
@for (navItem of navService.navItems; track $index) {
<div class="mb-3"> <div class="mb-3">
<a routerLink="/all-filters/">{{t('all-filters')}}</a> @if (navItem.routerLink) {
</div> <a [routerLink]="navItem.routerLink" [fragment]="navItem.fragment">{{t(navItem.transLocoKey)}}</a>
<div class="mb-3"> } @else if (navItem.href) {
<a routerLink="/announcements/">{{t('announcements')}}</a> <a [href]="navItem.href" rel="noopener noreferrer" target="_blank">{{t(navItem.transLocoKey)}}</a>
</div> } @else if (navItem.click) {
<div class="mb-3"> <a href="javascript:void(0);" (click)="navItem.click()">{{t(navItem.transLocoKey)}}</a>
<a [href]="WikiLink.Guides" rel="noopener noreferrer" target="_blank">{{t('help')}}</a> }
</div>
<div class="mb-3">
<a href="javascript:void(0);" (click)="logout()">{{t('logout')}}</a>
</div> </div>
}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="close()">{{t('close')}}</button> <button type="button" class="btn btn-primary" (click)="close()">{{t('close')}}</button>

View file

@ -5,6 +5,7 @@ import {Router, RouterLink} from "@angular/router";
import {ReactiveFormsModule} from "@angular/forms"; import {ReactiveFormsModule} from "@angular/forms";
import {TranslocoDirective} from "@jsverse/transloco"; import {TranslocoDirective} from "@jsverse/transloco";
import {SettingsTabId} from "../../../sidenav/preference-nav/preference-nav.component"; import {SettingsTabId} from "../../../sidenav/preference-nav/preference-nav.component";
import {NavService} from "../../../_services/nav.service";
@Component({ @Component({
selector: 'app-nav-link-modal', selector: 'app-nav-link-modal',
@ -25,17 +26,12 @@ export class NavLinkModalComponent {
private readonly cdRef = inject(ChangeDetectorRef); private readonly cdRef = inject(ChangeDetectorRef);
private readonly modal = inject(NgbActiveModal); private readonly modal = inject(NgbActiveModal);
private readonly router = inject(Router); private readonly router = inject(Router);
protected readonly navService = inject(NavService);
@Input({required: true}) logoutFn!: () => void;
close() { close() {
this.modal.close(); this.modal.close();
} }
logout() {
this.logoutFn();
}
closeIfOnSettings() { closeIfOnSettings() {
setTimeout(() => { setTimeout(() => {
const currentUrl = this.router.url; const currentUrl = this.router.url;