mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Reader Fixes and Enhancements (#880)
* Don't show an exception when bookmarking doesn't have anything to change. * Cleaned up the bookmark code a bit. * Implemented fullscreen mode in the web reader. Refactored User Settings to move Password and 3rd Party Clients to a tab rather than accordion. Removed color filters for web reader. * Implemented fullscreen mode into book reader * Added some code for toggling fullscreen which re-renders the screen to ensure the fitting works optimially * Fixed an issue where moving from FitToScreen -> Split (L/R) wouldn't render the screen correctly due to canvas not being reset. * Fixed bad optimization and scaling when drawing fit to screen * Removed left/right highlights on page direction change in favor for icons. Double arrow will dictate the page change. * Reduced overlay auto close time to 3 seconds * Updated the paginging direction overlay to use icons and colors. Added a blur effect on menus * Removed debug flags
This commit is contained in:
parent
ca5c67020e
commit
720c52f494
@ -468,18 +468,15 @@ namespace API.Controllers
|
|||||||
_unitOfWork.UserRepository.Update(user);
|
_unitOfWork.UserRepository.Update(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
if (await _unitOfWork.CommitAsync())
|
|
||||||
{
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
await _unitOfWork.RollbackAsync();
|
await _unitOfWork.RollbackAsync();
|
||||||
|
return BadRequest("Could not save bookmark");
|
||||||
}
|
}
|
||||||
|
|
||||||
return BadRequest("Could not save bookmark");
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -169,7 +169,7 @@ namespace API.Controllers
|
|||||||
|
|
||||||
|
|
||||||
_logger.LogInformation("Server Settings updated");
|
_logger.LogInformation("Server Settings updated");
|
||||||
_taskScheduler.ScheduleTasks();
|
await _taskScheduler.ScheduleTasks();
|
||||||
return Ok(updateSettingsDto);
|
return Ok(updateSettingsDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1317
API/Data/Migrations/20211227180752_FullscreenPref.Designer.cs
generated
Normal file
1317
API/Data/Migrations/20211227180752_FullscreenPref.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
API/Data/Migrations/20211227180752_FullscreenPref.cs
Normal file
26
API/Data/Migrations/20211227180752_FullscreenPref.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class FullscreenPref : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "FullscreenMode",
|
||||||
|
table: "AppUserPreferences",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "FullscreenMode",
|
||||||
|
table: "AppUserPreferences");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -183,6 +183,9 @@ namespace API.Data.Migrations
|
|||||||
b.Property<bool>("BookReaderTapToPaginate")
|
b.Property<bool>("BookReaderTapToPaginate")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("FullscreenMode")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("PageSplitOption")
|
b.Property<int>("PageSplitOption")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
@ -514,7 +514,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
var retSeries = query.Where(s => s.AppUserId == userId
|
var retSeries = query.Where(s => s.AppUserId == userId
|
||||||
&& s.PagesRead > 0
|
&& s.PagesRead > 0
|
||||||
&& s.PagesRead < s.Series.Pages)
|
&& s.PagesRead < s.Series.Pages)
|
||||||
.OrderByDescending(s => s.LastModified)
|
.OrderByDescending(s => s.LastModified) // TODO: This needs to be Chapter Created (Max)
|
||||||
.ThenByDescending(s => s.Series.LastModified)
|
.ThenByDescending(s => s.Series.LastModified)
|
||||||
.Select(s => s.Series)
|
.Select(s => s.Series)
|
||||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||||
|
@ -4,10 +4,19 @@ namespace API.Entities.Enums
|
|||||||
{
|
{
|
||||||
public enum LibraryType
|
public enum LibraryType
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Uses Manga regex for filename parsing
|
||||||
|
/// </summary>
|
||||||
[Description("Manga")]
|
[Description("Manga")]
|
||||||
Manga = 0,
|
Manga = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Uses Comic regex for filename parsing
|
||||||
|
/// </summary>
|
||||||
[Description("Comic")]
|
[Description("Comic")]
|
||||||
Comic = 1,
|
Comic = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// Uses Manga regex for filename parsing also uses epub metadata
|
||||||
|
/// </summary>
|
||||||
[Description("Book")]
|
[Description("Book")]
|
||||||
Book = 2,
|
Book = 2,
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import { PageSplitOption } from './page-split-option';
|
import { PageSplitOption } from './page-split-option';
|
||||||
import { READER_MODE } from './reader-mode';
|
import { READER_MODE } from './reader-mode';
|
||||||
import { ReadingDirection } from './reading-direction';
|
import { ReadingDirection } from './reading-direction';
|
||||||
|
@ -193,4 +193,48 @@ export class ReaderService {
|
|||||||
}
|
}
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enterFullscreen(el: Element, callback?: VoidFunction) {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
if (el.requestFullscreen) {
|
||||||
|
el.requestFullscreen().then(() => {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// else if (el.mozRequestFullScreen) {
|
||||||
|
// el.mozRequestFullScreen();
|
||||||
|
// } else if (el.webkitRequestFullscreen) {
|
||||||
|
// el.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
|
||||||
|
// } else if (el.msRequestFullscreen) {
|
||||||
|
// el.msRequestFullscreen();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exitFullscreen(callback?: VoidFunction) {
|
||||||
|
if (document.exitFullscreen && this.checkFullscreenMode()) {
|
||||||
|
document.exitFullscreen().then(() => {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// else if (document.msExitFullscreen) {
|
||||||
|
// document.msExitFullscreen();
|
||||||
|
// } else if (document.mozCancelFullScreen) {
|
||||||
|
// document.mozCancelFullScreen();
|
||||||
|
// } else if (document.webkitExitFullscreen) {
|
||||||
|
// document.webkitExitFullscreen();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns If document is in fullscreen mode
|
||||||
|
*/
|
||||||
|
checkFullscreenMode() {
|
||||||
|
return document.fullscreenElement != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="container-flex {{darkMode ? 'dark-mode' : ''}}">
|
<div class="container-flex {{darkMode ? 'dark-mode' : ''}}" #reader>
|
||||||
<div class="fixed-top" #stickyTop>
|
<div class="fixed-top" #stickyTop>
|
||||||
<a class="sr-only sr-only-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
|
<a class="sr-only sr-only-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
|
||||||
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
||||||
@ -57,6 +57,17 @@
|
|||||||
<span class="sr-only" id="tap-pagination-help">The ability to click the sides of the page to page left and right</span>
|
<span class="sr-only" id="tap-pagination-help">The ability to click the sides of the page to page left and right</span>
|
||||||
<button (click)="toggleClickToPaginate()" class="btn btn-icon" aria-labelledby="tap-pagination"><i class="fa fa-arrows-alt-h {{clickToPaginate ? 'icon-primary-color' : ''}}" aria-hidden="true"></i><span *ngIf="darkMode"> {{clickToPaginate ? 'On' : 'Off'}}</span></button>
|
<button (click)="toggleClickToPaginate()" class="btn btn-icon" aria-labelledby="tap-pagination"><i class="fa fa-arrows-alt-h {{clickToPaginate ? 'icon-primary-color' : ''}}" aria-hidden="true"></i><span *ngIf="darkMode"> {{clickToPaginate ? 'On' : 'Off'}}</span></button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<label id="fullscreen">Fullscreen <i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="fullscreenTooltip" role="button" tabindex="0" aria-describedby="fullscreen-help"></i></label>
|
||||||
|
<ng-template #fullscreenTooltip>Put reader in fullscreen mode</ng-template>
|
||||||
|
<span class="sr-only" id="fullscreen-help">
|
||||||
|
<ng-container [ngTemplateOutlet]="fullscreenTooltip"></ng-container>
|
||||||
|
</span>
|
||||||
|
<button (click)="toggleFullscreen()" class="btn btn-icon" aria-labelledby="fullscreen">
|
||||||
|
<i class="fa {{this.isFullscreen ? 'fa-compress-alt' : 'fa-expand-alt'}} {{isFullscreen ? 'icon-primary-color' : ''}}" aria-hidden="true"></i>
|
||||||
|
<span *ngIf="darkMode"> {{isFullscreen ? 'Exit' : 'Enter'}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="row no-gutters justify-content-between">
|
<div class="row no-gutters justify-content-between">
|
||||||
<button (click)="resetSettings()" class="btn btn-primary col">Reset to Defaults</button>
|
<button (click)="resetSettings()" class="btn btn-primary col">Reset to Defaults</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -111,6 +111,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
@ViewChild('readingHtml', {static: false}) readingHtml!: ElementRef<HTMLDivElement>;
|
@ViewChild('readingHtml', {static: false}) readingHtml!: ElementRef<HTMLDivElement>;
|
||||||
@ViewChild('readingSection', {static: false}) readingSectionElemRef!: ElementRef<HTMLDivElement>;
|
@ViewChild('readingSection', {static: false}) readingSectionElemRef!: ElementRef<HTMLDivElement>;
|
||||||
@ViewChild('stickyTop', {static: false}) stickyTopElemRef!: ElementRef<HTMLDivElement>;
|
@ViewChild('stickyTop', {static: false}) stickyTopElemRef!: ElementRef<HTMLDivElement>;
|
||||||
|
@ViewChild('reader', {static: true}) reader!: ElementRef;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Next Chapter Id. This is not garunteed to be a valid ChapterId. Prefetched on page load (non-blocking).
|
* Next Chapter Id. This is not garunteed to be a valid ChapterId. Prefetched on page load (non-blocking).
|
||||||
@ -185,6 +186,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
originalBodyColor: string | undefined;
|
originalBodyColor: string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the web browser is in fullscreen mode
|
||||||
|
*/
|
||||||
|
isFullscreen: boolean = false;
|
||||||
|
|
||||||
darkModeStyles = `
|
darkModeStyles = `
|
||||||
*:not(input), *:not(select), *:not(code), *:not(:link), *:not(.ngx-toastr) {
|
*:not(input), *:not(select), *:not(code), *:not(:link), *:not(.ngx-toastr) {
|
||||||
color: #dcdcdc !important;
|
color: #dcdcdc !important;
|
||||||
@ -365,6 +371,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.clickToPaginateVisualOverlayTimeout2 = undefined;
|
this.clickToPaginateVisualOverlayTimeout2 = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.readerService.exitFullscreen();
|
||||||
|
|
||||||
this.onDestroy.next();
|
this.onDestroy.next();
|
||||||
this.onDestroy.complete();
|
this.onDestroy.complete();
|
||||||
}
|
}
|
||||||
@ -1014,4 +1022,17 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.readerService.saveProgress(this.seriesId, this.volumeId, this.chapterId, this.pageNum).pipe(take(1)).subscribe(() => {/* No operation */});
|
this.readerService.saveProgress(this.seriesId, this.volumeId, this.chapterId, this.pageNum).pipe(take(1)).subscribe(() => {/* No operation */});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleFullscreen() {
|
||||||
|
this.isFullscreen = this.readerService.checkFullscreenMode();
|
||||||
|
if (this.isFullscreen) {
|
||||||
|
this.readerService.exitFullscreen(() => {
|
||||||
|
this.isFullscreen = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.readerService.enterFullscreen(this.reader.nativeElement, () => {
|
||||||
|
this.isFullscreen = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,3 @@ export enum PAGING_DIRECTION {
|
|||||||
BACKWARDS = -1,
|
BACKWARDS = -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum COLOR_FILTER {
|
|
||||||
NONE = '',
|
|
||||||
SEPIA = 'filter-sepia',
|
|
||||||
DARK = 'filter-dark'
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="reader">
|
<div class="reader" #reader>
|
||||||
<div class="fixed-top overlay" *ngIf="menuOpen" [@slideFromTop]="menuOpen">
|
<div class="fixed-top overlay" *ngIf="menuOpen" [@slideFromTop]="menuOpen">
|
||||||
<div style="display: flex; margin-top: 5px;">
|
<div style="display: flex; margin-top: 5px;">
|
||||||
<button class="btn btn-icon" style="height: 100%" title="Back" (click)="closeReader()">
|
<button class="btn btn-icon" style="height: 100%" title="Back" (click)="closeReader()">
|
||||||
@ -24,7 +24,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div (click)="toggleMenu()" class="reading-area">
|
<div (click)="toggleMenu()" class="reading-area">
|
||||||
<canvas #content class="{{getFittingOptionClass()}} {{this.colorMode}} {{readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD ? '' : 'd-none'}} "
|
<canvas #content class="{{getFittingOptionClass()}} {{readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD ? '' : 'd-none'}} {{showClickOverlay ? 'blur' : ''}}"
|
||||||
ondragstart="return false;" onselectstart="return false;">
|
ondragstart="return false;" onselectstart="return false;">
|
||||||
</canvas>
|
</canvas>
|
||||||
<div class="webtoon-images" *ngIf="readerMode === READER_MODE.WEBTOON && !isLoading">
|
<div class="webtoon-images" *ngIf="readerMode === READER_MODE.WEBTOON && !isLoading">
|
||||||
@ -39,8 +39,18 @@
|
|||||||
[bookmarkPage]="showBookmarkEffectEvent"></app-infinite-scroller>
|
[bookmarkPage]="showBookmarkEffectEvent"></app-infinite-scroller>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD">
|
<ng-container *ngIf="readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD">
|
||||||
<div class="{{readerMode === READER_MODE.MANGA_LR ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')"></div>
|
<div class="pagination-area {{readerMode === READER_MODE.MANGA_LR ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')">
|
||||||
<div class="{{readerMode === READER_MODE.MANGA_LR ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')"></div>
|
<div *ngIf="showClickOverlay">
|
||||||
|
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === READER_MODE.MANGA_LR ? 'right' : 'down'}}"
|
||||||
|
title="Next Page" aria-hidden="true"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pagination-area {{readerMode === READER_MODE.MANGA_LR ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')">
|
||||||
|
<div *ngIf="showClickOverlay">
|
||||||
|
<i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === READER_MODE.MANGA_LR ? 'left' : 'up'}}"
|
||||||
|
title="Previous Page" aria-hidden="true"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -78,9 +88,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<button class="btn btn-icon {{this.colorMode}}" [disabled]="readerMode === READER_MODE.WEBTOON" title="Color Options: {{colorOptionName}}" (click)="toggleColorMode();resetMenuCloseTimer();">
|
<button class="btn btn-icon" title="{{this.isFullscreen ? 'Collapse' : 'Fullscreen'}}" (click)="toggleFullscreen();resetMenuCloseTimer();">
|
||||||
<i class="fa fa-palette" aria-hidden="true"></i>
|
<i class="fa {{this.isFullscreen ? 'fa-compress-alt' : 'fa-expand-alt'}}" aria-hidden="true"></i>
|
||||||
<span class="sr-only"></span>
|
<span class="sr-only">{{this.isFullscreen ? 'Collapse' : 'Fullscreen'}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -6,8 +6,6 @@ $side-width: 25%;
|
|||||||
$dash-width: 3px;
|
$dash-width: 3px;
|
||||||
$pointer-offset: 5px;
|
$pointer-offset: 5px;
|
||||||
|
|
||||||
$secondary-color: #CCC;
|
|
||||||
|
|
||||||
|
|
||||||
@media(min-width: 600px) {
|
@media(min-width: 600px) {
|
||||||
.overlay .left .i {
|
.overlay .left .i {
|
||||||
@ -18,7 +16,6 @@ $secondary-color: #CCC;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.btn-icon {
|
.btn-icon {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
@ -35,6 +32,10 @@ canvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 48%;
|
left: 48%;
|
||||||
@ -48,14 +49,6 @@ canvas {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-dark {
|
|
||||||
filter: brightness(0.65);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-sepia {
|
|
||||||
filter: sepia(80%) hue-rotate(349deg) saturate(200%) brightness(0.65);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-menu {
|
.bottom-menu {
|
||||||
padding: 20px 20px;
|
padding: 20px 20px;
|
||||||
}
|
}
|
||||||
@ -63,6 +56,7 @@ canvas {
|
|||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
background-color: rgba(0,0,0,0.5);
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,14 +220,27 @@ canvas {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight {
|
.pagination-area {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: white;
|
||||||
|
font-size: 42px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
background-color: rgba(65, 225, 100, 0.5) !important;
|
background-color: rgba(65, 225, 100, 0.5) !important;
|
||||||
animation: fadein .5s both;
|
animation: fadein .5s both;
|
||||||
}
|
backdrop-filter: blur(10px);
|
||||||
.highlight-2 {
|
}
|
||||||
|
.highlight-2 {
|
||||||
background-color: rgba(65, 105, 225, 0.5) !important;
|
background-color: rgba(65, 105, 225, 0.5) !important;
|
||||||
animation: fadein .5s both;
|
animation: fadein .5s both;
|
||||||
}
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.bookmark-effect {
|
.bookmark-effect {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, OnDestroy, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core';
|
||||||
import { Location } from '@angular/common';
|
import { DOCUMENT, Location } from '@angular/common';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { take, takeUntil } from 'rxjs/operators';
|
import { take, takeUntil } from 'rxjs/operators';
|
||||||
import { User } from '../_models/user';
|
import { User } from '../_models/user';
|
||||||
@ -19,7 +19,7 @@ import { Stack } from '../shared/data-structures/stack';
|
|||||||
import { ChangeContext, LabelType, Options } from '@angular-slider/ngx-slider';
|
import { ChangeContext, LabelType, Options } from '@angular-slider/ngx-slider';
|
||||||
import { trigger, state, style, transition, animate } from '@angular/animations';
|
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||||
import { ChapterInfo } from './_models/chapter-info';
|
import { ChapterInfo } from './_models/chapter-info';
|
||||||
import { COLOR_FILTER, FITTING_OPTION, PAGING_DIRECTION, SPLIT_PAGE_PART } from './_models/reader-enums';
|
import { FITTING_OPTION, PAGING_DIRECTION, SPLIT_PAGE_PART } from './_models/reader-enums';
|
||||||
import { pageSplitOptions, scalingOptions } from '../_models/preferences/preferences';
|
import { pageSplitOptions, scalingOptions } from '../_models/preferences/preferences';
|
||||||
import { READER_MODE } from '../_models/preferences/reader-mode';
|
import { READER_MODE } from '../_models/preferences/reader-mode';
|
||||||
import { MangaFormat } from '../_models/manga-format';
|
import { MangaFormat } from '../_models/manga-format';
|
||||||
@ -32,7 +32,7 @@ const CHAPTER_ID_NOT_FETCHED = -2;
|
|||||||
const CHAPTER_ID_DOESNT_EXIST = -1;
|
const CHAPTER_ID_DOESNT_EXIST = -1;
|
||||||
|
|
||||||
const ANIMATION_SPEED = 200;
|
const ANIMATION_SPEED = 200;
|
||||||
const OVERLAY_AUTO_CLOSE_TIME = 6000;
|
const OVERLAY_AUTO_CLOSE_TIME = 3000;
|
||||||
const CLICK_OVERLAY_TIMEOUT = 3000;
|
const CLICK_OVERLAY_TIMEOUT = 3000;
|
||||||
|
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
pageSplitOption = PageSplitOption.FitSplit;
|
pageSplitOption = PageSplitOption.FitSplit;
|
||||||
currentImageSplitPart: SPLIT_PAGE_PART = SPLIT_PAGE_PART.NO_SPLIT;
|
currentImageSplitPart: SPLIT_PAGE_PART = SPLIT_PAGE_PART.NO_SPLIT;
|
||||||
pagingDirection: PAGING_DIRECTION = PAGING_DIRECTION.FORWARD;
|
pagingDirection: PAGING_DIRECTION = PAGING_DIRECTION.FORWARD;
|
||||||
colorMode: COLOR_FILTER = COLOR_FILTER.NONE;
|
isFullscreen: boolean = false;
|
||||||
autoCloseMenu: boolean = true;
|
autoCloseMenu: boolean = true;
|
||||||
readerMode: READER_MODE = READER_MODE.MANGA_LR;
|
readerMode: READER_MODE = READER_MODE.MANGA_LR;
|
||||||
|
|
||||||
@ -107,6 +107,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
|
||||||
|
@ViewChild('reader') reader!: ElementRef;
|
||||||
@ViewChild('content') canvas: ElementRef | undefined;
|
@ViewChild('content') canvas: ElementRef | undefined;
|
||||||
private ctx!: CanvasRenderingContext2D;
|
private ctx!: CanvasRenderingContext2D;
|
||||||
private canvasImage = new Image();
|
private canvasImage = new Image();
|
||||||
@ -249,17 +250,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get colorOptionName() {
|
|
||||||
switch(this.colorMode) {
|
|
||||||
case COLOR_FILTER.NONE:
|
|
||||||
return 'None';
|
|
||||||
case COLOR_FILTER.DARK:
|
|
||||||
return 'Dark';
|
|
||||||
case COLOR_FILTER.SEPIA:
|
|
||||||
return 'Sepia';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get READER_MODE(): typeof READER_MODE {
|
get READER_MODE(): typeof READER_MODE {
|
||||||
return READER_MODE;
|
return READER_MODE;
|
||||||
}
|
}
|
||||||
@ -277,7 +267,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
private formBuilder: FormBuilder, private navService: NavService,
|
private formBuilder: FormBuilder, private navService: NavService,
|
||||||
private toastr: ToastrService, private memberService: MemberService,
|
private toastr: ToastrService, private memberService: MemberService,
|
||||||
private libraryService: LibraryService, private utilityService: UtilityService,
|
private libraryService: LibraryService, private utilityService: UtilityService,
|
||||||
private renderer: Renderer2) {
|
private renderer: Renderer2, @Inject(DOCUMENT) private document: Document) {
|
||||||
this.navService.hideNavBar();
|
this.navService.hideNavBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,6 +315,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
this.updateForm();
|
this.updateForm();
|
||||||
|
|
||||||
|
|
||||||
this.generalSettingsForm.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe((changes: SimpleChanges) => {
|
this.generalSettingsForm.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe((changes: SimpleChanges) => {
|
||||||
this.autoCloseMenu = this.generalSettingsForm.get('autoCloseMenu')?.value;
|
this.autoCloseMenu = this.generalSettingsForm.get('autoCloseMenu')?.value;
|
||||||
const needsSplitting = this.isCoverImage();
|
const needsSplitting = this.isCoverImage();
|
||||||
@ -365,6 +356,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.onDestroy.complete();
|
this.onDestroy.complete();
|
||||||
this.goToPageEvent.complete();
|
this.goToPageEvent.complete();
|
||||||
this.showBookmarkEffectEvent.complete();
|
this.showBookmarkEffectEvent.complete();
|
||||||
|
this.readerService.exitFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:keyup', ['$event'])
|
@HostListener('window:keyup', ['$event'])
|
||||||
@ -407,6 +399,17 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clickOverlayClass(side: 'right' | 'left') {
|
||||||
|
if (!this.showClickOverlay) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.readingDirection === ReadingDirection.LeftToRight) {
|
||||||
|
return side === 'right' ? 'highlight' : 'highlight-2';
|
||||||
|
}
|
||||||
|
return side === 'right' ? 'highlight-2' : 'highlight';
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.nextChapterId = CHAPTER_ID_NOT_FETCHED;
|
this.nextChapterId = CHAPTER_ID_NOT_FETCHED;
|
||||||
this.prevChapterId = CHAPTER_ID_NOT_FETCHED;
|
this.prevChapterId = CHAPTER_ID_NOT_FETCHED;
|
||||||
@ -631,6 +634,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns If the current model reflects no split of fit split
|
* @returns If the current model reflects no split of fit split
|
||||||
|
* @remarks Fit to Screen falls under no split
|
||||||
*/
|
*/
|
||||||
isNoSplit() {
|
isNoSplit() {
|
||||||
const splitValue = parseInt(this.generalSettingsForm?.get('pageSplitOption')?.value, 10);
|
const splitValue = parseInt(this.generalSettingsForm?.get('pageSplitOption')?.value, 10);
|
||||||
@ -832,9 +836,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
if (needsScaling) {
|
if (needsScaling) {
|
||||||
this.canvas.nativeElement.width = isSafari ? 4_096 : 16_384;
|
this.canvas.nativeElement.width = isSafari ? 4_096 : 16_384;
|
||||||
this.canvas.nativeElement.height = isSafari ? 4_096 : 16_384;
|
this.canvas.nativeElement.height = isSafari ? 4_096 : 16_384;
|
||||||
} else if (this.isCoverImage()) {
|
|
||||||
//this.canvas.nativeElement.width = this.canvasImage.width / 2;
|
|
||||||
//this.canvas.nativeElement.height = this.canvasImage.height;
|
|
||||||
} else {
|
} else {
|
||||||
this.canvas.nativeElement.width = this.canvasImage.width;
|
this.canvas.nativeElement.width = this.canvasImage.width;
|
||||||
this.canvas.nativeElement.height = this.canvasImage.height;
|
this.canvas.nativeElement.height = this.canvasImage.height;
|
||||||
@ -883,11 +884,16 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
// Optimization: When the screen is larger than newWidth, allow no split rendering to occur for a better fit
|
// Optimization: When the screen is larger than newWidth, allow no split rendering to occur for a better fit
|
||||||
if (windowWidth > newWidth) {
|
if (windowWidth > newWidth) {
|
||||||
|
//console.log('Using raw draw');
|
||||||
|
this.setCanvasSize();
|
||||||
this.ctx.drawImage(this.canvasImage, 0, 0);
|
this.ctx.drawImage(this.canvasImage, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
|
//console.log('Using scaled draw');
|
||||||
|
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
||||||
this.ctx.drawImage(this.canvasImage, 0, 0, newWidth, newHeight);
|
this.ctx.drawImage(this.canvasImage, 0, 0, newWidth, newHeight);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//console.log('Normal Render')
|
||||||
this.ctx.drawImage(this.canvasImage, 0, 0);
|
this.ctx.drawImage(this.canvasImage, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -984,16 +990,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clickOverlayClass(side: 'right' | 'left') {
|
|
||||||
if (!this.showClickOverlay) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.readingDirection === ReadingDirection.LeftToRight) {
|
|
||||||
return side === 'right' ? 'highlight' : 'highlight-2';
|
|
||||||
}
|
|
||||||
return side === 'right' ? 'highlight-2' : 'highlight';
|
|
||||||
}
|
|
||||||
|
|
||||||
sliderDragUpdate(context: ChangeContext) {
|
sliderDragUpdate(context: ChangeContext) {
|
||||||
// This will update the value for value except when in webtoon due to how the webtoon reader
|
// This will update the value for value except when in webtoon due to how the webtoon reader
|
||||||
@ -1079,20 +1075,24 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
return goToPageNum;
|
return goToPageNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleColorMode() {
|
toggleFullscreen() {
|
||||||
switch(this.colorMode) {
|
this.isFullscreen = this.readerService.checkFullscreenMode();
|
||||||
case COLOR_FILTER.NONE:
|
if (this.isFullscreen) {
|
||||||
this.colorMode = COLOR_FILTER.DARK;
|
this.readerService.exitFullscreen(() => {
|
||||||
break;
|
this.isFullscreen = false;
|
||||||
case COLOR_FILTER.DARK:
|
this.firstPageRendered = false;
|
||||||
this.colorMode = COLOR_FILTER.SEPIA;
|
this.render();
|
||||||
break;
|
});
|
||||||
case COLOR_FILTER.SEPIA:
|
} else {
|
||||||
this.colorMode = COLOR_FILTER.NONE;
|
this.readerService.enterFullscreen(this.reader.nativeElement, () => {
|
||||||
break;
|
this.isFullscreen = true;
|
||||||
|
this.firstPageRendered = false;
|
||||||
|
this.render();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
toggleReaderMode() {
|
toggleReaderMode() {
|
||||||
switch(this.readerMode) {
|
switch(this.readerMode) {
|
||||||
case READER_MODE.MANGA_LR:
|
case READER_MODE.MANGA_LR:
|
||||||
@ -1163,4 +1163,14 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.toastr.info('Incognito mode is off. Progress will now start being tracked.');
|
this.toastr.info('Incognito mode is off. Progress will now start being tracked.');
|
||||||
this.readerService.saveProgress(this.seriesId, this.volumeId, this.chapterId, this.pageNum).pipe(take(1)).subscribe(() => {/* No operation */});
|
this.readerService.saveProgress(this.seriesId, this.volumeId, this.chapterId, this.pageNum).pipe(take(1)).subscribe(() => {/* No operation */});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWindowDimensions() {
|
||||||
|
const windowWidth = window.innerWidth
|
||||||
|
|| document.documentElement.clientWidth
|
||||||
|
|| document.body.clientWidth;
|
||||||
|
const windowHeight = window.innerHeight
|
||||||
|
|| document.documentElement.clientHeight
|
||||||
|
|| document.body.clientHeight;
|
||||||
|
return [windowWidth, windowHeight];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<h2>User Dashboard</h2>
|
<h2>User Dashboard</h2>
|
||||||
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs nav-pills">
|
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs nav-pills">
|
||||||
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
|
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
|
||||||
<a ngbNavLink routerLink="." [fragment]="tab.fragment">{{ tab.title | titlecase }}</a>
|
<a ngbNavLink routerLink="." [fragment]="tab.fragment">{{ tab.title | sentenceCase }}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<ng-container *ngIf="tab.fragment === ''">
|
<ng-container *ngIf="tab.fragment === ''">
|
||||||
<p>
|
<p>
|
||||||
@ -48,7 +48,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ngbPanelContent>
|
<ng-template ngbPanelContent>
|
||||||
<form [formGroup]="settingsForm" *ngIf="user !== undefined">
|
<form [formGroup]="settingsForm" *ngIf="user !== undefined">
|
||||||
<h3 id="manga-header">Manga</h3>
|
<h3 id="manga-header">Image Reader</h3>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="settings-reading-direction">Reading Direction</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="readingDirectionTooltip" role="button" tabindex="0"></i>
|
<label for="settings-reading-direction">Reading Direction</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="readingDirectionTooltip" role="button" tabindex="0"></i>
|
||||||
<ng-template #readingDirectionTooltip>Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page.</ng-template>
|
<ng-template #readingDirectionTooltip>Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page.</ng-template>
|
||||||
@ -95,7 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<h3>Books</h3>
|
<h3>Book Reader</h3>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label id="dark-mode-label">Dark Mode</label>
|
<label id="dark-mode-label">Dark Mode</label>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -166,15 +166,28 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-panel>
|
</ngb-panel>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
<ngb-panel id="password-panel" title="Password">
|
<ngb-panel id="api-panel" title="3rd Party Clients">
|
||||||
<ng-template ngbPanelHeader>
|
<ng-template ngbPanelHeader>
|
||||||
<div class="d-flex align-items-center justify-content-between">
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
<button ngbPanelToggle class="btn container-fluid text-left pl-0 accordion-header">Password</button>
|
<button ngbPanelToggle class="btn container-fluid text-left pl-0 accordion-header">3rd Party Clients</button>
|
||||||
<span class="pull-right"><i class="fa fa-angle-{{acc.isExpanded('password-panel') ? 'down' : 'up'}}" aria-hidden="true"></i></span>
|
<span class="pull-right"><i class="fa fa-angle-{{acc.isExpanded('api-panel') ? 'down' : 'up'}}" aria-hidden="true"></i></span>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ngbPanelContent>
|
<ng-template ngbPanelContent>
|
||||||
|
<p>All 3rd Party clients will either use the API key or the Connection Url below. These are like passwords, keep it private.</p>
|
||||||
|
<p class="alert alert-warning" role="alert" *ngIf="!opdsEnabled">OPDS is not enabled on this server.</p>
|
||||||
|
<app-api-key tooltipText="The API key is like a password. Keep it secret, Keep it safe."></app-api-key>
|
||||||
|
<app-api-key title="OPDS URL" [showRefresh]="false" [transform]="makeUrl"></app-api-key>
|
||||||
|
</ng-template>
|
||||||
|
</ngb-panel> -->
|
||||||
|
</ngb-accordion>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="tab.fragment === 'bookmarks'">
|
||||||
|
<app-series-bookmarks></app-series-bookmarks>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="tab.fragment === 'password'">
|
||||||
<ng-container *ngIf="isAuthenticationEnabled || isAdmin; else authDisabled">
|
<ng-container *ngIf="isAuthenticationEnabled || isAdmin; else authDisabled">
|
||||||
<p>Change your Password</p>
|
<p>Change your Password</p>
|
||||||
<div class="alert alert-danger" role="alert" *ngIf="resetPasswordErrors.length > 0">
|
<div class="alert alert-danger" role="alert" *ngIf="resetPasswordErrors.length > 0">
|
||||||
@ -211,26 +224,12 @@
|
|||||||
<ng-template #authDisabled>
|
<ng-template #authDisabled>
|
||||||
<p class="text-warning">Authentication is disabled on this server. A password is not required to authenticate.</p>
|
<p class="text-warning">Authentication is disabled on this server. A password is not required to authenticate.</p>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-container>
|
||||||
</ngb-panel>
|
<ng-container *ngIf="tab.fragment === 'clients'">
|
||||||
<ngb-panel id="api-panel" title="3rd Party Clients">
|
|
||||||
<ng-template ngbPanelHeader>
|
|
||||||
<div class="d-flex align-items-center justify-content-between">
|
|
||||||
<button ngbPanelToggle class="btn container-fluid text-left pl-0 accordion-header">3rd Party Clients</button>
|
|
||||||
<span class="pull-right"><i class="fa fa-angle-{{acc.isExpanded('api-panel') ? 'down' : 'up'}}" aria-hidden="true"></i></span>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template ngbPanelContent>
|
|
||||||
<p>All 3rd Party clients will either use the API key or the Connection Url below. These are like passwords, keep it private.</p>
|
<p>All 3rd Party clients will either use the API key or the Connection Url below. These are like passwords, keep it private.</p>
|
||||||
<p class="alert alert-warning" role="alert" *ngIf="!opdsEnabled">OPDS is not enabled on this server.</p>
|
<p class="alert alert-warning" role="alert" *ngIf="!opdsEnabled">OPDS is not enabled on this server.</p>
|
||||||
<app-api-key tooltipText="The API key is like a password. Keep it secret, Keep it safe."></app-api-key>
|
<app-api-key tooltipText="The API key is like a password. Keep it secret, Keep it safe."></app-api-key>
|
||||||
<app-api-key title="OPDS URL" [showRefresh]="false" [transform]="makeUrl"></app-api-key>
|
<app-api-key title="OPDS URL" [showRefresh]="false" [transform]="makeUrl"></app-api-key>
|
||||||
</ng-template>
|
|
||||||
</ngb-panel>
|
|
||||||
</ngb-accordion>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="tab.fragment === 'bookmarks'">
|
|
||||||
<app-series-bookmarks></app-series-bookmarks>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
@ -55,6 +55,8 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||||||
tabs: Array<{title: string, fragment: string}> = [
|
tabs: Array<{title: string, fragment: string}> = [
|
||||||
{title: 'Preferences', fragment: ''},
|
{title: 'Preferences', fragment: ''},
|
||||||
{title: 'Bookmarks', fragment: 'bookmarks'},
|
{title: 'Bookmarks', fragment: 'bookmarks'},
|
||||||
|
{title: 'Password', fragment: 'password'},
|
||||||
|
{title: '3rd Party Clients', fragment: 'clients'},
|
||||||
];
|
];
|
||||||
active = this.tabs[0];
|
active = this.tabs[0];
|
||||||
opdsEnabled: boolean = false;
|
opdsEnabled: boolean = false;
|
||||||
|
@ -7,6 +7,7 @@ import { ReactiveFormsModule } from '@angular/forms';
|
|||||||
import { NgxSliderModule } from '@angular-slider/ngx-slider';
|
import { NgxSliderModule } from '@angular-slider/ngx-slider';
|
||||||
import { UserSettingsRoutingModule } from './user-settings-routing.module';
|
import { UserSettingsRoutingModule } from './user-settings-routing.module';
|
||||||
import { ApiKeyComponent } from './api-key/api-key.component';
|
import { ApiKeyComponent } from './api-key/api-key.component';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ import { ApiKeyComponent } from './api-key/api-key.component';
|
|||||||
NgbTooltipModule,
|
NgbTooltipModule,
|
||||||
NgxSliderModule,
|
NgxSliderModule,
|
||||||
UserSettingsRoutingModule,
|
UserSettingsRoutingModule,
|
||||||
|
SharedModule // SentenceCase pipe
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class UserSettingsModule { }
|
export class UserSettingsModule { }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user