Bugs, Enhancements, and Performance (#580)
* Added parser case for "The Duke of Death and His Black Maid - Ch. 177 - The Ball (3).cbz" * Removed a file that is created and modified every test run. * Fixed a bad parser case for "Batman Beyond 02 (of 6) (1999)" which was consuming too many characters * Removed a lot of "Volume" parsing for Comics that don't make sense. This is prep work for the upcoming Comic Rework release. * Reworked a lot of parsing cases for comics based on naming conventions observed from releases found online. * Added a way for external scripts to use a user api key to authenticate * Fixed an issue if the manga only had one page, the bottom menu would be missing page and chapter controls. * Fixed a bug where on small phones, nav bar could overflow due to scroll to top * Tweaked a lot of regex for manga parsing to handle some cases where poorly named files, like "Vol. 03 Ch. 21" would end up parsing as Series "Vol. 03". * Even more handling of parser cases. Manga parser should be as it was but more robust to handle bad naming. * Fixed: Don't force metadata refresh on Scan Series, only on refresh metadata * Implemented the ability to automatically refresh after a series scan based on when server finishes. Remove a duplicate API call from series detail. * Removed another API call for series metadata that isn't needed. * Refactored Message creation to a factory, hardcoded strings are centralized, and RefreshSeriesMetadata sends an event and is refactored to be async. * Fixed a bug when really poorly named files are within a folder that contains the series name, fallback couldn't occur due to it being taken as root folder. Now we detect said condition and will go one level higher, resulting in potentially more I/O, but the series will not be deleted. * Added the Read in Incognito context item for Chapter cards * Skip an additional check for series summary for series that aren't EPUB or Archive formats. * Fixed an issue where cover image generation could occur due to a bad check on LastWriteTime on the underlying file. * Added some extra comic parser tests * Added a ScanLibrary event (not hooked up in UI) * Performance improvement on metadata service. Now when we scan for cover image changes, we emit when a change occurs and only then do we update parent entities (array copy). * Removed an hr from series detail and ensure we update the cover image for series when scan series finishes. * Updated the infinite scroller to use a Flags pattern for the debug mode. Updated a few logical conditions for mobile. * Removed the concurrency check on row progress as if too many calls hit the DB, it will throw, but it doesn't matter. Fixed a bad logic code which could cause scrolling after hitting the bottom of the chapter. * Ensure prefetching uses totalPages + 1 since we pass in totalPages as - 1 from manga reader * Fixed issue where last page of webtoon wouldn't be prefetched due to a < instead of <= on prefetching code * Implemented ability to send images from archives to the UI without incurring any extra memory pressure. * Dropdown menus now have a darker background * Webtoon reader now works on mobile. * Fixed how keyboard presses for up/down/left/right work with MANGA_UD reading mode. See issue #579 * Fixed cont reader for webtoons on mobile * Fixed a small issue where top spacer would too quickly switch to prev chapter * Updated user preferences to use same slider style. Removed some css that is not used. * Added comic parser case for "Saga 001 (2012) (Digital) (Empire-Zone)" * Added accessibility toggle to reading list order and aligned sliders to all use the same style. * Removed a todo for checking on new image serving code. It works great. * Fixed a missing await * Auth guard will now check if an existing toast is present giving same message before poping the toast. * Fixed alignment on phones for reading lists * Moved sorters so they aren't resused between multiple threads. Slightly higher memory footprint. * Fixed a broken unit test * Code smells * More unit test fixing
This commit is contained in:
parent
b62d581491
commit
cf4fd2cb9c
52 changed files with 685 additions and 336 deletions
|
|
@ -19,7 +19,7 @@ export class AdminGuard implements CanActivate {
|
|||
if (this.accountService.hasAdminRole(user)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
this.toastr.error('You are not authorized to view this page.');
|
||||
return false;
|
||||
})
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ export class AuthGuard implements CanActivate {
|
|||
if (user) {
|
||||
return true;
|
||||
}
|
||||
this.toastr.error('You are not authorized to view this page.');
|
||||
if (this.toastr.toasts.filter(toast => toast.message === 'Unauthorized' || toast.message === 'You are not authorized to view this page.').length === 0) {
|
||||
this.toastr.error('You are not authorized to view this page.');
|
||||
}
|
||||
localStorage.setItem(this.urlKey, window.location.pathname);
|
||||
this.router.navigateByUrl('/libraries');
|
||||
return false;
|
||||
|
|
|
|||
4
UI/Web/src/app/_models/events/scan-library-event.ts
Normal file
4
UI/Web/src/app/_models/events/scan-library-event.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export interface ScanLibraryEvent {
|
||||
libraryId: number;
|
||||
stage: 'complete';
|
||||
}
|
||||
3
UI/Web/src/app/_models/events/scan-series-event.ts
Normal file
3
UI/Web/src/app/_models/events/scan-series-event.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export interface ScanSeriesEvent {
|
||||
seriesId: number;
|
||||
}
|
||||
|
|
@ -259,6 +259,12 @@ export class ActionFactoryService {
|
|||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
},
|
||||
{
|
||||
action: Action.IncognitoRead,
|
||||
title: 'Read in Incognito',
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
},
|
||||
{
|
||||
action: Action.AddToReadingList,
|
||||
title: 'Add to Reading List',
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { EventEmitter, Injectable } from '@angular/core';
|
||||
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { User } from '@sentry/angular';
|
||||
import { BehaviorSubject, ReplaySubject } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { UpdateNotificationModalComponent } from '../shared/update-notification/update-notification-modal.component';
|
||||
import { ScanLibraryEvent } from '../_models/events/scan-library-event';
|
||||
import { ScanSeriesEvent } from '../_models/events/scan-series-event';
|
||||
|
||||
export enum EVENTS {
|
||||
UpdateAvailable = 'UpdateAvailable'
|
||||
UpdateAvailable = 'UpdateAvailable',
|
||||
ScanSeries = 'ScanSeries',
|
||||
ScanLibrary = 'ScanLibrary',
|
||||
RefreshMetadata = 'RefreshMetadata',
|
||||
}
|
||||
|
||||
export interface Message<T> {
|
||||
|
|
@ -26,6 +31,9 @@ export class MessageHubService {
|
|||
private messagesSource = new ReplaySubject<Message<any>>(1);
|
||||
public messages$ = this.messagesSource.asObservable();
|
||||
|
||||
public scanSeries: EventEmitter<ScanSeriesEvent> = new EventEmitter<ScanSeriesEvent>();
|
||||
public scanLibrary: EventEmitter<ScanLibraryEvent> = new EventEmitter<ScanLibraryEvent>();
|
||||
|
||||
constructor(private modalService: NgbModal) { }
|
||||
|
||||
createHubConnection(user: User) {
|
||||
|
|
@ -44,6 +52,25 @@ export class MessageHubService {
|
|||
//console.log('[Hub] Body: ', body);
|
||||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.ScanSeries, resp => {
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.ScanSeries,
|
||||
payload: resp.body
|
||||
});
|
||||
this.scanSeries.emit(resp.body);
|
||||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.ScanLibrary, resp => {
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.ScanLibrary,
|
||||
payload: resp.body
|
||||
});
|
||||
this.scanLibrary.emit(resp.body);
|
||||
// if ((resp.body as ScanLibraryEvent).stage === 'complete') {
|
||||
// this.toastr.
|
||||
// }
|
||||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.UpdateAvailable, resp => {
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.UpdateAvailable,
|
||||
|
|
|
|||
|
|
@ -2,21 +2,21 @@ export enum FITTING_OPTION {
|
|||
HEIGHT = 'full-height',
|
||||
WIDTH = 'full-width',
|
||||
ORIGINAL = 'original'
|
||||
}
|
||||
}
|
||||
|
||||
export enum SPLIT_PAGE_PART {
|
||||
NO_SPLIT = 'none',
|
||||
LEFT_PART = 'left',
|
||||
RIGHT_PART = 'right'
|
||||
}
|
||||
}
|
||||
|
||||
export enum PAGING_DIRECTION {
|
||||
FORWARD = 1,
|
||||
BACKWARDS = -1,
|
||||
}
|
||||
}
|
||||
|
||||
export enum COLOR_FILTER {
|
||||
NONE = '',
|
||||
SEPIA = 'filter-sepia',
|
||||
DARK = 'filter-dark'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
|
||||
<div class="fixed-top overlay" *ngIf="debug">
|
||||
<div class="fixed-top overlay" *ngIf="showDebugBar()">
|
||||
<strong>Captures Scroll Events:</strong> {{!this.isScrolling && this.allImagesLoaded}}
|
||||
<strong>Is Scrolling:</strong> {{isScrollingForwards() ? 'Forwards' : 'Backwards'}} {{this.isScrolling}}
|
||||
<strong>All Images Loaded:</strong> {{this.allImagesLoaded}}
|
||||
<strong>Prefetched</strong> {{minPageLoaded}}-{{maxPageLoaded}}
|
||||
<strong>Current Page:</strong>{{pageNum}}
|
||||
<strong>Width:</strong> {{webtoonImageWidth}}
|
||||
<strong>Pages:</strong> {{pageNum}} / {{totalPages}}
|
||||
<strong>At Top:</strong> {{atTop}}
|
||||
<strong>At Bottom:</strong> {{atBottom}}
|
||||
|
||||
<strong>Total Height:</strong> {{getTotalHeight()}}
|
||||
<strong>Total Scroll:</strong> {{getTotalScroll()}}
|
||||
<strong>Scroll Top:</strong> {{getScrollTop()}}
|
||||
</div>
|
||||
|
||||
<div *ngIf="atTop" class="spacer top" role="alert" (click)="loadPrevChapter.emit()">
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<ng-container *ngFor="let item of webtoonImages | async; let index = index;">
|
||||
<img src="{{item.src}}" style="display: block" class="mx-auto {{pageNum === item.page && debug ? 'active': ''}}" *ngIf="pageNum >= pageNum - bufferPages && pageNum <= pageNum + bufferPages" rel="nofollow" alt="image" (load)="onImageLoad($event)" id="page-{{item.page}}" [attr.page]="item.page" ondragstart="return false;" onselectstart="return false;">
|
||||
<img src="{{item.src}}" style="display: block" class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}}" *ngIf="pageNum >= pageNum - bufferPages && pageNum <= pageNum + bufferPages" rel="nofollow" alt="image" (load)="onImageLoad($event)" id="page-{{item.page}}" [attr.page]="item.page" ondragstart="return false;" onselectstart="return false;">
|
||||
</ng-container>
|
||||
<div *ngIf="atBottom" class="spacer bottom" role="alert" (click)="loadPrevChapter.emit()">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, Renderer2, SimpleChanges } from '@angular/core';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { BehaviorSubject, fromEvent, ReplaySubject, Subject } from 'rxjs';
|
||||
import { debounceTime, take, takeUntil } from 'rxjs/operators';
|
||||
import { debounceTime, takeUntil } from 'rxjs/operators';
|
||||
import { ReaderService } from '../../_services/reader.service';
|
||||
import { PAGING_DIRECTION } from '../_models/reader-enums';
|
||||
import { WebtoonImage } from '../_models/webtoon-image';
|
||||
|
|
@ -11,6 +11,30 @@ import { WebtoonImage } from '../_models/webtoon-image';
|
|||
*/
|
||||
const SPACER_SCROLL_INTO_PX = 200;
|
||||
|
||||
/**
|
||||
* Bitwise enums for configuring how much debug information we want
|
||||
*/
|
||||
const enum DEBUG_MODES {
|
||||
/**
|
||||
* No Debug information
|
||||
*/
|
||||
None = 0,
|
||||
/**
|
||||
* Turn on debug logging
|
||||
*/
|
||||
Logs = 2,
|
||||
/**
|
||||
* Turn on the action bar in UI
|
||||
*/
|
||||
ActionBar = 4,
|
||||
/**
|
||||
* Turn on Page outline
|
||||
*/
|
||||
Outline = 8
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-infinite-scroller',
|
||||
templateUrl: './infinite-scroller.component.html',
|
||||
|
|
@ -91,9 +115,9 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
*/
|
||||
previousScrollHeightMinusTop: number = 0;
|
||||
/**
|
||||
* Debug mode. Will show extra information
|
||||
* Debug mode. Will show extra information. Use bitwise (|) operators between different modes to enable different output
|
||||
*/
|
||||
debug: boolean = false;
|
||||
debugMode: DEBUG_MODES = DEBUG_MODES.None;
|
||||
|
||||
get minPageLoaded() {
|
||||
return Math.min(...Object.values(this.imagesLoaded));
|
||||
|
|
@ -105,6 +129,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
|
||||
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
constructor(private readerService: ReaderService, private renderer: Renderer2, private toastr: ToastrService) {}
|
||||
|
|
@ -171,39 +196,52 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
}
|
||||
|
||||
getTotalHeight() {
|
||||
let totalHeight = 0;
|
||||
document.querySelectorAll('img[id^="page-"]').forEach(img => totalHeight += img.getBoundingClientRect().height);
|
||||
return totalHeight;
|
||||
}
|
||||
getTotalScroll() {
|
||||
return document.documentElement.offsetHeight + document.documentElement.scrollTop;
|
||||
}
|
||||
getScrollTop() {
|
||||
return document.documentElement.scrollTop
|
||||
}
|
||||
|
||||
checkIfShouldTriggerContinuousReader() {
|
||||
if (this.isScrolling) return;
|
||||
|
||||
if (this.scrollingDirection === PAGING_DIRECTION.FORWARD) {
|
||||
let totalHeight = 0;
|
||||
document.querySelectorAll('img[id^="page-"]').forEach(img => totalHeight += img.getBoundingClientRect().height);
|
||||
const totalScroll = document.documentElement.offsetHeight + document.documentElement.scrollTop;
|
||||
const totalHeight = this.getTotalHeight();
|
||||
const totalScroll = this.getTotalScroll();
|
||||
|
||||
// If we were at top but have started scrolling down past page 0, remove top spacer
|
||||
if (this.atTop && this.pageNum > 0) {
|
||||
this.atTop = false;
|
||||
}
|
||||
if (totalScroll === totalHeight) {
|
||||
// debug mode will add an extra pixel from the image border + (this.debug ? 1 : 0)
|
||||
if (totalScroll === totalHeight && !this.atBottom) {
|
||||
this.atBottom = true;
|
||||
this.setPageNum(this.totalPages);
|
||||
// Scroll user back to original location
|
||||
this.previousScrollHeightMinusTop = document.documentElement.scrollTop;
|
||||
setTimeout(() => document.documentElement.scrollTop = this.previousScrollHeightMinusTop + (SPACER_SCROLL_INTO_PX / 2), 10);
|
||||
requestAnimationFrame(() => document.documentElement.scrollTop = this.previousScrollHeightMinusTop + (SPACER_SCROLL_INTO_PX / 2));
|
||||
} else if (totalScroll >= totalHeight + SPACER_SCROLL_INTO_PX && this.atBottom) {
|
||||
// This if statement will fire once we scroll into the spacer at all
|
||||
this.loadNextChapter.emit();
|
||||
}
|
||||
} else {
|
||||
if (document.documentElement.scrollTop === 0 && this.pageNum === 0) {
|
||||
// < 5 because debug mode and FF (mobile) can report non 0, despite being at 0
|
||||
if (this.getScrollTop() < 5 && this.pageNum === 0 && !this.atTop) {
|
||||
this.atBottom = false;
|
||||
if (this.atTop) {
|
||||
// If already at top, then we moving on
|
||||
this.loadPrevChapter.emit();
|
||||
}
|
||||
|
||||
this.atTop = true;
|
||||
// Scroll user back to original location
|
||||
this.previousScrollHeightMinusTop = document.documentElement.scrollHeight - document.documentElement.scrollTop;
|
||||
setTimeout(() => document.documentElement.scrollTop = document.documentElement.scrollHeight - this.previousScrollHeightMinusTop - (SPACER_SCROLL_INTO_PX / 2), 10);
|
||||
requestAnimationFrame(() => window.scrollTo(0, SPACER_SCROLL_INTO_PX));
|
||||
} else if (this.getScrollTop() < 5 && this.pageNum === 0 && this.atTop) {
|
||||
// If already at top, then we moving on
|
||||
this.loadPrevChapter.emit();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -233,7 +271,6 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.imagesLoaded = {};
|
||||
this.webtoonImages.next([]);
|
||||
this.atBottom = false;
|
||||
//this.atTop = document.documentElement.scrollTop === 0 && this.pageNum === 0;
|
||||
this.checkIfShouldTriggerContinuousReader();
|
||||
|
||||
const [startingIndex, endingIndex] = this.calculatePrefetchIndecies();
|
||||
|
|
@ -411,7 +448,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
if (startingIndex === 0 && endingIndex === 0) { return; }
|
||||
|
||||
this.debugLog('\t[PREFETCH] prefetching pages: ' + startingIndex + ' to ' + endingIndex);
|
||||
for(let i = startingIndex; i < endingIndex; i++) {
|
||||
for(let i = startingIndex; i <= endingIndex; i++) {
|
||||
this.loadWebtoonImage(i);
|
||||
}
|
||||
|
||||
|
|
@ -424,7 +461,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
|
||||
debugLog(message: string, extraData?: any) {
|
||||
if (!this.debug) { return; }
|
||||
if (!(this.debugMode & DEBUG_MODES.Logs)) return;
|
||||
|
||||
if (extraData !== undefined) {
|
||||
console.log(message, extraData);
|
||||
|
|
@ -432,4 +469,12 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
console.log(message);
|
||||
}
|
||||
}
|
||||
|
||||
showDebugBar() {
|
||||
return this.debugMode & DEBUG_MODES.ActionBar;
|
||||
}
|
||||
|
||||
showDebugOutline() {
|
||||
return this.debugMode & DEBUG_MODES.Outline;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
<div class="webtoon-images" *ngIf="readerMode === READER_MODE.WEBTOON && !isLoading">
|
||||
<app-infinite-scroller [pageNum]="pageNum" [bufferPages]="5" [goToPage]="goToPageEvent" (pageNumberChange)="handleWebtoonPageChange($event)" [totalPages]="maxPages - 1" [urlProvider]="getPageUrl" (loadNextChapter)="loadNextChapter()" (loadPrevChapter)="loadPrevChapter()"></app-infinite-scroller>
|
||||
</div>
|
||||
<ng-container *ngIf="readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD"> <!--; else webtoonClickArea; See if people want this mode WEBTOON_WITH_CLICKS-->
|
||||
<ng-container *ngIf="readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD">
|
||||
<div class="{{readerMode === READER_MODE.MANGA_LR ? 'right' : 'top'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')"></div>
|
||||
<div class="{{readerMode === READER_MODE.MANGA_LR ? 'left' : 'bottom'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')"></div>
|
||||
</ng-container>
|
||||
|
|
@ -43,14 +43,19 @@
|
|||
</div>
|
||||
|
||||
<div class="fixed-bottom overlay" *ngIf="menuOpen" [@slideFromBottom]="menuOpen">
|
||||
<div class="form-group" *ngIf="pageOptions != undefined && pageOptions.ceil != undefined && pageOptions.ceil > 0">
|
||||
<div class="form-group" *ngIf="pageOptions != undefined && pageOptions.ceil != undefined">
|
||||
<span class="sr-only" id="slider-info"></span>
|
||||
<div class="row no-gutters">
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter();resetMenuCloseTimer();" title="Prev Chapter/Volume"><i class="fa fa-fast-backward" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="prevPageDisabled || pageNum === 0" (click)="goToPage(0);resetMenuCloseTimer();" title="First Page"><i class="fa fa-step-backward" aria-hidden="true"></i></button>
|
||||
<div class="col custom-slider">
|
||||
<div class="col custom-slider" *ngIf="pageOptions.ceil > 0; else noSlider">
|
||||
<ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" [manualRefresh]="refreshSlider" (userChangeEnd)="sliderPageUpdate($event);startMenuCloseTimer()" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider>
|
||||
</div>
|
||||
<ng-template #noSlider>
|
||||
<div class="col custom-slider">
|
||||
<ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" (userChangeEnd)="startMenuCloseTimer()" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider>
|
||||
</div>
|
||||
</ng-template>
|
||||
<button class="btn btn-small btn-icon col-2" [disabled]="nextPageDisabled || pageNum >= maxPages - 1" (click)="goToPage(this.maxPages);resetMenuCloseTimer();" title="Last Page"><i class="fa fa-step-forward" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="nextChapterDisabled" (click)="loadNextChapter();resetMenuCloseTimer();" title="Next Chapter/Volume"><i class="fa fa-fast-forward" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
|
|
@ -59,7 +64,7 @@
|
|||
</div>
|
||||
<div class="row pt-4 ml-2 mr-2">
|
||||
<div class="col">
|
||||
<button class="btn btn-icon" (click)="setReadingDirection();resetMenuCloseTimer();" [disabled]="readerMode === READER_MODE.WEBTOON" aria-describedby="reading-direction" title="Reading Direction: {{readingDirection === ReadingDirection.LeftToRight ? 'Left to Right' : 'Right to Left'}}">
|
||||
<button class="btn btn-icon" (click)="setReadingDirection();resetMenuCloseTimer();" [disabled]="readerMode === READER_MODE.WEBTOON || readerMode === READER_MODE.MANGA_UD" aria-describedby="reading-direction" title="Reading Direction: {{readingDirection === ReadingDirection.LeftToRight ? 'Left to Right' : 'Right to Left'}}">
|
||||
<i class="fa fa-angle-double-{{readingDirection === ReadingDirection.LeftToRight ? 'right' : 'left'}}" aria-hidden="true"></i>
|
||||
<span id="reading-direction" class="sr-only">{{readingDirection === ReadingDirection.LeftToRight ? 'Left to Right' : 'Right to Left'}}</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -92,20 +92,6 @@ canvas {
|
|||
}
|
||||
|
||||
|
||||
.center-menu {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: $side-width;
|
||||
width: $center-width;
|
||||
border-bottom: $dash-width dashed $secondary-color;
|
||||
text-align: center;
|
||||
z-index: 2;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
.right {
|
||||
position: fixed;
|
||||
right: 0px;
|
||||
|
|
|
|||
|
|
@ -347,11 +347,26 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
@HostListener('window:keyup', ['$event'])
|
||||
handleKeyPress(event: KeyboardEvent) {
|
||||
if (event.key === KEY_CODES.RIGHT_ARROW || event.key === KEY_CODES.DOWN_ARROW) {
|
||||
this.readingDirection === ReadingDirection.LeftToRight ? this.nextPage() : this.prevPage();
|
||||
} else if (event.key === KEY_CODES.LEFT_ARROW || event.key === KEY_CODES.UP_ARROW) {
|
||||
this.readingDirection === ReadingDirection.LeftToRight ? this.prevPage() : this.nextPage();
|
||||
} else if (event.key === KEY_CODES.ESC_KEY) {
|
||||
|
||||
switch (this.readerMode) {
|
||||
case READER_MODE.MANGA_LR:
|
||||
if (event.key === KEY_CODES.RIGHT_ARROW) {
|
||||
this.readingDirection === ReadingDirection.LeftToRight ? this.nextPage() : this.prevPage();
|
||||
} else if (event.key === KEY_CODES.LEFT_ARROW) {
|
||||
this.readingDirection === ReadingDirection.LeftToRight ? this.prevPage() : this.nextPage();
|
||||
}
|
||||
break;
|
||||
case READER_MODE.MANGA_UD:
|
||||
case READER_MODE.WEBTOON:
|
||||
if (event.key === KEY_CODES.DOWN_ARROW) {
|
||||
this.nextPage()
|
||||
} else if (event.key === KEY_CODES.UP_ARROW) {
|
||||
this.prevPage()
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (event.key === KEY_CODES.ESC_KEY) {
|
||||
if (this.menuOpen) {
|
||||
this.toggleMenu();
|
||||
event.stopPropagation();
|
||||
|
|
@ -427,7 +442,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
});
|
||||
|
||||
// ! Should I move the prefetching code if we start in webtoon reader mode?
|
||||
|
||||
const images = [];
|
||||
for (let i = 0; i < PREFETCH_PAGES + 2; i++) {
|
||||
images.push(new Image());
|
||||
|
|
@ -967,6 +982,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
saveSettings() {
|
||||
// NOTE: This is not called anywhere
|
||||
if (this.user === undefined) return;
|
||||
|
||||
const data: Preferences = {
|
||||
|
|
@ -1008,6 +1024,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
* Bookmarks the current page for the chapter
|
||||
*/
|
||||
bookmarkPage() {
|
||||
// TODO: Show some sort of UI visual to show that a page was bookmarked
|
||||
const pageNum = this.pageNum;
|
||||
if (this.pageBookmarked) {
|
||||
this.readerService.unbookmark(this.seriesId, this.volumeId, this.chapterId, pageNum).pipe(take(1)).subscribe(() => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="container-fluid">
|
||||
<a class="sr-only sr-only-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
|
||||
<a class="navbar-brand" routerLink="/library" routerLinkActive="active"><img class="logo" src="../../assets/images/logo.png" alt="kavita icon" aria-hidden="true"/><span class="phone-hidden"> Kavita</span></a>
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<ul class="navbar-nav col mr-auto">
|
||||
|
||||
<div class="nav-item" *ngIf="(accountService.currentUser$ | async) as user">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -2,16 +2,19 @@
|
|||
<div class="example-box" *ngFor="let item of items; index as i" cdkDrag [cdkDragData]="item" cdkDragBoundary=".example-list">
|
||||
<div class="mr-3 align-middle">
|
||||
<i class="fa fa-grip-vertical drag-handle" aria-hidden="true" cdkDragHandle></i>
|
||||
<label for="reorder-{{i}}" class="sr-only">Reorder</label>
|
||||
<input *ngIf="accessibilityMode" id="reorder-{{i}}" type="number" min="0" [max]="items.length - 1" [value]="i" style="width: 40px" (keydown.enter)="updateIndex(i, item)" aria-describedby="instructions">
|
||||
</div>
|
||||
|
||||
<ng-container style="display: inline-block" [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
||||
|
||||
<div class="align-middle" style="padding-top: 40px">
|
||||
<label for="reorder-{{i}}" class="sr-only">Reorder</label>
|
||||
<input *ngIf="accessibilityMode" id="reorder-{{i}}" type="number" min="0" [max]="items.length - 1" [value]="i" style="width: 40px" (focusout)="updateIndex(i, item)" (keydown.enter)="updateIndex(i, item)" aria-describedby="instructions">
|
||||
</div>
|
||||
<button class="btn btn-icon pull-right" (click)="removeItem(item, i)">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
<span class="sr-only" attr.aria-labelledby="item.id--{{i}}">Remove item</span>
|
||||
</button>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
.example-list {
|
||||
min-width: 500px;
|
||||
max-width: 100%;
|
||||
//border: solid 1px #ccc;
|
||||
min-height: 60px;
|
||||
display: block;
|
||||
//background: white;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -12,13 +10,9 @@
|
|||
.example-box {
|
||||
padding: 20px 10px;
|
||||
border-bottom: solid 1px #ccc;
|
||||
//color: rgba(0, 0, 0, 0.87);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
//align-items: center;
|
||||
//justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
//background: white;
|
||||
font-size: 14px;
|
||||
|
||||
.drag-handle {
|
||||
|
|
|
|||
|
|
@ -1,46 +1,52 @@
|
|||
<div class="container mt-2" *ngIf="readingList">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-10 col-xs-8 col-sm-6">
|
||||
<div class="row no-gutters">
|
||||
<div class="container-sm mt-2" *ngIf="readingList">
|
||||
<div class="mb-3">
|
||||
<!-- Title row-->
|
||||
<div class="row no-gutters">
|
||||
|
||||
<h2 style="display: inline-block">
|
||||
<span *ngIf="actions.length > 0">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="readingList.title"></app-card-actionables>
|
||||
</span>
|
||||
{{readingList.title}} <span *ngIf="readingList?.promoted">(<i class="fa fa-angle-double-up" aria-hidden="true"></i>)</span>
|
||||
<span class="badge badge-primary badge-pill" attr.aria-label="{{items.length}} total items">{{items.length}}</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="row no-gutters">
|
||||
<div class="mr-2">
|
||||
<button class="btn btn-primary" title="Read" (click)="read()">
|
||||
<span>
|
||||
<i class="fa fa-book-open" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> Read</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-secondary" (click)="removeRead()" [disabled]="readingList?.promoted && !this.isAdmin">
|
||||
<span>
|
||||
<i class="fa fa-check"></i>
|
||||
</span>
|
||||
<span class="read-btn--text"> Remove Read</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters mt-2">
|
||||
<app-read-more [text]="readingList.summary" [maxLength]="250"></app-read-more>
|
||||
</div>
|
||||
<h2 style="display: inline-block">
|
||||
<span *ngIf="actions.length > 0">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="readingList.title"></app-card-actionables>
|
||||
</span>
|
||||
{{readingList.title}} <span *ngIf="readingList?.promoted">(<i class="fa fa-angle-double-up" aria-hidden="true"></i>)</span>
|
||||
<span class="badge badge-primary badge-pill" attr.aria-label="{{items.length}} total items">{{items.length}}</span>
|
||||
</h2>
|
||||
</div>
|
||||
<!-- Action row-->
|
||||
<div class="row no-gutters">
|
||||
<div class="mr-2">
|
||||
<button class="btn btn-primary" title="Read" (click)="read()">
|
||||
<span>
|
||||
<i class="fa fa-book-open" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> Read</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-secondary" (click)="removeRead()" [disabled]="readingList?.promoted && !this.isAdmin">
|
||||
<span>
|
||||
<i class="fa fa-check"></i>
|
||||
</span>
|
||||
<span class="read-btn--text"> Remove Read</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-2 mt-2" *ngIf="!(readingList?.promoted && !this.isAdmin)">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" id="accessibilit-mode" [value]="accessibilityMode" (change)="accessibilityMode = !accessibilityMode">
|
||||
<label class="form-check-label" for="accessibilit-mode">Order Numbers</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Summary row-->
|
||||
<div class="row no-gutters mt-2">
|
||||
<app-read-more [text]="readingList.summary" [maxLength]="250"></app-read-more>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="items.length === 0">
|
||||
No chapters added
|
||||
</div>
|
||||
|
||||
<!-- NOTE: It might be nice to have a switch for the accessibility toggle -->
|
||||
<app-dragable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" (itemRemove)="itemRemoved($event)">
|
||||
<app-dragable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" (itemRemove)="itemRemoved($event)" [accessibilityMode]="accessibilityMode">
|
||||
<ng-template #draggableItem let-item let-position="idx">
|
||||
<div class="media" style="width: 100%;">
|
||||
<img width="74px" style="width: 74px;" class="img-top lazyload mr-3" [src]="imageService.placeholderImage" [attr.data-src]="imageService.getChapterCoverImage(item.chapterId)"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
.container-sm {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ export class ReadingListDetailComponent implements OnInit {
|
|||
actions: Array<ActionItem<any>> = [];
|
||||
isAdmin: boolean = false;
|
||||
isLoading: boolean = false;
|
||||
accessibilityMode: boolean = false;
|
||||
|
||||
// Downloading
|
||||
hasDownloadingRole: boolean = false;
|
||||
|
|
|
|||
|
|
@ -95,8 +95,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
|
||||
<div>
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav-tabs nav-pills" [destroyOnHide]="false">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NgbModal, NgbRatingConfig } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { finalize, take, takeWhile } from 'rxjs/operators';
|
||||
import { Subject } from 'rxjs';
|
||||
import { finalize, take, takeUntil, takeWhile } from 'rxjs/operators';
|
||||
import { CardDetailsModalComponent } from '../cards/_modals/card-details-modal/card-details-modal.component';
|
||||
import { EditSeriesModalComponent } from '../cards/_modals/edit-series-modal/edit-series-modal.component';
|
||||
import { ConfirmConfig } from '../shared/confirm-dialog/_models/confirm-config';
|
||||
|
|
@ -13,6 +14,7 @@ import { DownloadService } from '../shared/_services/download.service';
|
|||
import { UtilityService } from '../shared/_services/utility.service';
|
||||
import { ReviewSeriesModalComponent } from '../_modals/review-series-modal/review-series-modal.component';
|
||||
import { Chapter } from '../_models/chapter';
|
||||
import { ScanSeriesEvent } from '../_models/events/scan-series-event';
|
||||
import { LibraryType } from '../_models/library';
|
||||
import { MangaFormat } from '../_models/manga-format';
|
||||
import { Series } from '../_models/series';
|
||||
|
|
@ -23,6 +25,7 @@ import { ActionItem, ActionFactoryService, Action } from '../_services/action-fa
|
|||
import { ActionService } from '../_services/action.service';
|
||||
import { ImageService } from '../_services/image.service';
|
||||
import { LibraryService } from '../_services/library.service';
|
||||
import { MessageHubService } from '../_services/message-hub.service';
|
||||
import { ReaderService } from '../_services/reader.service';
|
||||
import { SeriesService } from '../_services/series.service';
|
||||
|
||||
|
|
@ -32,7 +35,7 @@ import { SeriesService } from '../_services/series.service';
|
|||
templateUrl: './series-detail.component.html',
|
||||
styleUrls: ['./series-detail.component.scss']
|
||||
})
|
||||
export class SeriesDetailComponent implements OnInit {
|
||||
export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
|
||||
series!: Series;
|
||||
volumes: Volume[] = [];
|
||||
|
|
@ -76,6 +79,8 @@ export class SeriesDetailComponent implements OnInit {
|
|||
*/
|
||||
actionInProgress: boolean = false;
|
||||
|
||||
private onDestroy: Subject<void> = new Subject();
|
||||
|
||||
|
||||
get LibraryType(): typeof LibraryType {
|
||||
return LibraryType;
|
||||
|
|
@ -97,7 +102,7 @@ export class SeriesDetailComponent implements OnInit {
|
|||
private actionFactoryService: ActionFactoryService, private libraryService: LibraryService,
|
||||
private confirmService: ConfirmService, private titleService: Title,
|
||||
private downloadService: DownloadService, private actionService: ActionService,
|
||||
public imageSerivce: ImageService) {
|
||||
public imageSerivce: ImageService, private messageHub: MessageHubService) {
|
||||
ratingConfig.max = 5;
|
||||
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
|
|
@ -116,20 +121,25 @@ export class SeriesDetailComponent implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
this.messageHub.scanSeries.pipe(takeUntil(this.onDestroy)).subscribe((event: ScanSeriesEvent) => {
|
||||
if (event.seriesId == this.series.id)
|
||||
this.loadSeries(seriesId);
|
||||
this.seriesImage = this.imageService.randomize(this.imageService.getSeriesCoverImage(this.series.id));
|
||||
this.toastr.success('Scan series completed');
|
||||
});
|
||||
|
||||
const seriesId = parseInt(routeId, 10);
|
||||
this.libraryId = parseInt(libraryId, 10);
|
||||
this.seriesImage = this.imageService.getSeriesCoverImage(seriesId);
|
||||
this.loadSeriesMetadata(seriesId);
|
||||
this.libraryService.getLibraryType(this.libraryId).subscribe(type => {
|
||||
this.libraryType = type;
|
||||
this.loadSeries(seriesId);
|
||||
});
|
||||
}
|
||||
|
||||
loadSeriesMetadata(seriesId: number) {
|
||||
this.seriesService.getMetadata(seriesId).subscribe(metadata => {
|
||||
this.seriesMetadata = metadata;
|
||||
});
|
||||
ngOnDestroy() {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
handleSeriesActionCallback(action: Action, series: Series) {
|
||||
|
|
@ -422,7 +432,6 @@ export class SeriesDetailComponent implements OnInit {
|
|||
window.scrollTo(0, 0);
|
||||
if (closeResult.success) {
|
||||
this.loadSeries(this.series.id);
|
||||
this.loadSeriesMetadata(this.series.id);
|
||||
if (closeResult.coverImageUpdate) {
|
||||
// Random triggers a load change without any problems with API
|
||||
this.seriesImage = this.imageService.randomize(this.imageService.getSeriesCoverImage(this.series.id));
|
||||
|
|
|
|||
|
|
@ -142,20 +142,20 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label id="font-size">Font Size</label>
|
||||
<ngx-slider [options]="bookReaderFontSizeOptions" formControlName="bookReaderFontSize" aria-labelledby="font-size"></ngx-slider>
|
||||
<div class="custom-slider"><ngx-slider [options]="bookReaderFontSizeOptions" formControlName="bookReaderFontSize" aria-labelledby="font-size"></ngx-slider></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Line Height</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="bookLineHeightOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookLineHeightOptionTooltip>How much spacing between the lines of the book</ng-template>
|
||||
<span class="sr-only" id="settings-booklineheight-option-help">How much spacing between the lines of the book</span>
|
||||
<ngx-slider [options]="bookReaderLineSpacingOptions" formControlName="bookReaderLineSpacing" aria-describedby="settings-booklineheight-option-help"></ngx-slider>
|
||||
<div class="custom-slider"><ngx-slider [options]="bookReaderLineSpacingOptions" formControlName="bookReaderLineSpacing" aria-describedby="settings-booklineheight-option-help"></ngx-slider></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Margin</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="bookReaderMarginOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookReaderMarginOptionTooltip>How much spacing on each side of the screen. This will override to 0 on mobile devices regardless of this setting.</ng-template>
|
||||
<span class="sr-only" id="settings-bookmargin-option-help">How much spacing on each side of the screen. This will override to 0 on mobile devices regardless of this setting.</span>
|
||||
<ngx-slider [options]="bookReaderMarginOptions" formControlName="bookReaderMargin" aria-describedby="bookmargin"></ngx-slider>
|
||||
<div class="custom-slider"><ngx-slider [options]="bookReaderMarginOptions" formControlName="bookReaderMargin" aria-describedby="bookmargin"></ngx-slider></div>
|
||||
</div>
|
||||
|
||||
<div class="float-right mb-3">
|
||||
|
|
|
|||
|
|
@ -1,3 +1,55 @@
|
|||
@import '../../../theme/colors';
|
||||
|
||||
.invalid-feedback {
|
||||
display: inline-block !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Slider handle override
|
||||
::ng-deep {
|
||||
.custom-slider .ngx-slider .ngx-slider-bar {
|
||||
background: #545a52;
|
||||
height: 2px;
|
||||
}
|
||||
.custom-slider .ngx-slider .ngx-slider-selection {
|
||||
background: $primary-color;
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-pointer {
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
top: auto; /* to remove the default positioning */
|
||||
bottom: 0;
|
||||
background-color: $primary-color;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-pointer:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-bubble {
|
||||
bottom: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-limit {
|
||||
font-weight: bold;
|
||||
color: white !important;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-tick {
|
||||
width: 1px;
|
||||
height: 10px;
|
||||
margin-left: 4px;
|
||||
border-radius: 0;
|
||||
background: #ffe4d1;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-tick.ngx-slider-selected {
|
||||
background: $primary-color;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ $dark-primary-color: rgba(74, 198, 148, 0.9);
|
|||
$dark-text-color: #efefef;
|
||||
$dark-hover-color: #4ac694;
|
||||
$dark-form-background: rgba(1, 4, 9, 0.5);
|
||||
$dark-form-background-no-opacity: rgb(1, 4, 9);
|
||||
$dark-form-placeholder: #efefef;
|
||||
$dark-link-color: rgb(88, 166, 255);
|
||||
$dark-icon-color: white;
|
||||
|
|
@ -126,6 +127,7 @@ $dark-item-accent-bg: #292d32;
|
|||
|
||||
|
||||
.dropdown-menu {
|
||||
background-color: #171719;
|
||||
.dropdown-item:hover, .dropdown-item:focus {
|
||||
color: $dark-text-color;
|
||||
background-color: $dark-hover-color;
|
||||
|
|
|
|||
|
|
@ -51,54 +51,6 @@ body {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Slider handle override
|
||||
::ng-deep {
|
||||
.custom-slider .ngx-slider .ngx-slider-bar {
|
||||
background: #ffe4d1;
|
||||
height: 2px;
|
||||
}
|
||||
.custom-slider .ngx-slider .ngx-slider-selection {
|
||||
background: orange;
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-pointer {
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
top: auto; /* to remove the default positioning */
|
||||
bottom: 0;
|
||||
background-color: #333;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-pointer:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-bubble {
|
||||
bottom: 14px;
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-limit {
|
||||
font-weight: bold;
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-tick {
|
||||
width: 1px;
|
||||
height: 10px;
|
||||
margin-left: 4px;
|
||||
border-radius: 0;
|
||||
background: #ffe4d1;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-tick.ngx-slider-selected {
|
||||
background: orange;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Utiliities
|
||||
@include media-breakpoint-down(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue