mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-07 10:14:12 -04:00
Bugfix for sticky tabs on firefox (#1322)
* Updating calcs for firefox * Removing unused code * Fixed up browser discrepencies * Review updates * Review updates * Added debouncing to scroll * Fixed a janky scroll issue with overscrolling * Cleaned up the code to use renderer and injectable document for SSR. * Removing sticky tabs Co-authored-by: Joseph Milazzo <joseph.v.milazzo@gmail.com>
This commit is contained in:
parent
5b829af531
commit
c8418d127c
@ -1,58 +1,60 @@
|
||||
<div #companionBar>
|
||||
<app-side-nav-companion-bar *ngIf="series !== undefined" [hasExtras]="true" [extraDrawer]="extrasDrawer">
|
||||
<ng-container title>
|
||||
<h2 style="margin-bottom: 0px">
|
||||
<app-card-actionables [disabled]="actionInProgress" (actionHandler)="performAction($event)" [actions]="seriesActions" [labelBy]="series.name" iconClass="fa-ellipsis-v"></app-card-actionables>
|
||||
<span>{{series?.name}}</span>
|
||||
</h2>
|
||||
</ng-container>
|
||||
<ng-container subtitle *ngIf="series?.localizedName !== series?.name">
|
||||
<h6 class="subtitle-with-actionables" title="Localized Name">{{series?.localizedName}}</h6>
|
||||
</ng-container>
|
||||
<ng-container title>
|
||||
<h2 style="margin-bottom: 0px">
|
||||
<app-card-actionables [disabled]="actionInProgress" (actionHandler)="performAction($event)" [actions]="seriesActions" [labelBy]="series.name" iconClass="fa-ellipsis-v"></app-card-actionables>
|
||||
<span>{{series?.name}}</span>
|
||||
</h2>
|
||||
</ng-container>
|
||||
<ng-container subtitle *ngIf="series?.localizedName !== series?.name">
|
||||
<h6 class="subtitle-with-actionables" title="Localized Name">{{series?.localizedName}}</h6>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #extrasDrawer let-offcanvas>
|
||||
<div class="offcanvas-header">
|
||||
<h4 class="offcanvas-title" id="offcanvas-basic-title">Page Settings</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="offcanvas.dismiss()"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<form [formGroup]="pageExtrasGroup">
|
||||
<!-- <div class="row g-0">
|
||||
<div class="col-md-12 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-book-reading-direction" class="form-label">Sort Order</label>
|
||||
<button class="btn btn-sm btn-secondary-outline" (click)="updateSortOrder()" style="height: 25px; padding-bottom: 0px;">
|
||||
<i class="fa fa-arrow-up" title="Ascending" *ngIf="isAscendingSort; else descSort"></i>
|
||||
<ng-template #descSort>
|
||||
<i class="fa fa-arrow-down" title="Descending"></i>
|
||||
</ng-template>
|
||||
</button>
|
||||
<ng-template #extrasDrawer let-offcanvas>
|
||||
<div class="offcanvas-header">
|
||||
<h4 class="offcanvas-title" id="offcanvas-basic-title">Page Settings</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="offcanvas.dismiss()"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<form [formGroup]="pageExtrasGroup">
|
||||
<!-- <div class="row g-0">
|
||||
<div class="col-md-12 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-book-reading-direction" class="form-label">Sort Order</label>
|
||||
<button class="btn btn-sm btn-secondary-outline" (click)="updateSortOrder()" style="height: 25px; padding-bottom: 0px;">
|
||||
<i class="fa fa-arrow-up" title="Ascending" *ngIf="isAscendingSort; else descSort"></i>
|
||||
<ng-template #descSort>
|
||||
<i class="fa fa-arrow-down" title="Descending"></i>
|
||||
</ng-template>
|
||||
</button>
|
||||
|
||||
<select class="form-select" aria-describedby="settings-reading-direction-help" formControlName="sortingOption">
|
||||
<option *ngFor="let opt of sortingOptions" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="row g-0">
|
||||
<div class="col-md-12 col-sm-12 pe-2 mb-3">
|
||||
<label id="list-layout-mode-label" class="form-label">Layout Mode</label>
|
||||
<br/>
|
||||
<div class="btn-group d-flex justify-content-center" role="group" aria-label="Layout Mode">
|
||||
<input type="radio" formControlName="renderMode" [value]="PageLayoutMode.Cards" class="btn-check" id="layout-mode-default" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-default">Card</label>
|
||||
<select class="form-select" aria-describedby="settings-reading-direction-help" formControlName="sortingOption">
|
||||
<option *ngFor="let opt of sortingOptions" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="row g-0">
|
||||
<div class="col-md-12 col-sm-12 pe-2 mb-3">
|
||||
<label id="list-layout-mode-label" class="form-label">Layout Mode</label>
|
||||
<br/>
|
||||
<div class="btn-group d-flex justify-content-center" role="group" aria-label="Layout Mode">
|
||||
<input type="radio" formControlName="renderMode" [value]="PageLayoutMode.Cards" class="btn-check" id="layout-mode-default" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-default">Card</label>
|
||||
|
||||
<input type="radio" formControlName="renderMode" [value]="PageLayoutMode.List" class="btn-check" id="layout-mode-col1" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-col1">List</label>
|
||||
<input type="radio" formControlName="renderMode" [value]="PageLayoutMode.List" class="btn-check" id="layout-mode-col1" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-col1">List</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ng-template>
|
||||
</form>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
</app-side-nav-companion-bar>
|
||||
</div>
|
||||
|
||||
|
||||
<div (scroll)="onScroll()" [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" *ngIf="series !== undefined" #scrollingBlock>
|
||||
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" *ngIf="series !== undefined" #scrollingBlock>
|
||||
<div class="row mb-3 info-container">
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
|
||||
<app-image maxWidth="300px" [imageUrl]="seriesImage"></app-image>
|
||||
|
@ -16,20 +16,14 @@
|
||||
|
||||
.virtual-scroller, virtual-scroller {
|
||||
width: 100%;
|
||||
//height: 10000px;
|
||||
height: calc(100vh - 85px);
|
||||
max-height: calc(var(--vh)*100 - 170px);
|
||||
}
|
||||
|
||||
// This is responsible for ensuring we scroll down and only tabs and companion bar is visible
|
||||
.main-container {
|
||||
// Height set dynamically by series-detail.component.ts getHeight();
|
||||
// Height set dynamically by getHeight()
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.nav-tabs.fixed {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
background-color: var(--bs-body-bg);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overscroll-behavior-y: none;
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild, Renderer2, AfterViewInit, Inject } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NgbModal, NgbNavChangeEvent, NgbOffcanvas } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { forkJoin, Subject } from 'rxjs';
|
||||
import { forkJoin, fromEvent, Subject, debounceTime } from 'rxjs';
|
||||
import { finalize, take, takeUntil, takeWhile } from 'rxjs/operators';
|
||||
import { BulkSelectionService } from '../cards/bulk-selection.service';
|
||||
import { EditSeriesModalComponent } from '../cards/_modals/edit-series-modal/edit-series-modal.component';
|
||||
@ -37,7 +37,7 @@ import { RelationKind } from '../_models/series-detail/relation-kind';
|
||||
import { CardDetailDrawerComponent } from '../cards/card-detail-drawer/card-detail-drawer.component';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { PageLayoutMode } from '../_models/page-layout-mode';
|
||||
import { VirtualScrollerComponent } from '@iharbeck/ngx-virtual-scroller';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
|
||||
interface RelatedSeris {
|
||||
series: Series;
|
||||
@ -63,9 +63,10 @@ interface StoryLineItem {
|
||||
templateUrl: './series-detail.component.html',
|
||||
styleUrls: ['./series-detail.component.scss']
|
||||
})
|
||||
export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
export class SeriesDetailComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
@ViewChild('scrollingBlock') scrollingBlock: ElementRef<HTMLDivElement> | undefined;
|
||||
@ViewChild('scrollingBlock') scrollingBlock: ElementRef<HTMLDivElement> | undefined;
|
||||
@ViewChild('companionBar') companionBar: ElementRef<HTMLDivElement> | undefined;
|
||||
|
||||
/**
|
||||
* Series Id. Set at load before UI renders
|
||||
@ -220,6 +221,16 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
return PageLayoutMode;
|
||||
}
|
||||
|
||||
get ScrollingBlockHeight() {
|
||||
if (this.scrollingBlock === undefined) return 'calc(var(--vh)*100)';
|
||||
const navbar = this.document.querySelector('.navbar') as HTMLElement;
|
||||
if (navbar === null) return 'calc(var(--vh)*100)';
|
||||
|
||||
const companionHeight = this.companionBar!.nativeElement.offsetHeight;
|
||||
const navbarHeight = navbar.offsetHeight;
|
||||
const totalHeight = companionHeight + navbarHeight + 21; //21px to account for padding
|
||||
return 'calc(var(--vh)*100 - ' + totalHeight + 'px)';
|
||||
}
|
||||
|
||||
constructor(private route: ActivatedRoute, private seriesService: SeriesService,
|
||||
private router: Router, public bulkSelectionService: BulkSelectionService,
|
||||
@ -231,7 +242,8 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
private downloadService: DownloadService, private actionService: ActionService,
|
||||
public imageSerivce: ImageService, private messageHub: MessageHubService,
|
||||
private readingListService: ReadingListService, public navService: NavService,
|
||||
private offcanvasService: NgbOffcanvas
|
||||
private offcanvasService: NgbOffcanvas, private renderer: Renderer2,
|
||||
@Inject(DOCUMENT) private document: Document
|
||||
) {
|
||||
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
@ -244,30 +256,6 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
onScroll(): void {
|
||||
const tabs = document.querySelector('.nav-tabs') as HTMLElement | null;
|
||||
const main = document.querySelector('.main-container') as HTMLElement | null;
|
||||
const content = document.querySelector('.tab-content') as HTMLElement | null;
|
||||
let mainOffset = main!.offsetTop;
|
||||
let tabOffset = tabs!.offsetTop;
|
||||
let contentOffset = content!.offsetTop;
|
||||
let mainScrollPos = main!.scrollTop;
|
||||
|
||||
if (!document.querySelector('.nav-tabs.fixed') && (tabOffset - mainOffset) <= mainScrollPos) {
|
||||
tabs!.classList.add("fixed");
|
||||
tabs!.style.top = mainOffset+'px';
|
||||
} else if (document.querySelector('.nav-tabs.fixed') && mainScrollPos <= (contentOffset - mainOffset)) {
|
||||
tabs!.classList.remove("fixed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
get ScrollingBlockHeight() {
|
||||
if (this.scrollingBlock === undefined) return 'calc(var(--vh)*100)';
|
||||
const mainOffset = this.scrollingBlock.nativeElement.offsetTop;
|
||||
return 'calc(var(--vh)*100 - ' + mainOffset + 'px)';
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
const routeId = this.route.snapshot.paramMap.get('seriesId');
|
||||
@ -307,6 +295,17 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.initScroll();
|
||||
}
|
||||
|
||||
initScroll() {
|
||||
if (this.scrollingBlock === undefined || this.scrollingBlock.nativeElement === undefined) {
|
||||
setTimeout(() => {this.initScroll()}, 10);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:keydown.shift', ['$event'])
|
||||
handleKeypress(event: KeyboardEvent) {
|
||||
if (event.key === KEY_CODES.SHIFT) {
|
||||
@ -435,7 +434,6 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
series: this.seriesService.getSeries(seriesId)
|
||||
}).subscribe(results => {
|
||||
this.libraryType = results.libType;
|
||||
console.log('library type: ', this.libraryType);
|
||||
this.series = results.series;
|
||||
|
||||
this.createHTML();
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { HttpParams } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { Chapter } from 'src/app/_models/chapter';
|
||||
import { LibraryType } from 'src/app/_models/library';
|
||||
import { MangaFormat } from 'src/app/_models/manga-format';
|
||||
import { PaginatedResult } from 'src/app/_models/pagination';
|
||||
import { Series } from 'src/app/_models/series';
|
||||
import { SeriesFilter, SortField } from 'src/app/_models/series-filter';
|
||||
import { Volume } from 'src/app/_models/volume';
|
||||
|
||||
export enum KEY_CODES {
|
||||
|
Loading…
x
Reference in New Issue
Block a user