First PR of the new year (#1717)
* Fixed a bug on bookmark mode not finding correct image for prefetcher. * Fixed up the edit series relationship modal on tablet viewports. * On double page mode, only bookmark 1 page if only 1 pages is renderered on screen. * Added percentage read of a given library and average hours read per week to user stats. * Fixed a bug in the reader with paging in bookmark mode * Added a "This Week" option to top readers history * Added date ranges for reading time. Added dates that don't have anything, but might remove. * On phone, when applying a metadata filter, when clicking apply, collapse the filter automatically. * Disable jump bar and the resuming from last spot when a custom sort is applied. * Ensure all Regex.Replace or Matches have timeouts set * Fixed a long standing bug where fit to height on tablets wouldn't center the image * Streamlined url parsing to be more reliable * Reduced an additional db query in chapter info. * Added a missing task to convert covers to webP and added messaging to help the user understand to run it after modifying the setting. * Changed OPDS to be enabled by default for new installs. This should reduce issues with users being confused about it before it's enabled. * When there are multiple files for a chapter, show a count card on the series detail to help user understand duplicates exist. Made the unread badge smaller to avoid collision. * Added Word Count to user stats and wired up average reading per week. * Fixed word count failing on some epubs * Removed some debug code * Don't give more information than is necessary about file paths for page dimensions. * Fixed a bug where pagination area would be too small when the book's content was less that height on default mode. * Updated Default layout mode to Scroll for books. * Added bytes in the UI and at an API layer for CDisplayEx * Don't log health checks to logs at all. * Changed Word Count to Length to match the way pages work * Made reading time more clear when min hours is 0 * Apply more aggressive coalescing when remapping bad metadata keys for epubs. * Changed the amount of padding between icon and text for side nav item. * Fixed a NPE on book reader (harmless) * Fixed an ordering issue where Volume 1 was a single file but also tagged as Chapter 1 and Volume 2 was Chapter 0. Thus Volume 2 was being selected for continue point when Volume 1 should have been. * When clicking on an activity stream header from dashboard, show the title on the resulting page. * Removed a property that can't be animated * Fixed a typeahead typescript issue * Added Size into Series Info and Added some tooltip and spacing changes to better explain some fields. * Added size for volume drawers and cleaned up some date edge case handling * Fixed an annoying bug where when on mobile opening a view with a metadata filter, Kavita would open the filter automatically.
This commit is contained in:
parent
8eb5b466ef
commit
a545f96a05
52 changed files with 410 additions and 187 deletions
|
@ -14,7 +14,7 @@ img {
|
|||
|
||||
&.full-height {
|
||||
height: 100vh;
|
||||
display: inline-block;
|
||||
display: flex; // changed from inline-block to fix the centering on tablets not working
|
||||
}
|
||||
|
||||
&.original {
|
||||
|
|
|
@ -6,4 +6,5 @@ export interface MangaFile {
|
|||
pages: number;
|
||||
format: MangaFormat;
|
||||
created: string;
|
||||
bytes: number;
|
||||
}
|
||||
|
|
|
@ -44,5 +44,5 @@ export const scalingOptions = [{text: 'Automatic', value: ScalingOption.Automati
|
|||
export const pageSplitOptions = [{text: 'Fit to Screen', value: PageSplitOption.FitSplit}, {text: 'Right to Left', value: PageSplitOption.SplitRightToLeft}, {text: 'Left to Right', value: PageSplitOption.SplitLeftToRight}, {text: 'No Split', value: PageSplitOption.NoSplit}];
|
||||
export const readingModes = [{text: 'Left to Right', value: ReaderMode.LeftRight}, {text: 'Up to Down', value: ReaderMode.UpDown}, {text: 'Webtoon', value: ReaderMode.Webtoon}];
|
||||
export const layoutModes = [{text: 'Single', value: LayoutMode.Single}, {text: 'Double', value: LayoutMode.Double}, {text: 'Double (Manga)', value: LayoutMode.DoubleReversed}];
|
||||
export const bookLayoutModes = [{text: 'Default', value: BookPageLayoutMode.Default}, {text: '1 Column', value: BookPageLayoutMode.Column1}, {text: '2 Column', value: BookPageLayoutMode.Column2}];
|
||||
export const bookLayoutModes = [{text: 'Scroll', value: BookPageLayoutMode.Default}, {text: '1 Column', value: BookPageLayoutMode.Column1}, {text: '2 Column', value: BookPageLayoutMode.Column2}];
|
||||
export const pageLayoutModes = [{text: 'Cards', value: PageLayoutMode.Cards}, {text: 'List', value: PageLayoutMode.List}];
|
||||
|
|
|
@ -191,12 +191,14 @@ export class ReaderService {
|
|||
*/
|
||||
imageUrlToPageNum(imageSrc: string) {
|
||||
if (imageSrc === undefined || imageSrc === '') { return -1; }
|
||||
return parseInt(imageSrc.split('&page=')[1], 10);
|
||||
const params = new URLSearchParams(new URL(imageSrc).search);
|
||||
return parseInt(params.get('page') || '-1', 10);
|
||||
}
|
||||
|
||||
imageUrlToChapterId(imageSrc: string) {
|
||||
if (imageSrc === undefined || imageSrc === '') { return -1; }
|
||||
return parseInt(imageSrc.split('chapterId=')[1].split('&')[0], 10);
|
||||
const params = new URLSearchParams(new URL(imageSrc).search);
|
||||
return parseInt(params.get('chapterId') || '-1', 10);
|
||||
}
|
||||
|
||||
getNextChapterUrl(url: string, nextChapterId: number, incognitoMode: boolean = false, readingListMode: boolean = false, readingListId: number = -1) {
|
||||
|
|
|
@ -57,4 +57,8 @@ export class ServerService {
|
|||
convertBookmarks() {
|
||||
return this.httpClient.post(this.baseUrl + 'server/convert-bookmarks', {});
|
||||
}
|
||||
|
||||
convertCovers() {
|
||||
return this.httpClient.post(this.baseUrl + 'server/convert-covers', {});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
<div class="row g-0">
|
||||
<p>WebP can drastically reduce space requirements for files. WebP is not supported on all browsers or versions. To learn if these settings are appropriate for your setup, visit <a href="https://caniuse.com/?search=webp" target="_blank" rel="noopener noreferrer">Can I Use</a>.</p>
|
||||
<div *ngIf="settingsForm.dirty" class="alert alert-danger" role="alert">You must trigger the conversion to WebP task in Tasks Tab.</div>
|
||||
<div class="col-md-6 col-sm-12 mb-3">
|
||||
<label for="bookmark-webp" class="form-label me-1" aria-describedby="settings-convertBookmarkToWebP-help">Save Bookmarks as WebP</label>
|
||||
<i class="fa fa-info-circle" placement="right" [ngbTooltip]="convertBookmarkToWebPTooltip" role="button" tabindex="0"></i>
|
||||
|
|
|
@ -39,6 +39,12 @@ export class ManageTasksSettingsComponent implements OnInit {
|
|||
api: this.serverService.convertBookmarks(),
|
||||
successMessage: 'Conversion of Bookmarks has been queued'
|
||||
},
|
||||
{
|
||||
name: 'Convert Covers to WebP',
|
||||
description: 'Runs a long-running task which will convert all existing covers to WebP. This is slow (especially on ARM devices).',
|
||||
api: this.serverService.convertCovers(),
|
||||
successMessage: 'Conversion of Coverts has been queued'
|
||||
},
|
||||
{
|
||||
name: 'Clear Cache',
|
||||
description: 'Clears cached files for reading. Usefull when you\'ve just updated a file that you were previously reading within last 24 hours.',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<app-side-nav-companion-bar [hasFilter]="true" [filterOpenByDefault]="filterSettings.openByDefault" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
|
||||
<h2 title>
|
||||
All Series
|
||||
{{title}}
|
||||
</h2>
|
||||
<h6 subtitle *ngIf="pagination">{{pagination.totalItems}} Series</h6>
|
||||
</app-side-nav-companion-bar>
|
||||
|
|
|
@ -27,6 +27,7 @@ import { SeriesService } from 'src/app/_services/series.service';
|
|||
})
|
||||
export class AllSeriesComponent implements OnInit, OnDestroy {
|
||||
|
||||
title: string = 'All Series';
|
||||
series: Series[] = [];
|
||||
loadingSeries = false;
|
||||
pagination!: Pagination;
|
||||
|
@ -93,7 +94,9 @@ export class AllSeriesComponent implements OnInit, OnDestroy {
|
|||
private readonly cdRef: ChangeDetectorRef) {
|
||||
|
||||
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
this.titleService.setTitle('Kavita - All Series');
|
||||
|
||||
this.title = this.route.snapshot.queryParamMap.get('title') || 'All Series';
|
||||
this.titleService.setTitle('Kavita - ' + this.title);
|
||||
|
||||
this.pagination = this.filterUtilityService.pagination(this.route.snapshot);
|
||||
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot);
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
tabindex="-1" [ngStyle]="{height: PageHeightForPagination}"></div>
|
||||
</ng-container>
|
||||
|
||||
<div class="book-container" [ngClass]="{'immersive' : immersiveMode}">
|
||||
<div #bookContainer class="book-container" [ngClass]="{'immersive' : immersiveMode}">
|
||||
|
||||
<div #readingHtml class="book-content {{ColumnLayout}}" [ngStyle]="{'max-height': ColumnHeight, 'column-width': ColumnWidth}"
|
||||
[ngClass]="{'immersive': immersiveMode && actionBarVisible}"
|
||||
|
|
|
@ -252,7 +252,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
@ViewChild('readingHtml', {static: false}) readingHtml!: ElementRef<HTMLDivElement>;
|
||||
@ViewChild('bookContainer', {static: false}) bookContainerElemRef!: ElementRef<HTMLDivElement>;
|
||||
/**
|
||||
* book-content class
|
||||
*/
|
||||
@ViewChild('bookContentElemRef', {static: false}) bookContentElemRef!: ElementRef<HTMLDivElement>;
|
||||
@ViewChild('readingSection', {static: false}) readingSectionElemRef!: ElementRef<HTMLDivElement>;
|
||||
@ViewChild('stickyTop', {static: false}) stickyTopElemRef!: ElementRef<HTMLDivElement>;
|
||||
@ViewChild('reader', {static: true}) reader!: ElementRef;
|
||||
|
@ -326,7 +330,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
const [currentVirtualPage, totalVirtualPages, _] = this.getVirtualPage();
|
||||
if (this.readingHtml == null) return this.pageNum + 1 >= this.maxPages;
|
||||
if (this.bookContentElemRef == null) return this.pageNum + 1 >= this.maxPages;
|
||||
|
||||
return this.pageNum + 1 >= this.maxPages && (currentVirtualPage === totalVirtualPages);
|
||||
}
|
||||
|
@ -339,7 +343,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
const [currentVirtualPage,,] = this.getVirtualPage();
|
||||
if (this.readingHtml == null) return this.pageNum + 1 >= this.maxPages;
|
||||
if (this.bookContentElemRef == null) return this.pageNum + 1 >= this.maxPages;
|
||||
|
||||
return this.pageNum === 0 && (currentVirtualPage === 0);
|
||||
}
|
||||
|
@ -378,7 +382,13 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
get PageHeightForPagination() {
|
||||
if (this.layoutMode === BookPageLayoutMode.Default) {
|
||||
return (this.readingHtml?.nativeElement?.scrollHeight || 0) - ((this.topOffset * (this.immersiveMode ? 0 : 1)) * 2) + 'px';
|
||||
|
||||
// if the book content is less than the height of the container, override and return height of container for pagination area
|
||||
if (this.bookContainerElemRef?.nativeElement?.clientHeight > this.bookContentElemRef?.nativeElement?.clientHeight) {
|
||||
return (this.bookContainerElemRef?.nativeElement?.clientHeight || 0) + 'px';
|
||||
}
|
||||
|
||||
return (this.bookContentElemRef?.nativeElement?.scrollHeight || 0) - ((this.topOffset * (this.immersiveMode ? 0 : 1)) * 2) + 'px';
|
||||
}
|
||||
|
||||
if (this.immersiveMode) return this.windowHeight + 'px';
|
||||
|
@ -848,9 +858,9 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.reader.nativeElement.children
|
||||
// We need to check if we are paging back, because we need to adjust the scroll
|
||||
if (this.pagingDirection === PAGING_DIRECTION.BACKWARDS) {
|
||||
setTimeout(() => this.scrollService.scrollToX(this.readingHtml.nativeElement.scrollWidth, this.readingHtml.nativeElement));
|
||||
setTimeout(() => this.scrollService.scrollToX(this.bookContentElemRef.nativeElement.scrollWidth, this.bookContentElemRef.nativeElement));
|
||||
} else {
|
||||
setTimeout(() => this.scrollService.scrollToX(0, this.readingHtml.nativeElement));
|
||||
setTimeout(() => this.scrollService.scrollToX(0, this.bookContentElemRef.nativeElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -925,7 +935,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
if (currentVirtualPage > 1) {
|
||||
// -2 apparently goes back 1 virtual page...
|
||||
this.scrollService.scrollToX((currentVirtualPage - 2) * pageWidth, this.readingHtml.nativeElement);
|
||||
this.scrollService.scrollToX((currentVirtualPage - 2) * pageWidth, this.bookContentElemRef.nativeElement);
|
||||
this.handleScrollEvent();
|
||||
return;
|
||||
}
|
||||
|
@ -957,7 +967,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
if (currentVirtualPage < totalVirtualPages) {
|
||||
// +0 apparently goes forward 1 virtual page...
|
||||
this.scrollService.scrollToX((currentVirtualPage) * pageWidth, this.readingHtml.nativeElement);
|
||||
this.scrollService.scrollToX((currentVirtualPage) * pageWidth, this.bookContentElemRef.nativeElement);
|
||||
this.handleScrollEvent();
|
||||
return;
|
||||
}
|
||||
|
@ -995,10 +1005,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
* @returns
|
||||
*/
|
||||
getVirtualPage() {
|
||||
if (this.readingHtml === undefined || this.readingSectionElemRef === undefined) return [1, 1, 0];
|
||||
if (this.bookContentElemRef === undefined || this.readingSectionElemRef === undefined) return [1, 1, 0];
|
||||
|
||||
const scrollOffset = this.readingHtml.nativeElement.scrollLeft;
|
||||
const totalScroll = this.readingHtml.nativeElement.scrollWidth;
|
||||
const scrollOffset = this.bookContentElemRef.nativeElement.scrollLeft;
|
||||
const totalScroll = this.bookContentElemRef.nativeElement.scrollWidth;
|
||||
const pageWidth = this.getPageWidth();
|
||||
const delta = totalScroll - scrollOffset;
|
||||
|
||||
|
@ -1022,9 +1032,9 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
getFirstVisibleElementXPath() {
|
||||
let resumeElement: string | null = null;
|
||||
if (this.readingHtml === null) return null;
|
||||
if (this.bookContentElemRef === null) return null;
|
||||
|
||||
const intersectingEntries = Array.from(this.readingHtml.nativeElement.querySelectorAll('div,o,p,ul,li,a,img,h1,h2,h3,h4,h5,h6,span'))
|
||||
const intersectingEntries = Array.from(this.bookContentElemRef.nativeElement.querySelectorAll('div,o,p,ul,li,a,img,h1,h2,h3,h4,h5,h6,span'))
|
||||
.filter(element => !element.classList.contains('no-observe'))
|
||||
.filter(entry => {
|
||||
return this.utilityService.isInViewport(entry, this.topOffset);
|
||||
|
@ -1048,7 +1058,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
*/
|
||||
updateReaderStyles(pageStyles: PageStyle) {
|
||||
this.pageStyles = pageStyles;
|
||||
if (this.readingHtml === undefined || !this.readingHtml.nativeElement) return;
|
||||
if (this.bookContentElemRef === undefined || !this.bookContentElemRef.nativeElement) return;
|
||||
|
||||
// Before we apply styles, let's get an element on the screen so we can scroll to it after any shifts
|
||||
const resumeElement: string | null | undefined = this.getFirstVisibleElementXPath();
|
||||
|
@ -1060,17 +1070,17 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
Object.entries(this.pageStyles).forEach(item => {
|
||||
if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') {
|
||||
// Remove the style or skip
|
||||
this.renderer.removeStyle(this.readingHtml.nativeElement, item[0]);
|
||||
this.renderer.removeStyle(this.bookContentElemRef.nativeElement, item[0]);
|
||||
return;
|
||||
}
|
||||
if (pageLevelStyles.includes(item[0])) {
|
||||
this.renderer.setStyle(this.readingHtml.nativeElement, item[0], item[1], RendererStyleFlags2.Important);
|
||||
this.renderer.setStyle(this.bookContentElemRef.nativeElement, item[0], item[1], RendererStyleFlags2.Important);
|
||||
}
|
||||
});
|
||||
|
||||
const individualElementStyles = Object.entries(this.pageStyles).filter(item => elementLevelStyles.includes(item[0]));
|
||||
for(let i = 0; i < this.readingHtml.nativeElement.children.length; i++) {
|
||||
const elem = this.readingHtml.nativeElement.children.item(i);
|
||||
for(let i = 0; i < this.bookContentElemRef.nativeElement.children.length; i++) {
|
||||
const elem = this.bookContentElemRef.nativeElement.children.item(i);
|
||||
if (elem?.tagName === 'STYLE') continue;
|
||||
individualElementStyles.forEach(item => {
|
||||
if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') {
|
||||
|
@ -1114,7 +1124,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.windowWidth = Math.max(this.readingSectionElemRef.nativeElement.clientWidth, window.innerWidth);
|
||||
|
||||
// Recalculate if bottom action bar is needed
|
||||
this.scrollbarNeeded = this.readingHtml.nativeElement.clientHeight > this.reader.nativeElement.clientHeight;
|
||||
this.scrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientHeight > this.reader?.nativeElement?.clientHeight;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
|
@ -1221,12 +1231,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.updateImagesWithHeight();
|
||||
|
||||
// Calulate if bottom actionbar is needed. On a timeout to get accurate heights
|
||||
if (this.readingHtml == null) {
|
||||
if (this.bookContentElemRef == null) {
|
||||
setTimeout(() => this.updateLayoutMode(this.layoutMode), 10);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.scrollbarNeeded = this.readingHtml.nativeElement.clientHeight > this.reader.nativeElement.clientHeight;
|
||||
this.scrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientHeight > this.reader?.nativeElement?.clientHeight;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
|
@ -1252,11 +1262,14 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
updateReadingSectionHeight() {
|
||||
const renderer = this.renderer;
|
||||
const elem = this.readingSectionElemRef;
|
||||
setTimeout(() => {
|
||||
if (renderer === undefined || elem === undefined) return;
|
||||
if (this.immersiveMode) {
|
||||
this.renderer?.setStyle(this.readingSectionElemRef, 'height', 'calc(var(--vh, 1vh) * 100)', RendererStyleFlags2.Important);
|
||||
renderer.setStyle(elem, 'height', 'calc(var(--vh, 1vh) * 100)', RendererStyleFlags2.Important);
|
||||
} else {
|
||||
this.renderer?.setStyle(this.readingSectionElemRef, 'height', 'calc(var(--vh, 1vh) * 100 - ' + this.topOffset + 'px)', RendererStyleFlags2.Important);
|
||||
renderer.setStyle(elem, 'height', 'calc(var(--vh, 1vh) * 100 - ' + this.topOffset + 'px)', RendererStyleFlags2.Important);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -108,14 +108,14 @@
|
|||
|
||||
<div class="controls">
|
||||
<label id="layout-mode" class="form-label" style="margin-bottom:0.5rem">Layout Mode <i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="layoutTooltip" role="button" tabindex="1" aria-describedby="layout-help"></i></label>
|
||||
<ng-template #layoutTooltip>Default: Mirrors epub file (usually one long scrolling page per chapter).<br/>1 Column: Creates a single virtual page at a time.<br/>2 Column: Creates two virtual pages at a time laid out side-by-side.</ng-template>
|
||||
<ng-template #layoutTooltip>Scroll: Mirrors epub file (usually one long scrolling page per chapter).<br/>1 Column: Creates a single virtual page at a time.<br/>2 Column: Creates two virtual pages at a time laid out side-by-side.</ng-template>
|
||||
<span class="visually-hidden" id="layout-help">
|
||||
<ng-container [ngTemplateOutlet]="layoutTooltip"></ng-container>
|
||||
</span>
|
||||
<br>
|
||||
<div class="btn-group d-flex justify-content-center" role="group" aria-label="Layout Mode">
|
||||
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Default" class="btn-check" id="layout-mode-default" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-default">Default</label>
|
||||
<label class="btn btn-outline-primary" for="layout-mode-default">Scroll</label>
|
||||
|
||||
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Column1" class="btn-check" id="layout-mode-col1" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-col1">1 Column</label>
|
||||
|
|
|
@ -369,18 +369,24 @@
|
|||
</div>
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="col-md-6">Created: {{series.created | date:'shortDate'}}</div>
|
||||
<div class="col-md-6">Last Read: {{series.latestReadDate | date:'shortDate' | defaultDate}}</div>
|
||||
<div class="col-md-6">Last Added To: {{series.lastChapterAdded | date:'short' | defaultDate}}</div>
|
||||
<div class="col-md-6">Last Scanned: {{series.lastFolderScanned | date:'short' | defaultDate}}</div>
|
||||
<div class="col-md-6">Folder Path: {{series.folderPath | defaultValue}}</div>
|
||||
<div class="col-md-6">Last Read: {{series.latestReadDate | defaultDate | timeAgo}}</div>
|
||||
<div class="col-md-6">Last Added To: {{series.lastChapterAdded | defaultDate | timeAgo}}</div>
|
||||
<div class="col-md-6">Last Scanned: {{series.lastFolderScanned | defaultDate | timeAgo}}</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="col-auto">Folder Path: {{series.folderPath | defaultValue}}</div>
|
||||
</div>
|
||||
<div class="row g-0 mb-2" *ngIf="metadata">
|
||||
<!-- TODO: Put tooltips in here to explain to the user what these are (ComicInfo tags) -->
|
||||
<div class="col-md-6">Max Items: {{metadata.maxCount}}</div>
|
||||
<div class="col-md-6">Total Items: {{metadata.totalCount}}</div>
|
||||
<div class="col-md-6">
|
||||
Max Items: {{metadata.maxCount}} <i class="fa fa-info-circle ms-1" placement="right" ngbTooltip="Max of Volume/Issue field in ComicInfo. Used in conjunction with total items to determine publication status." role="button" tabindex="0"></i>
|
||||
</div>
|
||||
<div class="col-md-6" title="">
|
||||
Total Items: {{metadata.totalCount}} <i class="fa fa-info-circle ms-1" placement="right" ngbTooltip="Total number of issues/volumes in the series" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
<div class="col-md-6">Publication Status: {{metadata.publicationStatus | publicationStatus}}</div>
|
||||
<div class="col-md-6">Total Pages: {{series.pages}}</div>
|
||||
|
||||
<div class="col-md-6">Size: {{size | bytes}}</div>
|
||||
</div>
|
||||
<h4>Volumes</h4>
|
||||
<div class="spinner-border text-secondary" role="status" *ngIf="isLoadingVolumes">
|
||||
|
|
|
@ -54,7 +54,9 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
|
|||
activeTabId = TabID.General;
|
||||
editSeriesForm!: FormGroup;
|
||||
libraryName: string | undefined = undefined;
|
||||
size: number = 0;
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
|
||||
|
||||
// Typeaheads
|
||||
|
@ -122,7 +124,6 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
|
|||
|
||||
this.initSeries = Object.assign({}, this.series);
|
||||
|
||||
|
||||
this.editSeriesForm = this.fb.group({
|
||||
id: new FormControl(this.series.id, []),
|
||||
summary: new FormControl('', []),
|
||||
|
@ -232,6 +233,16 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
|
|||
return f;
|
||||
})).flat();
|
||||
});
|
||||
|
||||
if (volumes.length > 0) {
|
||||
this.size = volumes.reduce((sum1, volume) => {
|
||||
return sum1 + volume.chapters.reduce((sum2, chapter) => {
|
||||
return sum2 + chapter.files.reduce((sum3, file) => {
|
||||
return sum3 + file.bytes;
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -146,12 +146,14 @@
|
|||
Added:
|
||||
<!-- TODO: This data.created can be removed after v0.5.5 release -->
|
||||
<ng-container *ngIf="file.created == '0001-01-01T00:00:00'; else fileDate">
|
||||
{{(data.created | date: 'short') || '-'}}
|
||||
{{data.created | date: 'short' | defaultDate}}
|
||||
</ng-container>
|
||||
<ng-template #fileDate>
|
||||
{{(file.created | date: 'short') || '-'}}
|
||||
{{file.created | date: 'short' | defaultDate}}
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
<div class="col">
|
||||
Size: {{file.bytes | bytes}}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<ng-container *ngIf="chapter !== undefined && chapter.releaseDate && (chapter.releaseDate | date: 'shortDate') !== '1/1/01'">
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title label="Release Date" [clickable]="false" fontClasses="fa-regular fa-calendar" title="Release">
|
||||
{{chapter.releaseDate | date:'shortDate'}}
|
||||
{{chapter.releaseDate | date:'shortDate' | defaultDate}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
|
@ -28,7 +28,7 @@
|
|||
|
||||
<ng-container *ngIf="chapter.files[0].format === MangaFormat.EPUB && totalWordCount > 0">
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title label="Word Count" [clickable]="false" fontClasses="fa-solid fa-book-open">
|
||||
<app-icon-and-title label="Length" [clickable]="false" fontClasses="fa-solid fa-book-open">
|
||||
{{totalWordCount | compactNumber}} Words
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
|
@ -38,7 +38,7 @@
|
|||
<ng-container *ngIf="chapter.files[0].format === MangaFormat.EPUB && totalWordCount > 0 || chapter.files[0].format !== MangaFormat.EPUB">
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title label="Read Time" [clickable]="false" fontClasses="fa-regular fa-clock">
|
||||
<ng-container *ngIf="readingTime.maxHours === 0; else normalReadTime"><1 Hour</ng-container>
|
||||
<ng-container *ngIf="readingTime.maxHours === 0 || readingTime.minHours === 0; else normalReadTime"><1 Hour</ng-container>
|
||||
<ng-template #normalReadTime>
|
||||
{{readingTime.minHours}}{{readingTime.maxHours !== readingTime.minHours ? ('-' + readingTime.maxHours) : ''}} Hour{{readingTime.minHours > 1 ? 's' : ''}}
|
||||
</ng-template>
|
||||
|
@ -50,7 +50,16 @@
|
|||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title label="Date Added" [clickable]="false" fontClasses="fa-solid fa-file-import" title="Date Added">
|
||||
{{chapter.created | date:'short' || '-'}}
|
||||
{{chapter.created | date:'short' | defaultDate}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="showExtendedProperties && size > 0">
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title label="Size" [clickable]="false" fontClasses="fa-solid fa-scale-unbalanced" title="ID">
|
||||
{{size | bytes}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
@ -37,6 +37,7 @@ export class EntityInfoCardsComponent implements OnInit, OnDestroy {
|
|||
totalPages: number = 0;
|
||||
totalWordCount: number = 0;
|
||||
readingTime: HourEstimateRange = {maxHours: 1, minHours: 1, avgHours: 1};
|
||||
size: number = 0;
|
||||
|
||||
private readonly onDestroy: Subject<void> = new Subject();
|
||||
|
||||
|
@ -59,6 +60,17 @@ export class EntityInfoCardsComponent implements OnInit, OnDestroy {
|
|||
|
||||
this.chapter = this.utilityService.isChapter(this.entity) ? (this.entity as Chapter) : (this.entity as Volume).chapters[0];
|
||||
|
||||
|
||||
if (this.isChapter) {
|
||||
this.size = this.utilityService.asChapter(this.entity).files.reduce((sum, v) => sum + v.bytes, 0);
|
||||
} else {
|
||||
this.size = this.utilityService.asVolume(this.entity).chapters.reduce((sum1, chapter) => {
|
||||
return sum1 + chapter.files.reduce((sum2, file) => {
|
||||
return sum2 + file.bytes;
|
||||
}, 0);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (this.includeMetadata) {
|
||||
this.seriesService.getChapterMetadata(this.chapter.id).subscribe(metadata => {
|
||||
this.chapterMetadata = metadata;
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<ng-container *ngIf="series.format === MangaFormat.EPUB; else showPages">
|
||||
<ng-container *ngIf="series.wordCount > 0">
|
||||
<div class="col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
|
||||
<app-icon-and-title label="Word Count" [clickable]="false" fontClasses="fa-solid fa-book-open">
|
||||
<app-icon-and-title label="Length" [clickable]="false" fontClasses="fa-solid fa-book-open">
|
||||
{{series.wordCount | compactNumber}} Words
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
|
@ -81,7 +81,7 @@
|
|||
<ng-container *ngIf="series.format === MangaFormat.EPUB && series.wordCount > 0 || series.format !== MangaFormat.EPUB">
|
||||
<div class="col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
|
||||
<app-icon-and-title label="Read Time" [clickable]="false" fontClasses="fa-regular fa-clock">
|
||||
<ng-container *ngIf="readingTime.maxHours === 0; else normalReadTime"><1 Hour</ng-container>
|
||||
<ng-container *ngIf="readingTime.maxHours === 0 || readingTime.minHours === 0; else normalReadTime"><1 Hour</ng-container>
|
||||
<ng-template #normalReadTime>
|
||||
{{readingTime.minHours}}{{readingTime.maxHours !== readingTime.minHours ? ('-' + readingTime.maxHours) : ''}} Hour{{readingTime.minHours > 1 ? 's' : ''}}
|
||||
</ng-template>
|
||||
|
|
|
@ -168,17 +168,20 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||
const params: any = {};
|
||||
params[FilterQueryParam.SortBy] = SortField.LastChapterAdded + ',false'; // sort by last chapter added, desc
|
||||
params[FilterQueryParam.Page] = 1;
|
||||
params['title'] = 'Recently Updated';
|
||||
this.router.navigate(['all-series'], {queryParams: params});
|
||||
} else if (sectionTitle.toLowerCase() === 'on deck') {
|
||||
const params: any = {};
|
||||
params[FilterQueryParam.ReadStatus] = 'true,false,false';
|
||||
params[FilterQueryParam.SortBy] = SortField.LastChapterAdded + ',false'; // sort by last chapter added, desc
|
||||
params[FilterQueryParam.Page] = 1;
|
||||
params['title'] = 'On Deck';
|
||||
this.router.navigate(['all-series'], {queryParams: params});
|
||||
}else if (sectionTitle.toLowerCase() === 'newly added series') {
|
||||
const params: any = {};
|
||||
params[FilterQueryParam.SortBy] = SortField.Created + ',false'; // sort by created, desc
|
||||
params[FilterQueryParam.Page] = 1;
|
||||
params['title'] = 'Newly Added';
|
||||
this.router.navigate(['all-series'], {queryParams: params});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1053,10 +1053,14 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
// NOTE: I may want to provide a different prefetcher for double renderer
|
||||
for(let i = 0; i <= PREFETCH_PAGES - 3; i++) {
|
||||
const numOffset = this.pageNum + i;
|
||||
//console.log('numOffset: ', numOffset);
|
||||
if (numOffset > this.maxPages - 1) continue;
|
||||
|
||||
const index = (numOffset % this.cachedImages.length + this.cachedImages.length) % this.cachedImages.length;
|
||||
if (this.readerService.imageUrlToPageNum(this.cachedImages[index].src) !== numOffset) {
|
||||
const cachedImagePageNum = this.readerService.imageUrlToPageNum(this.cachedImages[index].src);
|
||||
const cachedImageChapterId = this.readerService.imageUrlToChapterId(this.cachedImages[index].src);
|
||||
//console.log('chapter id for ', cachedImagePageNum, ' = ', cachedImageChapterId)
|
||||
if (cachedImagePageNum !== numOffset) { // && cachedImageChapterId === this.chapterId
|
||||
this.cachedImages[index] = new Image();
|
||||
this.cachedImages[index].src = this.getPageUrl(numOffset);
|
||||
}
|
||||
|
|
|
@ -629,12 +629,12 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||
|
||||
apply() {
|
||||
this.applyFilter.emit({filter: this.filter, isFirst: this.updateApplied === 0});
|
||||
this.updateApplied++;
|
||||
|
||||
if (this.utilityService.getActiveBreakpoint() === Breakpoint.Mobile) {
|
||||
|
||||
if (this.utilityService.getActiveBreakpoint() === Breakpoint.Mobile && this.updateApplied !== 0) {
|
||||
this.toggleSelected();
|
||||
}
|
||||
|
||||
|
||||
this.updateApplied++;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||
export class DefaultDatePipe implements PipeTransform {
|
||||
|
||||
transform(value: any, replacementString = 'Never'): string {
|
||||
if (value === null || value === undefined || value === '' || value === Infinity || value === NaN || value === '1/1/01') return replacementString;
|
||||
if (value === null || value === undefined || value === '' || value === Infinity || Number.isNaN(value) || value === '1/1/01') return replacementString;
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
|
@ -148,6 +148,7 @@
|
|||
<app-card-item class="col-auto mt-2 mb-2" *ngIf="!item.chapter.isSpecial" [entity]="item.chapter" [title]="item.chapter.title" (click)="openChapter(item.chapter)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(item.chapter.id)"
|
||||
[read]="item.chapter.pagesRead" [total]="item.chapter.pages" [actions]="chapterActions"
|
||||
[count]="item.chapter.files.length"
|
||||
(selection)="bulkSelectionService.handleCardSelection('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx, storyChapters.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true"></app-card-item>
|
||||
</ng-template>
|
||||
|
@ -227,6 +228,7 @@
|
|||
<app-card-item class="col-auto mt-2 mb-2" *ngIf="!item.isSpecial" [entity]="item" [title]="item.title" (click)="openChapter(item)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(item.id)"
|
||||
[read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions"
|
||||
[count]="item.files.length"
|
||||
(selection)="bulkSelectionService.handleCardSelection('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx, chapters.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true">
|
||||
<ng-container title>
|
||||
|
@ -263,6 +265,7 @@
|
|||
<app-card-item class="col-auto mt-2 mb-2" [entity]="item" [title]="item.title || item.range" (click)="openChapter(item)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(item.id)"
|
||||
[read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions"
|
||||
[count]="item.files.length"
|
||||
(selection)="bulkSelectionService.handleCardSelection('special', scroll.viewPortInfo.startIndexWithBuffer + idx, chapters.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('special', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true">
|
||||
</app-card-item>
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
cursor: pointer;
|
||||
|
||||
.side-nav-text {
|
||||
padding-left: 10px;
|
||||
opacity: 1;
|
||||
min-width: 100px;
|
||||
|
||||
|
|
|
@ -8,6 +8,15 @@
|
|||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title label="Total Words Read" [clickable]="false" fontClasses="fa-regular fa-file-lines" title="Total Words Read">
|
||||
{{totalWordsRead | compactNumber}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container >
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title label="Time Spent Reading" [clickable]="false" fontClasses="fas fa-eye" title="Time Spent Reading">
|
||||
|
@ -20,7 +29,7 @@
|
|||
<ng-container>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title label="Average Hours Read / Week" [clickable]="false" fontClasses="fas fa-eye" title="Average Hours Read / Week">
|
||||
{{avgHoursPerWeekSpentReading | compactNumber}} hours
|
||||
{{avgHoursPerWeekSpentReading | compactNumber | number: '1.0-2'}} hours
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
|
|
|
@ -9,6 +9,7 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core
|
|||
export class UserStatsInfoCardsComponent implements OnInit {
|
||||
|
||||
@Input() totalPagesRead: number = 0;
|
||||
@Input() totalWordsRead: number = 0;
|
||||
@Input() timeSpentReading: number = 0;
|
||||
@Input() chaptersRead: number = 0;
|
||||
@Input() lastActive: string = '';
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<div class="container-fluid" *ngIf="userId">
|
||||
|
||||
<!-- High level stats (use same design as series metadata info cards)-->
|
||||
<div class="row g-0 d-flex justify-content-around">
|
||||
<ng-container *ngIf="userStats$ | async as userStats">
|
||||
<app-user-stats-info-cards [totalPagesRead]="userStats.totalPagesRead" [timeSpentReading]="userStats.timeSpentReading"
|
||||
[chaptersRead]="userStats.chaptersRead" [lastActive]="userStats.lastActive"></app-user-stats-info-cards>
|
||||
<app-user-stats-info-cards [totalPagesRead]="userStats.totalPagesRead" [totalWordsRead]="userStats.totalWordsRead" [timeSpentReading]="userStats.timeSpentReading"
|
||||
[chaptersRead]="userStats.chaptersRead" [lastActive]="userStats.lastActive" [avgHoursPerWeekSpentReading]="userStats.avgHoursPerWeekSpentReading"></app-user-stats-info-cards>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
|
@ -17,14 +16,4 @@
|
|||
<div class="row g-0 pt-4 pb-2 " style="height: 242px">
|
||||
<app-stat-list [data$]="precentageRead$" label="% Read" title="Library Read Progress"></app-stat-list>
|
||||
</div>
|
||||
|
||||
<!-- <div class="row g-0">
|
||||
Books Read (this can be chapters read fully)
|
||||
Number of bookmarks
|
||||
Last Active Time
|
||||
Average days reading on server a week
|
||||
Total Series in want to read list?
|
||||
</div> -->
|
||||
|
||||
|
||||
</div>
|
|
@ -1,21 +1,15 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||
import { map, Observable, of, shareReplay, Subject, takeUntil } from 'rxjs';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { map, Observable, shareReplay, Subject, takeUntil } from 'rxjs';
|
||||
import { FilterUtilitiesService } from 'src/app/shared/_services/filter-utilities.service';
|
||||
import { Series } from 'src/app/_models/series';
|
||||
import { UserReadStatistics } from 'src/app/statistics/_models/user-read-statistics';
|
||||
import { SeriesService } from 'src/app/_services/series.service';
|
||||
import { StatisticsService } from 'src/app/_services/statistics.service';
|
||||
import { SortableHeader, SortEvent } from 'src/app/_single-module/table/_directives/sortable-header.directive';
|
||||
import { ReadHistoryEvent } from '../../_models/read-history-event';
|
||||
import { MemberService } from 'src/app/_services/member.service';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
import { PieDataItem } from '../../_models/pie-data-item';
|
||||
import { LibraryTypePipe } from 'src/app/pipe/library-type.pipe';
|
||||
import { LibraryService } from 'src/app/_services/library.service';
|
||||
import { PercentPipe } from '@angular/common';
|
||||
|
||||
type SeriesWithProgress = Series & {progress: number};
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-stats',
|
||||
templateUrl: './user-stats.component.html',
|
||||
|
|
|
@ -2,6 +2,7 @@ import { StatCount } from "./stat-count";
|
|||
|
||||
export interface UserReadStatistics {
|
||||
totalPagesRead: number;
|
||||
totalWordsRead: number;
|
||||
timeSpentReading: number;
|
||||
chaptersRead: number;
|
||||
lastActive: string;
|
||||
|
|
|
@ -14,7 +14,7 @@ import { SelectionCompareFn, TypeaheadSettings } from '../_models/typeahead-sett
|
|||
* @param selectedOptions Optional data elements to inform the SelectionModel of. If not passed, as toggle() occur, items are tracked.
|
||||
* @param propAccessor Optional string that points to a unique field within the T type. Used for quickly looking up.
|
||||
*/
|
||||
export class SelectionModel<T> {
|
||||
export class SelectionModel<T extends object> {
|
||||
_data!: Array<{value: T, selected: boolean}>;
|
||||
_propAccessor: string = '';
|
||||
|
||||
|
@ -137,13 +137,13 @@ const ANIMATION_SPEED = 200;
|
|||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [
|
||||
trigger('slideFromTop', [
|
||||
state('in', style({ height: '0px', overflow: 'hidden'})),
|
||||
state('in', style({ height: '0px'})),
|
||||
transition('void => *', [
|
||||
style({ height: '100%', overflow: 'auto' }),
|
||||
animate(ANIMATION_SPEED)
|
||||
]),
|
||||
transition('* => void', [
|
||||
animate(ANIMATION_SPEED, style({ height: '0px', overflow: 'hidden' })),
|
||||
animate(ANIMATION_SPEED, style({ height: '0px' })),
|
||||
])
|
||||
])
|
||||
]
|
||||
|
|
|
@ -247,7 +247,7 @@
|
|||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-book-layout-mode" class="form-label">Layout Mode</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="bookLayoutModeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookLayoutModeTooltip>How content should be laid out. Default is as the book packs it. 1 or 2 Column fits to the height of the device and fits 1 or 2 columns of text per page</ng-template>
|
||||
<ng-template #bookLayoutModeTooltip>How content should be laid out. Scroll is as the book packs it. 1 or 2 Column fits to the height of the device and fits 1 or 2 columns of text per page</ng-template>
|
||||
<span class="visually-hidden" id="settings-book-layout-mode-help"><ng-container [ngTemplateOutlet]="bookLayoutModeTooltip"></ng-container></span>
|
||||
<select class="form-select" aria-describedby="settings-book-layout-mode-help" formControlName="bookReaderLayoutMode" id="settings-book-layout-mode">
|
||||
<option *ngFor="let opt of bookLayoutModes" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
|
|
|
@ -2,51 +2,3 @@
|
|||
background-color: var(--side-nav-bg-color);
|
||||
box-shadow: var(--side-nav-box-shadow);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// @media (max-width: 576px) {
|
||||
// .side-nav-item {
|
||||
// align-items: center;
|
||||
// display: flex;
|
||||
// justify-content: space-between;
|
||||
// padding: 15px 10px;
|
||||
// width: 100%;
|
||||
// height: 70px;
|
||||
// min-height: 40px;
|
||||
// overflow: hidden;
|
||||
// font-size: 1rem;
|
||||
|
||||
// cursor: pointer; // This needs to be based a flag
|
||||
|
||||
// .side-nav-text {
|
||||
// padding-left: 10px;
|
||||
// opacity: 1;
|
||||
// min-width: 100px;
|
||||
// width: 100%;
|
||||
|
||||
// div {
|
||||
// min-width: 102px;
|
||||
// width: 100%
|
||||
// }
|
||||
// }
|
||||
|
||||
// &.closed {
|
||||
// .side-nav-text {
|
||||
// opacity: 0;
|
||||
// }
|
||||
|
||||
// .card-actions {
|
||||
// opacity: 0;
|
||||
// font-size: inherit
|
||||
// }
|
||||
// }
|
||||
|
||||
// span {
|
||||
// &:last-child {
|
||||
// flex-grow: 1;
|
||||
// justify-content: end;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -194,7 +194,7 @@
|
|||
--card-progress-bar-color: var(--primary-color);
|
||||
--card-overlay-bg-color: rgba(0, 0, 0, 0);
|
||||
--card-overlay-hover-bg-color: rgba(0, 0, 0, 0.2);
|
||||
--card-progress-triangle-size: 30px;
|
||||
--card-progress-triangle-size: 20px;
|
||||
|
||||
/* Slider */
|
||||
--slider-text-color: white;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue