New UX Part 1.5 (#3105)

This commit is contained in:
Joe Milazzo 2024-08-11 06:10:46 -05:00 committed by GitHub
parent c188e0f23b
commit ac21b04fa4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
239 changed files with 1626 additions and 776 deletions

View file

@ -25,7 +25,7 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import { SentenceCasePipe } from '../../../_pipes/sentence-case.pipe';
import { CircularLoaderComponent } from '../../../shared/circular-loader/circular-loader.component';
import { NgClass, NgStyle, AsyncPipe } from '@angular/common';
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
@Component({
selector: 'app-nav-events-toggle',

View file

@ -19,7 +19,7 @@ import { KEY_CODES } from 'src/app/shared/_services/utility.service';
import { SearchResultGroup } from 'src/app/_models/search/search-result-group';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {AsyncPipe, NgClass, NgTemplateOutlet} from '@angular/common';
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {LoadingComponent} from "../../../shared/loading/loading.component";
import {map, startWith, tap} from "rxjs";
import {AccountService} from "../../../_services/account.service";

View file

@ -159,40 +159,50 @@
}
</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((breakpoint$ | async)! > Breakpoint.Tablet) {
@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>
@if((breakpoint$ | async)! <= Breakpoint.Mobile) {
<button class="btn btn-outline-secondary primary-text" (click)="openLinkSelectionMenu()">
<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>
} @else {
<div class="nav-item not-xs-only">
<a routerLink="/settings" [fragment]="SettingsTabId.Preferences" 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>
<div ngbDropdown class="nav-item dropdown d-sm-none d-md-block" 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>
}
}
}

View file

@ -1,17 +1,17 @@
import {AsyncPipe, DOCUMENT, NgIf, NgOptimizedImage} from '@angular/common';
import {AsyncPipe, DOCUMENT, NgOptimizedImage, NgTemplateOutlet} from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
ElementRef,
ElementRef, HostListener,
inject,
Inject,
OnInit,
ViewChild
} from '@angular/core';
import {NavigationEnd, Router, RouterLink, RouterLinkActive} from '@angular/router';
import {fromEvent} from 'rxjs';
import {BehaviorSubject, fromEvent, Observable} from 'rxjs';
import {debounceTime, distinctUntilChanged, filter, tap} from 'rxjs/operators';
import {Chapter} from 'src/app/_models/chapter';
import {UserCollection} from 'src/app/_models/collection-tag';
@ -29,12 +29,12 @@ import {SearchService} from 'src/app/_services/search.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {SentenceCasePipe} from '../../../_pipes/sentence-case.pipe';
import {PersonRolePipe} from '../../../_pipes/person-role.pipe';
import {NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle} from '@ng-bootstrap/ng-bootstrap';
import {NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle, NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {EventsWidgetComponent} from '../events-widget/events-widget.component';
import {SeriesFormatComponent} from '../../../shared/series-format/series-format.component';
import {ImageComponent} from '../../../shared/image/image.component';
import {GroupedTypeaheadComponent, SearchEvent} from '../grouped-typeahead/grouped-typeahead.component';
import {TranslocoDirective} from "@ngneat/transloco";
import {translate, TranslocoDirective} from "@jsverse/transloco";
import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service";
import {FilterStatement} from "../../../_models/metadata/v2/filter-statement";
import {FilterField} from "../../../_models/metadata/v2/filter-field";
@ -48,6 +48,10 @@ import {PromotedIconComponent} from "../../../shared/_components/promoted-icon/p
import {SettingsTabId} from "../../../sidenav/preference-nav/preference-nav.component";
import {Breakpoint, UtilityService} from "../../../shared/_services/utility.service";
import {WikiLink} from "../../../_models/wiki";
import {
GenericListModalComponent
} from "../../../statistics/_components/_modals/generic-list-modal/generic-list-modal.component";
import {NavLinkModalComponent} from "../nav-link-modal/nav-link-modal.component";
@Component({
selector: 'app-nav-header',
@ -57,7 +61,7 @@ import {WikiLink} from "../../../_models/wiki";
standalone: true,
imports: [RouterLink, RouterLinkActive, NgOptimizedImage, GroupedTypeaheadComponent, ImageComponent,
SeriesFormatComponent, EventsWidgetComponent, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem,
AsyncPipe, PersonRolePipe, SentenceCasePipe, TranslocoDirective, ProviderImagePipe, ProviderNamePipe, CollectionOwnerComponent, PromotedIconComponent]
AsyncPipe, PersonRolePipe, SentenceCasePipe, TranslocoDirective, ProviderImagePipe, ProviderNamePipe, CollectionOwnerComponent, PromotedIconComponent, NgTemplateOutlet]
})
export class NavHeaderComponent implements OnInit {
@ -71,6 +75,7 @@ export class NavHeaderComponent implements OnInit {
protected readonly navService = inject(NavService);
protected readonly imageService = inject(ImageService);
protected readonly utilityService = inject(UtilityService);
protected readonly modalService = inject(NgbModal);
protected readonly FilterField = FilterField;
protected readonly WikiLink = WikiLink;
@ -90,6 +95,15 @@ export class NavHeaderComponent implements OnInit {
searchFocused: boolean = false;
scrollElem: HTMLElement;
breakpointSource = new BehaviorSubject<Breakpoint>(this.utilityService.getActiveBreakpoint());
breakpoint$: Observable<Breakpoint> = this.breakpointSource.asObservable();
@HostListener('window:resize', ['$event'])
@HostListener('window:orientationchange', ['$event'])
onResize(){
this.breakpointSource.next(this.utilityService.getActiveBreakpoint());
}
constructor(@Inject(DOCUMENT) private document: Document) {
this.scrollElem = this.document.body;
}
@ -134,10 +148,6 @@ export class NavHeaderComponent implements OnInit {
this.document.getElementById('content')?.focus();
}
onChangeSearch(evt: SearchEvent) {
this.isLoading = true;
this.searchTerm = evt.value.trim();
@ -290,4 +300,9 @@ export class NavHeaderComponent implements OnInit {
this.navService.toggleSideNav();
}
openLinkSelectionMenu() {
const ref = this.modalService.open(NavLinkModalComponent, {fullscreen: 'sm'});
ref.componentInstance.logoutFn = this.logout.bind(this);
}
}

View file

@ -0,0 +1,29 @@
<ng-container *transloco="let t; read:'nav-header'">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{t('nav-link-header')}}</h4>
<button type="button" class="btn-close" [attr.aria-label]="t('close')" (click)="close()"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<a routerLink="/settings" [fragment]="SettingsTabId.Preferences" [title]="t('settings')">{{t('settings')}}</a>
</div>
<div class="mb-3">
<a routerLink="/all-filters/">{{t('all-filters')}}</a>
</div>
<div class="mb-3">
<a routerLink="/announcements/">{{t('announcements')}}</a>
</div>
<div class="mb-3">
<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 class="modal-footer">
<button type="button" class="btn btn-primary" (click)="close()">{{t('close')}}</button>
</div>
</ng-container>

View file

@ -0,0 +1,42 @@
import {Component, inject, Input} from '@angular/core';
import {WikiLink} from "../../../_models/wiki";
import {NgbActiveModal, NgbDropdownItem} from "@ng-bootstrap/ng-bootstrap";
import {RouterLink} from "@angular/router";
import {FilterPipe} from "../../../_pipes/filter.pipe";
import {ReactiveFormsModule} from "@angular/forms";
import {Select2Module} from "ng-select2-component";
import {TranslocoDirective} from "@jsverse/transloco";
import {SettingsTabId} from "../../../sidenav/preference-nav/preference-nav.component";
@Component({
selector: 'app-nav-link-modal',
standalone: true,
imports: [
NgbDropdownItem,
RouterLink,
FilterPipe,
ReactiveFormsModule,
Select2Module,
TranslocoDirective
],
templateUrl: './nav-link-modal.component.html',
styleUrl: './nav-link-modal.component.scss'
})
export class NavLinkModalComponent {
@Input({required: true}) logoutFn!: () => void;
private readonly modal = inject(NgbActiveModal);
protected readonly WikiLink = WikiLink;
close() {
this.modal.close();
}
logout() {
this.logoutFn();
}
protected readonly SettingsTabId = SettingsTabId;
}