UX Overhaul Part 1 (#3047)
Co-authored-by: Joseph Milazzo <joseph.v.milazzo@gmail.com>
This commit is contained in:
parent
5934d516f3
commit
ff79710ac6
324 changed files with 11589 additions and 4598 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<ng-container *transloco="let t; read: 'events-widget'">
|
||||
|
||||
@if (isAdmin$ | async) {
|
||||
@if (accountService.isAdmin$ | async) {
|
||||
@if (downloadService.activeDownloads$ | async; as activeDownloads) {
|
||||
@if (errors$ | async; as errors) {
|
||||
@if (infos$ | async; as infos) {
|
||||
|
|
@ -11,16 +11,17 @@
|
|||
[autoClose]="'outside'">
|
||||
|
||||
@if (onlineUsers.length > 1) {
|
||||
<span class="me-2" [ngClass]="{'colored': activeEvents > 0 || activeDownloads.length > 0 || updateAvailable}">{{onlineUsers.length}}</span>
|
||||
<span class="me-2" [ngClass]="{'colored': ((activeEvents$ | async) || 0) > 0 || activeDownloads.length > 0 || updateAvailable}">{{onlineUsers.length}}</span>
|
||||
}
|
||||
<i aria-hidden="true" class="fa fa-wave-square nav" [ngClass]="{'colored': activeEvents > 0 || activeDownloads.length > 0 || updateAvailable}"></i>
|
||||
|
||||
<i aria-hidden="true" class="fa fa-wave-square nav" [ngClass]="{'colored': ((activeEvents$ | async) || 0) > 0 || activeDownloads.length > 0 || updateAvailable}"></i>
|
||||
|
||||
|
||||
@if (errors.length > 0) {
|
||||
<i aria-hidden="true" class="fa fa-circle-exclamation nav widget-button--indicator error"></i>
|
||||
} @else if (infos.length > 0) {
|
||||
<i aria-hidden="true" class="fa fa-circle-info nav widget-button--indicator info"></i>
|
||||
} @else if (activeEvents > 0 || activeDownloads.length > 0) {
|
||||
} @else if (((activeEvents$ | async) || 0) > 0 || activeDownloads.length > 0) {
|
||||
<div class="nav widget-button--indicator spinner-border spinner-border-sm"></div>
|
||||
} @else if (updateAvailable) {
|
||||
<i aria-hidden="true" class="fa fa-circle-arrow-up nav widget-button--indicator update"></i>
|
||||
|
|
|
|||
|
|
@ -65,10 +65,6 @@
|
|||
white-space:nowrap;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:hover {
|
||||
box-shadow: 0 0 0 0.1rem var(--navbar-btn-hover-outline-color);
|
||||
}
|
||||
|
||||
.small-spinner {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ import {
|
|||
OnInit
|
||||
} from '@angular/core';
|
||||
import { NgbModal, NgbModalRef, NgbPopover } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import {BehaviorSubject, debounceTime, startWith} from 'rxjs';
|
||||
import { ConfirmConfig } from 'src/app/shared/confirm-dialog/_models/confirm-config';
|
||||
import { ConfirmService } from 'src/app/shared/confirm.service';
|
||||
import { UpdateNotificationModalComponent } from 'src/app/shared/update-notification/update-notification-modal.component';
|
||||
|
|
@ -40,7 +39,7 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
|
|||
public readonly downloadService = inject(DownloadService);
|
||||
public readonly messageHub = inject(MessageHubService);
|
||||
private readonly modalService = inject(NgbModal);
|
||||
private readonly accountService = inject(AccountService);
|
||||
protected readonly accountService = inject(AccountService);
|
||||
private readonly confirmService = inject(ConfirmService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
|
@ -48,8 +47,6 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
|
|||
@Input({required: true}) user!: User;
|
||||
|
||||
|
||||
isAdmin$: Observable<boolean> = of(false);
|
||||
|
||||
/**
|
||||
* Progress events (Event Type: 'started', 'ended', 'updated' that have progress property)
|
||||
*/
|
||||
|
|
@ -67,6 +64,8 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
|
|||
|
||||
private updateNotificationModalRef: NgbModalRef | null = null;
|
||||
|
||||
activeEventsSource = new BehaviorSubject<number>(0);
|
||||
activeEvents$ = this.activeEventsSource.asObservable().pipe(startWith(0), takeUntilDestroyed(this.destroyRef), debounceTime(100));
|
||||
activeEvents: number = 0;
|
||||
/**
|
||||
* Intercepts from Single Updates to show an extra indicator to the user
|
||||
|
|
@ -93,23 +92,19 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
|
|||
values.push(event.payload as ErrorEvent);
|
||||
this.errorSource.next(values);
|
||||
this.activeEvents += 1;
|
||||
this.activeEventsSource.next(this.activeEvents);
|
||||
this.cdRef.markForCheck();
|
||||
} else if (event.event === EVENTS.Info) {
|
||||
const values = this.infoSource.getValue();
|
||||
values.push(event.payload as InfoEvent);
|
||||
this.infoSource.next(values);
|
||||
this.activeEvents += 1;
|
||||
this.activeEventsSource.next(this.activeEvents);
|
||||
this.cdRef.markForCheck();
|
||||
} else if (event.event === EVENTS.UpdateAvailable) {
|
||||
this.handleUpdateAvailableClick(event.payload);
|
||||
}
|
||||
});
|
||||
|
||||
this.isAdmin$ = this.accountService.currentUser$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map(user => (user && this.accountService.hasAdminRole(user)) || false),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
processNotificationProgressEvent(event: Message<NotificationProgressEvent>) {
|
||||
|
|
@ -121,6 +116,7 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
|
|||
values.push(message);
|
||||
this.singleUpdateSource.next(values);
|
||||
this.activeEvents += 1;
|
||||
this.activeEventsSource.next(this.activeEvents);
|
||||
if (event.payload.name === EVENTS.UpdateAvailable) {
|
||||
this.updateAvailable = true;
|
||||
}
|
||||
|
|
@ -140,6 +136,7 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
|
|||
data = data.filter(m => m.name !== message.name);
|
||||
this.progressEventsSource.next(data);
|
||||
this.activeEvents = Math.max(this.activeEvents - 1, 0);
|
||||
this.activeEventsSource.next(this.activeEvents);
|
||||
this.cdRef.markForCheck();
|
||||
break;
|
||||
default:
|
||||
|
|
@ -153,6 +150,7 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
|
|||
if (index < 0) {
|
||||
data.push(message);
|
||||
this.activeEvents += 1;
|
||||
this.activeEventsSource.next(this.activeEvents);
|
||||
this.cdRef.markForCheck();
|
||||
} else {
|
||||
data[index] = message;
|
||||
|
|
@ -204,6 +202,7 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
|
|||
this.infoSource.next([]);
|
||||
this.errorSource.next([]);
|
||||
this.activeEvents -= Math.max(infoCount + errorCount, 0);
|
||||
this.activeEventsSource.next(this.activeEvents);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
|
|
@ -223,6 +222,7 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
|
|||
this.errorSource.next(data);
|
||||
}
|
||||
this.activeEvents = Math.max(this.activeEvents - 1, 0);
|
||||
this.activeEventsSource.next(this.activeEvents);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,156 +18,157 @@
|
|||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (hasFocus) {
|
||||
<div class="dropdown">
|
||||
<ul class="list-group" role="listbox" id="dropdown">
|
||||
@if (hasFocus) {
|
||||
<div class="overlay">
|
||||
<div class="dropdown">
|
||||
<ul class="list-group" role="listbox" id="dropdown">
|
||||
|
||||
@if (seriesTemplate !== undefined && groupedData.series.length > 0) {
|
||||
<li class="list-group-item section-header"><h5 id="series-group">Series</h5></li>
|
||||
<ul class="list-group results" role="group" aria-describedby="series-group">
|
||||
@for(option of groupedData.series; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" aria-labelledby="series-group" role="option">
|
||||
<ng-container [ngTemplateOutlet]="seriesTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
@if (seriesTemplate !== undefined && groupedData.series.length > 0) {
|
||||
<li class="list-group-item section-header"><h5 id="series-group">Series</h5></li>
|
||||
<ul class="list-group results" role="group" aria-describedby="series-group">
|
||||
@for(option of groupedData.series; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" aria-labelledby="series-group" role="option">
|
||||
<ng-container [ngTemplateOutlet]="seriesTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
|
||||
@if (collectionTemplate !== undefined && groupedData.collections.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('collections')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.collections; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="collectionTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
|
||||
@if (readingListTemplate !== undefined && groupedData.readingLists.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('reading-lists')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.readingLists; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="readingListTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (bookmarkTemplate !== undefined && groupedData.bookmarks.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('bookmarks')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.bookmarks; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="bookmarkTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
|
||||
@if (libraryTemplate !== undefined && groupedData.libraries.length > 0) {
|
||||
<li class="list-group-item section-header"><h5 id="libraries-group">{{t('libraries')}}</h5></li>
|
||||
<ul class="list-group results" role="group" aria-describedby="libraries-group">
|
||||
@for(option of groupedData.libraries; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" aria-labelledby="libraries-group" role="option">
|
||||
<ng-container [ngTemplateOutlet]="libraryTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (genreTemplate !== undefined && groupedData.genres.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('genres')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.genres; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="genreTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (tagTemplate !== undefined && groupedData.tags.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('tags')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.tags; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="tagTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (personTemplate !== undefined && groupedData.persons.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('people')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.persons; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="personTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (chapterTemplate !== undefined && groupedData.chapters.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('chapters')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.chapters; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="chapterTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (fileTemplate !== undefined && groupedData.files.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('files')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.files; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="fileTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (!hasData && searchTerm.length > 0 && !isLoading) {
|
||||
|
||||
<ul class="list-group results">
|
||||
<li class="list-group-item">
|
||||
<ng-container [ngTemplateOutlet]="noResultsTemplate"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
|
||||
@if (collectionTemplate !== undefined && groupedData.collections.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('collections')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.collections; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="collectionTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
|
||||
@if (readingListTemplate !== undefined && groupedData.readingLists.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('reading-lists')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.readingLists; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="readingListTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (bookmarkTemplate !== undefined && groupedData.bookmarks.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('bookmarks')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.bookmarks; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="bookmarkTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
|
||||
@if (libraryTemplate !== undefined && groupedData.libraries.length > 0) {
|
||||
<li class="list-group-item section-header"><h5 id="libraries-group">{{t('libraries')}}</h5></li>
|
||||
<ul class="list-group results" role="group" aria-describedby="libraries-group">
|
||||
@for(option of groupedData.libraries; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" aria-labelledby="libraries-group" role="option">
|
||||
<ng-container [ngTemplateOutlet]="libraryTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (genreTemplate !== undefined && groupedData.genres.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('genres')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.genres; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="genreTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (tagTemplate !== undefined && groupedData.tags.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('tags')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.tags; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="tagTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (personTemplate !== undefined && groupedData.persons.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('people')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.persons; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="personTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (chapterTemplate !== undefined && groupedData.chapters.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('chapters')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.chapters; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="chapterTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (fileTemplate !== undefined && groupedData.files.length > 0) {
|
||||
<li class="list-group-item section-header"><h5>{{t('files')}}</h5></li>
|
||||
<ul class="list-group results">
|
||||
@for(option of groupedData.files; track option; let index = $index) {
|
||||
<li (click)="handleResultClick(option)" tabindex="0"
|
||||
class="list-group-item" role="option">
|
||||
<ng-container [ngTemplateOutlet]="fileTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: index }"></ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (!hasData && searchTerm.length > 0 && !isLoading) {
|
||||
|
||||
<ul class="list-group results">
|
||||
<li class="list-group-item">
|
||||
<ng-container [ngTemplateOutlet]="noResultsTemplate"></ng-container>
|
||||
@if (searchTerm.length > 0 && !isLoading) {
|
||||
<li class="list-group-item" style="min-height: 34px" (click)="$event.stopPropagation()">
|
||||
<ng-container [ngTemplateOutlet]="extraTemplate"></ng-container>
|
||||
<form [formGroup]="searchSettingsForm">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="search-include-extras" role="switch" formControlName="includeExtras" class="form-check-input"
|
||||
aria-labelledby="auto-close-label" aria-describedby="tag-promoted-help">
|
||||
<label class="form-check-label" for="search-include-extras">{{t('include-extras')}}</label>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (hasData && (isAdmin$ | async)) {
|
||||
<li class="list-group-item" style="min-height: 34px" (click)="$event.stopPropagation()">
|
||||
<ng-container [ngTemplateOutlet]="extraTemplate"></ng-container>
|
||||
<form [formGroup]="searchSettingsForm">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="search-include-extras" role="switch" formControlName="includeExtras" class="form-check-input"
|
||||
aria-labelledby="auto-close-label" aria-describedby="tag-promoted-help">
|
||||
<label class="form-check-label" for="search-include-extras">{{t('include-extras')}}</label>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ form {
|
|||
}
|
||||
|
||||
&.focused {
|
||||
width: 99%;
|
||||
width: 98.5%;
|
||||
border-color: var(--input-focused-border-color);
|
||||
}
|
||||
|
||||
|
|
@ -92,15 +92,19 @@ form {
|
|||
color: var(--body-text-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--list-group-item-bg-color) !important;
|
||||
background-color: var(--search-list-group-item-bg-color, black) !important;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
background-color: var(--search-list-group-item-bg-color, black);
|
||||
}
|
||||
|
||||
|
||||
.dropdown {
|
||||
width: 100vw;
|
||||
height: calc(var(--vh)*100 - 56px); //header offset
|
||||
background: var(--dropdown-overlay-color);
|
||||
top: 55px; //header offset
|
||||
position: fixed;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
|
|
@ -117,7 +121,7 @@ form {
|
|||
overflow-x: hidden;
|
||||
display: block;
|
||||
flex: auto;
|
||||
max-height: calc(100vh - 58px);
|
||||
max-height: calc((var(--vh) * 100) - var(--nav-offset));
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
|
|
@ -154,3 +158,12 @@ ul ul {
|
|||
cursor: pointer;
|
||||
top: 30%;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(0,0,0,0.4);
|
||||
width: 100%;
|
||||
height: 100dvh;
|
||||
}
|
||||
|
|
@ -105,10 +105,6 @@ export class GroupedTypeaheadComponent implements OnInit {
|
|||
includeChapterAndFiles: boolean = false;
|
||||
prevSearchTerm: string = '';
|
||||
searchSettingsForm = new FormGroup(({'includeExtras': new FormControl(false)}));
|
||||
isAdmin$ = this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), map(u => {
|
||||
if (!u) return false;
|
||||
return this.accountService.hasAdminRole(u);
|
||||
}));
|
||||
|
||||
get searchTerm() {
|
||||
return this.typeaheadForm.get('typeahead')?.value || '';
|
||||
|
|
|
|||
|
|
@ -1,195 +1,204 @@
|
|||
<ng-container *transloco="let t; read: 'nav-header'">
|
||||
<nav class="navbar navbar-expand-md navbar-dark fixed-top" *ngIf="navService?.navbarVisible$ | async">
|
||||
<div class="container-fluid">
|
||||
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">{{t('skip-alt')}}</a>
|
||||
<a class="side-nav-toggle" *ngIf="navService?.sideNavVisibility$ | async" (click)="hideSideNav()"><i class="fas fa-bars"></i></a>
|
||||
<a class="navbar-brand dark-exempt" routerLink="/home" routerLinkActive="active">
|
||||
<app-image width="28px" height="28px" imageUrl="assets/images/logo-32.png" classes="logo" />
|
||||
<span class="d-none d-md-inline logo"> Kavita</span>
|
||||
</a>
|
||||
<ul class="navbar-nav col me-auto">
|
||||
@if (navService.navbarVisible$ | async) {
|
||||
<nav class="navbar navbar-expand-md navbar-dark fixed-top">
|
||||
<div class="container-fluid">
|
||||
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">{{t('skip-alt')}}</a>
|
||||
|
||||
<div class="nav-item" *ngIf="(accountService.currentUser$ | async) as user">
|
||||
<label for="nav-search" class="form-label visually-hidden">{{t('search-series-alt')}}</label>
|
||||
<div class="ng-autocomplete">
|
||||
<app-grouped-typeahead
|
||||
#search
|
||||
id="nav-search"
|
||||
[minQueryLength]="2"
|
||||
[isLoading]="isLoading"
|
||||
initialValue=""
|
||||
[placeholder]="t('search-alt')"
|
||||
[groupedData]="searchResults"
|
||||
(inputChanged)="onChangeSearch($event)"
|
||||
(clearField)="clearSearch()"
|
||||
(focusChanged)="focusUpdate($event)"
|
||||
>
|
||||
@if (navService.sideNavVisibility$ | async) {
|
||||
<a class="side-nav-toggle" (click)="hideSideNav()"><i class="fas fa-bars"></i></a>
|
||||
}
|
||||
|
||||
<ng-template #libraryTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickLibraryResult(item)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #seriesTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickSeriesSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
<ng-container *ngIf="searchTerm.toLowerCase().trim() as st">
|
||||
<span *ngIf="item.name.toLowerCase().trim().indexOf(st) >= 0; else localizedName">{{item.name}}</span>
|
||||
<ng-template #localizedName>
|
||||
<span [innerHTML]="item.localizedName"></span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<a class="navbar-brand dark-exempt" routerLink="/home" routerLinkActive="active">
|
||||
<app-image width="28px" height="28px" imageUrl="assets/images/logo-32.png" classes="logo" />
|
||||
<span class="d-none d-md-inline logo"> Kavita</span>
|
||||
</a>
|
||||
<ul class="navbar-nav col me-auto">
|
||||
@if (accountService.currentUser$ | async; as user) {
|
||||
<div class="nav-item">
|
||||
<label for="nav-search" class="form-label visually-hidden">{{t('search-series-alt')}}</label>
|
||||
<div class="ng-autocomplete">
|
||||
<app-grouped-typeahead
|
||||
#search
|
||||
id="nav-search"
|
||||
[minQueryLength]="2"
|
||||
[isLoading]="isLoading"
|
||||
initialValue=""
|
||||
[placeholder]="t('search-alt')"
|
||||
[groupedData]="searchResults"
|
||||
(inputChanged)="onChangeSearch($event)"
|
||||
(clearField)="clearSearch()"
|
||||
(focusChanged)="focusUpdate($event)"
|
||||
>
|
||||
|
||||
<div class="text-light fst-italic" style="font-size: 0.8rem;">in {{item.libraryName}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #bookmarkTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickBookmarkSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
<ng-container *ngIf="searchTerm.toLowerCase().trim() as st">
|
||||
<span *ngIf="item.seriesName.toLowerCase().trim().indexOf(st) >= 0; else localizedName">{{item.seriesName}}</span>
|
||||
<ng-template #localizedName>
|
||||
<span [innerHTML]="item.localizedSeriesName"></span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #collectionTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickCollectionSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getCollectionCoverImage(item.id)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<div>
|
||||
<span>{{item.title}}</span>
|
||||
<app-promoted-icon [promoted]="item.promoted"></app-promoted-icon>
|
||||
<ng-template #libraryTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickLibraryResult(item)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<app-collection-owner [collection]="item"></app-collection-owner>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #readingListTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickReadingListSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
<span *ngIf="item.promoted">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true" [title]="t('promoted')"></i>
|
||||
<span class="visually-hidden">{{t('promoted')}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #seriesTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickSeriesSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
@if (searchTerm.toLowerCase().trim(); as st) {
|
||||
@if (item.name.toLowerCase().trim().indexOf(st) >= 0) {
|
||||
<span>{{item.name}}</span>
|
||||
} @else {
|
||||
<span [innerHTML]="item.localizedName"></span>
|
||||
}
|
||||
}
|
||||
<div class="text-light fst-italic" style="font-size: 0.8rem;">in {{item.libraryName}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #tagTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="goToOther(FilterField.Tags, item.id)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #bookmarkTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickBookmarkSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
|
||||
<ng-template #personTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goToPerson(item.role, item.id)">
|
||||
<div class="ms-1">
|
||||
@if (searchTerm.toLowerCase().trim(); as st) {
|
||||
@if (item.seriesName.toLowerCase().trim().indexOf(st) >= 0) {
|
||||
<span>{{item.seriesName}}</span>
|
||||
} @else {
|
||||
<span [innerHTML]="item.localizedSeriesName"></span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div [innerHTML]="item.name"></div>
|
||||
<div class="text-light fst-italic">{{item.role | personRole}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #collectionTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickCollectionSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getCollectionCoverImage(item.id)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<div>
|
||||
<span>{{item.title}}</span>
|
||||
<app-promoted-icon [promoted]="item.promoted"></app-promoted-icon>
|
||||
</div>
|
||||
<app-collection-owner [collection]="item"></app-collection-owner>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #genreTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goToOther(FilterField.Genres, item.id)">
|
||||
<div class="ms-1">
|
||||
<div [innerHTML]="item.title"></div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #readingListTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickReadingListSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
<app-promoted-icon [promoted]="item.promoted"></app-promoted-icon>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #tagTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="goToOther(FilterField.Tags, item.id)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #personTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goToPerson(item.role, item.id)">
|
||||
<div class="ms-1">
|
||||
|
||||
<div [innerHTML]="item.name"></div>
|
||||
<div class="text-light fst-italic">{{item.role | personRole}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #genreTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goToOther(FilterField.Genres, item.id)">
|
||||
<div class="ms-1">
|
||||
<div [innerHTML]="item.title"></div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
|
||||
<ng-template #chapterTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="clickChapterSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<ng-container *ngIf="item.files.length > 0">
|
||||
<app-series-format [format]="item.files?.[0].format"></app-series-format>
|
||||
</ng-container>
|
||||
<!-- TODO: this needs the series name before the chapter issue -->
|
||||
<span>{{item.titleName || item.range}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #chapterTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="clickChapterSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
@if (item.files.length > 0) {
|
||||
<app-series-format [format]="item.files?.[0].format"></app-series-format>
|
||||
}
|
||||
<!-- TODO: this needs the series name before the chapter issue -->
|
||||
<span>{{item.titleName || item.range}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #fileTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickFileSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
<span>{{item.filePath}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #fileTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickFileSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
<span>{{item.filePath}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #noResultsTemplate let-notFound>
|
||||
{{t('no-data')}}
|
||||
</ng-template>
|
||||
</app-grouped-typeahead>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
<ng-container *ngIf="!searchFocused">
|
||||
<div class="back-to-top" *ngIf="backToTopNeeded">
|
||||
<button class="btn btn-icon scroll-to-top" (click)="scrollToTop()">
|
||||
<i class="fa fa-angle-double-up nav" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('scroll-to-top-alt')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="(accountService.currentUser$ | async) as user">
|
||||
<div class="nav-item">
|
||||
<app-nav-events-toggle [user]="user"></app-nav-events-toggle>
|
||||
</div>
|
||||
<div class="nav-item not-xs-only">
|
||||
<a routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')" class="dark-exempt btn btn-icon" title="Server Settings">
|
||||
<i class="fa fa-cogs nav" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('server-settings')}}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div ngbDropdown class="nav-item dropdown" display="dynamic" placement="bottom-right" *ngIf="(accountService.currentUser$ | async) as user" dropdown>
|
||||
<button class="btn btn-outline-secondary primary-text" ngbDropdownToggle>
|
||||
<i class="fa-solid fa-user-circle align-self-center phone-hidden d-xs-inline-block d-sm-inline-block d-md-none"></i>
|
||||
<span class="d-none d-xs-none d-sm-none d-md-inline-block">{{user.username | sentenceCase}}</span>
|
||||
</button>
|
||||
<div ngbDropdownMenu>
|
||||
<a class="xs-only" ngbDropdownItem routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')">{{t('server-settings')}}</a>
|
||||
<a ngbDropdownItem routerLink="/preferences/">{{t('settings')}}</a>
|
||||
<a ngbDropdownItem routerLink="/all-filters/">{{t('all-filters')}}</a>
|
||||
<a ngbDropdownItem href="https://wiki.kavitareader.com" rel="noopener noreferrer" target="_blank">{{t('help')}}</a>
|
||||
<a ngbDropdownItem routerLink="/announcements/" *ngIf="accountService.hasAdminRole(user)">{{t('announcements')}}</a>
|
||||
<a ngbDropdownItem (click)="logout()">{{t('logout')}}</a>
|
||||
<ng-template #noResultsTemplate let-notFound>
|
||||
{{t('no-data')}}
|
||||
</ng-template>
|
||||
</app-grouped-typeahead>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
</ul>
|
||||
|
||||
@if (!searchFocused) {
|
||||
@if (backToTopNeeded) {
|
||||
<div class="back-to-top">
|
||||
<button class="btn btn-icon scroll-to-top" (click)="scrollToTop()">
|
||||
<i class="fa fa-angle-double-up nav" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('scroll-to-top-alt')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (accountService.currentUser$ | async; as user) {
|
||||
<div class="nav-item">
|
||||
<app-nav-events-toggle [user]="user"></app-nav-events-toggle>
|
||||
</div>
|
||||
<div class="nav-item not-xs-only">
|
||||
<a routerLink="/settings" [fragment]="SettingsTabId.Account" class="dark-exempt btn btn-icon" [title]="t('settings')">
|
||||
<i class="fa fa-cogs nav" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('settings')}}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div ngbDropdown class="nav-item dropdown" display="dynamic" placement="bottom-right">
|
||||
<button class="btn btn-outline-secondary primary-text" ngbDropdownToggle>
|
||||
<i class="fa-solid fa-user-circle align-self-center phone-hidden d-xs-inline-block d-sm-inline-block d-md-none"></i>
|
||||
<span class="d-none d-xs-none d-sm-none d-md-inline-block">{{user.username | sentenceCase}}</span>
|
||||
</button>
|
||||
<div ngbDropdownMenu>
|
||||
<a ngbDropdownItem routerLink="/all-filters/">{{t('all-filters')}}</a>
|
||||
<a ngbDropdownItem routerLink="/announcements/">{{t('announcements')}}</a>
|
||||
<a ngbDropdownItem [href]="WikiLink.Guides" rel="noopener noreferrer" target="_blank">{{t('help')}}</a>
|
||||
<a ngbDropdownItem (click)="logout()">{{t('logout')}}</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
|
||||
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ import {ProviderImagePipe} from "../../../_pipes/provider-image.pipe";
|
|||
import {ProviderNamePipe} from "../../../_pipes/provider-name.pipe";
|
||||
import {CollectionOwnerComponent} from "../../../collections/_components/collection-owner/collection-owner.component";
|
||||
import {PromotedIconComponent} from "../../../shared/_components/promoted-icon/promoted-icon.component";
|
||||
import {SettingsTabId} from "../../../sidenav/preference-nav/preference-nav.component";
|
||||
import {Breakpoint, UtilityService} from "../../../shared/_services/utility.service";
|
||||
import {WikiLink} from "../../../_models/wiki";
|
||||
|
||||
@Component({
|
||||
selector: 'app-nav-header',
|
||||
|
|
@ -52,14 +55,31 @@ import {PromotedIconComponent} from "../../../shared/_components/promoted-icon/p
|
|||
styleUrls: ['./nav-header.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [NgIf, RouterLink, RouterLinkActive, NgOptimizedImage, GroupedTypeaheadComponent, ImageComponent,
|
||||
imports: [RouterLink, RouterLinkActive, NgOptimizedImage, GroupedTypeaheadComponent, ImageComponent,
|
||||
SeriesFormatComponent, EventsWidgetComponent, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem,
|
||||
AsyncPipe, PersonRolePipe, SentenceCasePipe, TranslocoDirective, ProviderImagePipe, ProviderNamePipe, CollectionOwnerComponent, PromotedIconComponent]
|
||||
})
|
||||
export class NavHeaderComponent implements OnInit {
|
||||
|
||||
@ViewChild('search') searchViewRef!: any;
|
||||
private readonly router = inject(Router);
|
||||
private readonly scrollService = inject(ScrollService);
|
||||
private readonly searchService = inject(SearchService);
|
||||
private readonly filterUtilityService = inject(FilterUtilitiesService);
|
||||
protected readonly accountService = inject(AccountService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
protected readonly navService = inject(NavService);
|
||||
protected readonly imageService = inject(ImageService);
|
||||
protected readonly utilityService = inject(UtilityService);
|
||||
|
||||
protected readonly FilterField = FilterField;
|
||||
protected readonly WikiLink = WikiLink;
|
||||
protected readonly ScrobbleProvider = ScrobbleProvider;
|
||||
protected readonly SettingsTabId = SettingsTabId;
|
||||
protected readonly Breakpoint = Breakpoint;
|
||||
|
||||
@ViewChild('search') searchViewRef!: any;
|
||||
|
||||
|
||||
isLoading = false;
|
||||
debounceTime = 300;
|
||||
|
|
@ -69,12 +89,8 @@ export class NavHeaderComponent implements OnInit {
|
|||
backToTopNeeded = false;
|
||||
searchFocused: boolean = false;
|
||||
scrollElem: HTMLElement;
|
||||
protected readonly FilterField = FilterField;
|
||||
|
||||
constructor(public accountService: AccountService, private router: Router, public navService: NavService,
|
||||
public imageService: ImageService, @Inject(DOCUMENT) private document: Document,
|
||||
private scrollService: ScrollService, private searchService: SearchService, private readonly cdRef: ChangeDetectorRef,
|
||||
private filterUtilityService: FilterUtilitiesService) {
|
||||
constructor(@Inject(DOCUMENT) private document: Document) {
|
||||
this.scrollElem = this.document.body;
|
||||
}
|
||||
|
||||
|
|
@ -274,6 +290,4 @@ export class NavHeaderComponent implements OnInit {
|
|||
this.navService.toggleSideNav();
|
||||
}
|
||||
|
||||
|
||||
protected readonly ScrobbleProvider = ScrobbleProvider;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue