mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Bugfix/reader issues (#1311)
* Updated the design of icon and text to show a label. * Fixed a bug when fit to height and there is overflow on horizontal, the pagination area is stuck to the original width and after scrolling right, the pagination area doesn't move. * Attempt to fix a border showing on eink readers white mode with book reader * Removed debug code * Added back in pagination controls * fixing viewport overflow issue * Ensure volume detail drawer shows all pages for the volume, not just first chapter. Don't show release date when it's not a real date. Non-ongoing series will now show a different icon. * Fixing drawer viewport issue * Fix for book cover extending across multiple pages (#1269) * Attaching book-reader scale style to parent instead of image * Fixing some width issues on images * Making sure max-height is respected * Fixing duplicate styles which caused excessive breaks * Updated the readme to reflect new UX and some tweaks to wordings. (#1270) * Bump versions by dotnet-bump-version. * version bump (#1271) * Bump versions by dotnet-bump-version. * UX Changes, Tasks, WebP, and More! (#1280) * When account updates occur for a user, send an event to them to tell them to refresh their account information (if they are on the site at the time). This way if we revoke permissions, the site will reactively adapt. * Some cleanup on the user preferences to remove some calls we don't need anymore. * Removed old bulk cleanup bookmark code as it's no longer needed. * Tweaked the messaging for stat collection to reflect what we collect now versus when this was initially implemented. * Implemented the ability for users to configure their servers to save bookmarks as webP. Reorganized the tabs for Admin dashboard to account for upcoming features. * Implemented the ability to bulk convert bookmarks (as many times as the user wants). Added a display of Reoccurring Jobs to the Tasks admin tab. Currently it's just placeholder, but will be enhanced further later in the release. * Tweaked the wording around the convert switch. * Moved System actions to the task tab * Added a controller just for Tachiyomi so we can have dedicated APIs for that client. Deprecated an existing API on the Reader route. * Fixed the unit tests * Bump versions by dotnet-bump-version. * Implemented the ability to read format tag and force special status. (#1284) * Bump versions by dotnet-bump-version. * Implemented the ability to parse some volume and chapter keywords for chinese. (#1285) * Bump versions by dotnet-bump-version. * Word Count (#1286) * Adding some code for Robbie * See more on series detail metadata area is now at the bottom on the section * Cleaned up subtitle headings to use a single class for offset with actionables * Added some markup for the new design, waiting for Robbie to finish it off * styling age-rating badge * Started hooking up basic analyze file service and hooks in the UI. Basic code to implement the count is implemented and in benchmarks. * Hooked up analyze ui to backend * Refactored Series Detail metadata area to use a new icon/title design * Cleaned up the new design * Pushing for robbie to do css * Massive performance improvement to scan series where we only need to scan folders reported that have series in them, rather than the whole library. * Removed theme page as we no longer need it. Added WordCount to DTOs so the UI can show them. Added new pipe to format numbers in compact mode. * Hooked up actual reading time based on user's words per hour * Refactor some magic numbers to consts * Hooked in progress reporting for series word count * Hooked up analyze files * Re-implemented time to read on comics * Removed the word Last Read * Show proper language name instead of iso tag on series detail page. Added some error handling on word count code. * Reworked error handling * Fixed some security vulnerabilities in npm. * Handle a case where there are no text nodes and instead of returning an empty list, htmlagilitypack returns null. * Tweaked the styles a bit on the icon-and-title * Code cleanup Co-authored-by: Robbie Davis <robbie@therobbiedavis.com> * Bump versions by dotnet-bump-version. * Tweaked when we calculate min reading time * Don't use plural if there is only 1 hour for reading * Fixed the logic for caluclating time to read on comics * Bump versions by dotnet-bump-version. * Drawers, Estimated Reading Time, Korean Parsing Support (#1297) * Started building out idea around detail drawer. Need code from word count to continue * Fixed the logic for caluclating time to read on comics * Adding styles * more styling fixes * Cleaned up the styles a bit more so it's at least functional. Not sure on the feature, might abandon. * Pulled Robbie's changes in and partially migrated them to the drawer. * Add offset overrides for offcanvas so it takes our header into account * Implemented a basic time left to finish the series (or at least what's in Kavita). Rough around the edges. * Cleaned up the drawer code. * Added Quick Catch ups to recommended page. Updated the timeout for scan tasks to ensure we don't run 2 at the same time. * Quick catchups implemented * Added preliminary support for Korean filename parsing. Reduced an array alloc that is called many thousands of times per scan. * Fixing drawer overflow * Fixed a calculation bug with average reading time. * Small spacing changes to drawer * Don't show estimated reading time if the user hasn't read anything * Bump eventsource from 1.1.1 to 2.0.2 in /UI/Web Bumps [eventsource](https://github.com/EventSource/eventsource) from 1.1.1 to 2.0.2. - [Release notes](https://github.com/EventSource/eventsource/releases) - [Changelog](https://github.com/EventSource/eventsource/blob/master/HISTORY.md) - [Commits](https://github.com/EventSource/eventsource/compare/v1.1.1...v2.0.2) --- updated-dependencies: - dependency-name: eventsource dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * Added image to series detail drawer Co-authored-by: Robbie Davis <robbie@therobbiedavis.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump versions by dotnet-bump-version. * Time Estimation Cleanup (#1301) * Moved the calculation for time to read to the backend. Tweaked some logic around showing est time to complete. * Added debug logging to help pinpoint a duplicate issue in Kavita. * More combination logic is error checked in a special way for Robbie to reproduce an issue. * Migrated chapter detail card to use backend for time calculation. Ensure we take all chapters into account for volume time calcs * Tweaked messaging for some critical logs to include file * Ensure pages count uses comma separated number * Moved Hangfire annotations to interface level. Adjusted word count service to always recalculate when user requests via analyze series files. * Bump versions by dotnet-bump-version. * Jump Bar Testing (#1302) * Implemented a basic jump bar for the library view. This currently just interacts with existing pagination controls and is not inlined with infinite scroll yet. This is a first pass implementation. * Refactored time estimates into the reading service. * Cleaned up when the jump bar is shown to mimic pagination controls * Cleanup up code in reader service. * Scroll to card when selecting a jump key that is shown on the current page. * Ensure estimated times always has the smaller number on left hand side. * Fixed a bug with a missing vertical rule * Fixed an off by 1 pixel for search overlay * Bump versions by dotnet-bump-version. * Jumpbar Tweaks (#1305) * Adjusted the detail drawer to be slightly larger. * Attempted to shorten the jump bar on smaller screens. Robbie needs to take a look at this. * Adding plex-like styling to jumpbar * style fixes * style fixes * More fixes *sigh* * fix height issue * Fixing jumpbar on mobile * viewport height fix * added --primary-color-scrollbar for overflow across the app Co-authored-by: Robbie Davis <robbie@therobbiedavis.com> * Bump versions by dotnet-bump-version. Co-authored-by: Joseph Milazzo <joseph.v.milazzo@gmail.com> Co-authored-by: majora2007 <josephmajora@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump versions by dotnet-bump-version. * Close drawer when opening a chapter to read * The actionables were not working on chapters in the detail drawer for a volume. Added a distinct read action instead of having the user to have hidden knowledge you can click the cover images. * Debug code in. Fixed a bug where book reader pagination wasn't covering from top to bottom when immersive mode was on. Trying to better ensure images don't span multiple virtual pages in column mode. Changed icon/title so label is bolded * Updated some dependencies for security issues. Fixed up the fix for images on column view wrapping to next virtual page. Lots of css tweaks to the layout code to make it easier to work with. * When switching from column layout to default, scroll back to where the user was. * Fixed some overlap on default mode with bottom bar * Series Detail uses the cover image update mechanism builtin, instead of the old way to handle. Added some text when there is no summary on a volume/chapter. * Side nav filter now has clear button within field. Filter no longer shows when side nav is collapsed. Co-authored-by: Robbie Davis <robbie@therobbiedavis.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
0906ca49a9
commit
77b8551620
@ -46,7 +46,7 @@
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.29" />
|
||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.42" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
|
||||
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.5" />
|
||||
@ -63,14 +63,14 @@
|
||||
<PackageReference Include="NetVips.Native" Version="8.12.2" />
|
||||
<PackageReference Include="NReco.Logging.File" Version="1.1.5" />
|
||||
<PackageReference Include="SharpCompress" Version="0.31.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.1" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.39.0.47922">
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.2" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.40.0.48530">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.18.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.19.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="17.0.15" />
|
||||
<PackageReference Include="VersOne.Epub" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
|
@ -5,7 +5,7 @@
|
||||
"TokenKey": "super secret unguessable key",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Default": "Critical",
|
||||
"Microsoft": "Information",
|
||||
"Microsoft.Hosting.Lifetime": "Error",
|
||||
"Hangfire": "Information",
|
||||
|
@ -12,7 +12,7 @@
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.39.0.47922">
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.40.0.48530">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -65,6 +65,10 @@ export enum Action {
|
||||
* Open Series detail page for said series
|
||||
*/
|
||||
ViewSeries = 13,
|
||||
/**
|
||||
* Open the reader for entity
|
||||
*/
|
||||
Read = 14,
|
||||
}
|
||||
|
||||
export interface ActionItem<T> {
|
||||
|
@ -36,13 +36,11 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(){
|
||||
this.setDocHeight();
|
||||
}
|
||||
|
||||
@HostListener('window:orientationchange', ['$event'])
|
||||
onOrientationChange() {
|
||||
this.setDocHeight();
|
||||
setDocHeight() {
|
||||
// Sets a CSS variable for the actual device viewport height. Needed for mobile dev.
|
||||
const vh = window.innerHeight * 0.01;
|
||||
this.document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -59,10 +57,4 @@ export class AppComponent implements OnInit {
|
||||
this.libraryService.getLibraryNames().pipe(take(1)).subscribe(() => {/* No Operation */});
|
||||
}
|
||||
}
|
||||
|
||||
setDocHeight() {
|
||||
// Sets a CSS variable for the actual device viewport height. Needed for mobile dev.
|
||||
let vh = window.innerHeight * 0.01;
|
||||
this.document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||
}
|
||||
}
|
||||
|
@ -72,24 +72,26 @@
|
||||
</app-drawer>
|
||||
</div>
|
||||
|
||||
<div #readingSection class="reading-section {{ColumnLayout}}" [@isLoading]="isLoading ? true : false">
|
||||
<div #readingSection class="reading-section {{ColumnLayout}}" [ngClass]="{'immersive' : immersiveMode || !actionBarVisible}" [@isLoading]="isLoading ? true : false">
|
||||
|
||||
<ng-container *ngIf="clickToPaginate">
|
||||
<div class="left {{clickOverlayClass('left')}} no-observe"
|
||||
<div class="left {{clickOverlayClass('left')}} no-observe" [ngClass]="{'immersive' : immersiveMode}"
|
||||
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
tabindex="-1" [ngStyle]="{height: PageHeightForPagination}"></div>
|
||||
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe"
|
||||
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe"
|
||||
[ngClass]="{'immersive' : immersiveMode}"
|
||||
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
|
||||
tabindex="-1" [ngStyle]="{height: PageHeightForPagination}"></div>
|
||||
</ng-container>
|
||||
|
||||
<div class="book-container {{ColumnLayout}}">
|
||||
<div class="book-container" [ngClass]="{'immersive' : immersiveMode}"> <!-- {{ColumnLayout}}-->
|
||||
|
||||
<div #readingHtml class="book-content {{ColumnLayout}}" [ngStyle]="{'max-height': ColumnHeight, 'column-width': ColumnWidth}"
|
||||
<div #readingHtml class="book-content {{ColumnLayout}}" [ngStyle]="{'max-height': ColumnHeight, 'column-width': ColumnWidth}"
|
||||
[ngClass]="{'immersive': immersiveMode && actionBarVisible}"
|
||||
[innerHtml]="page" *ngIf="page !== undefined" (click)="toggleMenu($event)" (mousedown)="mouseDown($event)"></div>
|
||||
|
||||
|
||||
<div *ngIf="page !== undefined && (scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default)" (click)="$event.stopPropagation();">
|
||||
<div *ngIf="page !== undefined && (scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default)" (click)="$event.stopPropagation();" [ngClass]="{'bottom-bar': layoutMode !== BookPageLayoutMode.Default}">
|
||||
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -130,6 +130,7 @@ $action-bar-height: 38px;
|
||||
overflow: auto;
|
||||
height: calc(var(--vh, 1vh) * 100);
|
||||
position: relative;
|
||||
// This is completely invisible, everything else renders over it
|
||||
|
||||
&.column-layout-1 {
|
||||
height: calc(var(--vh) * 100);
|
||||
@ -145,8 +146,11 @@ $action-bar-height: 38px;
|
||||
//overflow: auto; // This will break progress reporting
|
||||
height: 100vh;
|
||||
padding-top: $action-bar-height;
|
||||
padding-bottom: $action-bar-height;
|
||||
position: relative;
|
||||
|
||||
//background-color: green !important;
|
||||
|
||||
&.column-layout-1 {
|
||||
height: calc((var(--vh, 1vh) * 100) - $action-bar-height);
|
||||
}
|
||||
@ -154,12 +158,20 @@ $action-bar-height: 38px;
|
||||
&.column-layout-2 {
|
||||
height: calc((var(--vh, 1vh) * 100) - $action-bar-height);
|
||||
}
|
||||
|
||||
&.immersive {
|
||||
height: calc((var(--vh, 1vh) * 100));
|
||||
//padding-top: 0px;
|
||||
//padding-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.book-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
//background-color: purple !important;
|
||||
|
||||
&.column-layout-1 {
|
||||
height: calc((var(--vh, 1vh) * 100) - $action-bar-height);
|
||||
}
|
||||
@ -173,13 +185,18 @@ $action-bar-height: 38px;
|
||||
position: relative;
|
||||
padding: 20px 0;
|
||||
margin: 0px 0px;
|
||||
//background-color: red !important;
|
||||
|
||||
&.column-layout-1 {
|
||||
height: calc((var(--vh) * 100) - calc($action-bar-height * 2));
|
||||
height: calc((var(--vh) * 100) - calc($action-bar-height)); // * 2
|
||||
}
|
||||
|
||||
&.column-layout-2 {
|
||||
height: calc((var(--vh) * 100) - calc($action-bar-height * 2));
|
||||
height: calc((var(--vh) * 100) - calc($action-bar-height)); // * 2
|
||||
}
|
||||
|
||||
&.immersive {
|
||||
height: calc((var(--vh, 1vh) * 100) - $action-bar-height);
|
||||
}
|
||||
|
||||
a, :link {
|
||||
@ -203,6 +220,12 @@ $action-bar-height: 38px;
|
||||
box-shadow: var(--drawer-pagination-horizontal-rule);
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This is essentially fitting the text to height and when you press next you are scrolling over by page width
|
||||
@ -213,10 +236,6 @@ $action-bar-height: 38px;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
&.debug {
|
||||
column-rule: 20px solid rebeccapurple;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -229,10 +248,6 @@ $action-bar-height: 38px;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
&.debug {
|
||||
column-rule: 20px solid rebeccapurple;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -249,7 +264,8 @@ $action-bar-height: 38px;
|
||||
// This is applied to images in the backend
|
||||
::ng-deep .kavita-scale-width-container {
|
||||
width: auto;
|
||||
max-height: calc((var(--vh)*100) - 116px) !important;
|
||||
// * 4 is just for extra buffer which is needed based on testing. --book-reader-content-max-height is set by us on calculation of columnHeight
|
||||
max-height: calc(var(--book-reader-content-max-height) - ($action-bar-height * 4)), calc((var(--vh)*100) - ($action-bar-height * 4)) !important;
|
||||
}
|
||||
|
||||
// This is applied to images in the backend
|
||||
@ -272,13 +288,21 @@ $action-bar-height: 38px;
|
||||
|
||||
.right {
|
||||
position: absolute;
|
||||
right: 0px; // with scrollbar: 17px
|
||||
right: 0px;
|
||||
top: $action-bar-height;
|
||||
width: 20%; // with scrollbar: 18%
|
||||
|
||||
z-index: 2;
|
||||
width: 20%;
|
||||
z-index: 3;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
border: none !important;
|
||||
opacity: 0;
|
||||
outline: none;
|
||||
//background-color: aqua;
|
||||
|
||||
&.immersive {
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
// This class pushes the click area to the left a bit to let users click the scrollbar
|
||||
@ -287,9 +311,18 @@ $action-bar-height: 38px;
|
||||
right: 17px;
|
||||
top: $action-bar-height;
|
||||
width: 18%;
|
||||
z-index: 2;
|
||||
z-index: 3;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
border: none !important;
|
||||
opacity: 0;
|
||||
outline: none;
|
||||
//background-color: aqua;
|
||||
|
||||
&.immersive {
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.left {
|
||||
@ -298,16 +331,24 @@ $action-bar-height: 38px;
|
||||
top: $action-bar-height;
|
||||
width: 20%;
|
||||
background: transparent;
|
||||
|
||||
z-index: 2;
|
||||
border-color: transparent;
|
||||
border: none !important;
|
||||
z-index: 3;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
outline: none;
|
||||
//background-color: aqua;
|
||||
|
||||
&.immersive {
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.highlight {
|
||||
background-color: rgba(65, 225, 100, 0.5) !important;
|
||||
animation: fadein .5s both;
|
||||
background-color: rgba(65, 225, 100, 0.5) !important;
|
||||
animation: fadein .5s both;
|
||||
}
|
||||
.highlight-2 {
|
||||
background-color: rgba(65, 105, 225, 0.5) !important;
|
||||
@ -333,13 +374,13 @@ $action-bar-height: 38px;
|
||||
background-color: unset;
|
||||
|
||||
&:hover, &:focus {
|
||||
border-color: var(--br-actionbar-button-hover-border-color); // #545b62;
|
||||
border-color: var(--br-actionbar-button-hover-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
background-color: unset;
|
||||
color: var(--br-actionbar-button-text-color); // #6c757d;
|
||||
color: var(--br-actionbar-button-text-color);
|
||||
}
|
||||
|
||||
i {
|
||||
|
@ -352,7 +352,9 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
get ColumnHeight() {
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default) {
|
||||
// Take the height after page loads, subtract the top/bottom bar
|
||||
return this.windowHeight - (this.topOffset *2) + 'px';
|
||||
const height = this.windowHeight - (this.topOffset * 2);
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-max-height', `${height}px`);
|
||||
return height + 'px';
|
||||
}
|
||||
return 'unset';
|
||||
}
|
||||
@ -370,10 +372,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
get PageHeightForPagination() {
|
||||
if (this.layoutMode === BookPageLayoutMode.Default) {
|
||||
return (this.readingSectionElemRef?.nativeElement?.scrollHeight || 0) - (this.topOffset * 2) + 'px';
|
||||
return (this.readingSectionElemRef?.nativeElement?.scrollHeight || 0) - ((this.topOffset * (this.immersiveMode ? 0 : 1)) * 2) + 'px';
|
||||
}
|
||||
|
||||
return this.ColumnHeight;
|
||||
if (this.immersiveMode) return this.windowHeight + 'px';
|
||||
return (this.windowHeight) - (this.topOffset * 2) + 'px';
|
||||
}
|
||||
|
||||
|
||||
@ -808,7 +811,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
const images = this.readingSectionElemRef?.nativeElement.querySelectorAll('img') || [];
|
||||
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default) {
|
||||
const height = this.ColumnHeight;
|
||||
const height = (parseInt(this.ColumnHeight.replace('px', ''), 10) - (this.topOffset * 2)) + 'px';
|
||||
Array.from(images).forEach(img => {
|
||||
this.renderer.setStyle(img, 'max-height', height);
|
||||
});
|
||||
@ -1209,6 +1212,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {this.scrollbarNeeded = this.readingHtml.nativeElement.clientHeight > this.reader.nativeElement.clientHeight;});
|
||||
|
||||
// When I switch layout, I might need to resume the progress point.
|
||||
if (mode === BookPageLayoutMode.Default) {
|
||||
const lastSelector = this.lastSeenScrollPartPath;
|
||||
setTimeout(() => this.scrollTo(lastSelector));
|
||||
}
|
||||
}
|
||||
|
||||
updateReadingDirection(readingDirection: ReadingDirection) {
|
||||
@ -1220,6 +1229,19 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
if (this.immersiveMode && !this.drawerOpen) {
|
||||
this.actionBarVisible = false;
|
||||
}
|
||||
|
||||
this.updateReadingSectionHeight();
|
||||
}
|
||||
|
||||
updateReadingSectionHeight() {
|
||||
setTimeout(() => {
|
||||
console.log('setting height on ', this.readingSectionElemRef)
|
||||
if (this.immersiveMode) {
|
||||
this.renderer.setStyle(this.readingSectionElemRef, '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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Table of Contents
|
||||
|
@ -43,24 +43,29 @@
|
||||
<div class="d-none d-md-block col-md-2 col-lg-1">
|
||||
<app-image class="me-2" width="74px" [imageUrl]="chapter.coverImage"></app-image>
|
||||
</div>
|
||||
<div *ngIf="summary$ | async as summary" class="col-md-10 col-lg-11">
|
||||
<app-read-more [text]="summary" [maxLength]="250"></app-read-more>
|
||||
<div class="col-md-10 col-lg-11">
|
||||
<ng-container *ngIf="summary$ | async as summary; else noSummary">
|
||||
<app-read-more [text]="summary" [maxLength]="250"></app-read-more>
|
||||
</ng-container>
|
||||
<ng-template #noSummary>
|
||||
No Summary available.
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-3">
|
||||
<ng-container *ngIf="chapter.pages">
|
||||
<ng-container *ngIf="totalPages > 0">
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-file-lines" title="Pages">
|
||||
{{chapter.pages | number:''}} Pages
|
||||
<app-icon-and-title label="Print Length" [clickable]="false" fontClasses="fa-regular fa-file-lines" title="Pages">
|
||||
{{totalPages | number:''}} Pages
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="chapterMetadata !== undefined && chapterMetadata.releaseDate">
|
||||
<ng-container *ngIf="chapterMetadata !== undefined && chapterMetadata.releaseDate && (chapterMetadata.releaseDate | date: 'shortDate') !== '1/1/01'">
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-calendar" title="Release">
|
||||
<app-icon-and-title label="Release Date" [clickable]="false" fontClasses="fa-regular fa-calendar" title="Release">
|
||||
{{chapterMetadata.releaseDate | date:'shortDate'}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
@ -69,7 +74,7 @@
|
||||
|
||||
<ng-container *ngIf="chapter.files[0].format === MangaFormat.EPUB && chapterMetadata !== undefined && chapterMetadata.wordCount > 0 || chapter.files[0].format !== MangaFormat.EPUB">
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-clock">
|
||||
<app-icon-and-title label="Read Time" [clickable]="false" fontClasses="fa-regular fa-clock">
|
||||
{{readingTime.minHours}}{{readingTime.maxHours !== readingTime.minHours ? ('-' + readingTime.maxHours) : ''}} Hour{{readingTime.minHours > 1 ? 's' : ''}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
@ -78,7 +83,7 @@
|
||||
<ng-container *ngIf="chapter.files[0].format === MangaFormat.EPUB && chapterMetadata !== undefined && chapterMetadata.wordCount > 0">
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [clickable]="false" fontClasses="fa-solid fa-book-open">
|
||||
<app-icon-and-title label="Word Count" [clickable]="false" fontClasses="fa-solid fa-book-open">
|
||||
{{chapterMetadata.wordCount | compactNumber}} Words
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
@ -88,12 +93,30 @@
|
||||
<ng-container *ngIf="ageRating !== '' && ageRating !== 'Unknown'">
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [clickable]="false" fontClasses="fas fa-eye" title="Age Rating">
|
||||
<app-icon-and-title label="Age Rating" [clickable]="false" fontClasses="fas fa-eye" title="Age Rating">
|
||||
{{ageRating}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="chapter.created && chapter.created !== '' && (chapter.created | date: 'shortDate') !== '1/1/01'">
|
||||
<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' || '-'}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title label="ID" [clickable]="false" fontClasses="fa-solid fa-fingerprint" title="ID">
|
||||
{{data.id}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
|
||||
@ -182,24 +205,6 @@
|
||||
<li [ngbNavItem]="tabs[TabID.Files]">
|
||||
<a ngbNavLink>{{tabs[TabID.Files].title}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="row g-0 mb-3">
|
||||
<ng-container *ngIf="chapter.created && chapter.created !== '' && (chapter.created | date: 'shortDate') !== '1/1/01'">
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [clickable]="false" fontClasses="fa-solid fa-file-import" title="Date Added">
|
||||
Created: {{chapter.created | date:'short' || '-'}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [clickable]="false" fontClasses="fa-solid fa-fingerprint" title="ID">
|
||||
ID: {{data.id}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<h4 *ngIf="!utilityService.isChapter(data)">{{utilityService.formatChapterName(libraryType) + 's'}}</h4>
|
||||
<ul class="list-unstyled">
|
||||
<li class="d-flex my-4" *ngFor="let chapter of chapters">
|
||||
|
@ -12,5 +12,5 @@
|
||||
|
||||
.tab-content {
|
||||
overflow: auto;
|
||||
height: calc(45vh - 63px); // drawer height - offcanvas heading height
|
||||
height: calc(40vh - 63px); // drawer height - offcanvas heading height
|
||||
}
|
||||
|
@ -2,7 +2,9 @@ import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgbActiveOffcanvas } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { Observable, of, take } from 'rxjs';
|
||||
import { finalize, Observable, of, take, takeWhile, tap } from 'rxjs';
|
||||
import { Download } from 'src/app/shared/_models/download';
|
||||
import { DownloadService } from 'src/app/shared/_services/download.service';
|
||||
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { Chapter } from 'src/app/_models/chapter';
|
||||
import { ChapterMetadata } from 'src/app/_models/chapter-metadata';
|
||||
@ -18,7 +20,7 @@ import { ActionService } from 'src/app/_services/action.service';
|
||||
import { ImageService } from 'src/app/_services/image.service';
|
||||
import { LibraryService } from 'src/app/_services/library.service';
|
||||
import { MetadataService } from 'src/app/_services/metadata.service';
|
||||
import { MAX_PAGES_PER_MINUTE, MAX_WORDS_PER_HOUR, MIN_PAGES_PER_MINUTE, MIN_WORDS_PER_HOUR, ReaderService } from 'src/app/_services/reader.service';
|
||||
import { ReaderService } from 'src/app/_services/reader.service';
|
||||
import { SeriesService } from 'src/app/_services/series.service';
|
||||
import { UploadService } from 'src/app/_services/upload.service';
|
||||
|
||||
@ -81,6 +83,13 @@ export class CardDetailDrawerComponent implements OnInit {
|
||||
readingTime: HourEstimateRange = {maxHours: 1, minHours: 1, avgHours: 1, hasProgress: false};
|
||||
minHoursToRead: number = 1;
|
||||
maxHoursToRead: number = 1;
|
||||
/**
|
||||
* We use a separate variable because if this is a volume, we need a sum of all chapters
|
||||
*/
|
||||
totalPages: number = 0;
|
||||
|
||||
download$: Observable<Download> | null = null;
|
||||
downloadInProgress: boolean = false;
|
||||
|
||||
|
||||
|
||||
@ -109,7 +118,7 @@ export class CardDetailDrawerComponent implements OnInit {
|
||||
private accountService: AccountService, private actionFactoryService: ActionFactoryService,
|
||||
private actionService: ActionService, private router: Router, private libraryService: LibraryService,
|
||||
private seriesService: SeriesService, private readerService: ReaderService, public metadataService: MetadataService,
|
||||
public activeOffcanvas: NgbActiveOffcanvas) { }
|
||||
public activeOffcanvas: NgbActiveOffcanvas, private downloadService: DownloadService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isChapter = this.utilityService.isChapter(this.data);
|
||||
@ -123,13 +132,13 @@ export class CardDetailDrawerComponent implements OnInit {
|
||||
|
||||
this.metadataService.getAgeRating(this.chapterMetadata.ageRating).subscribe(ageRating => this.ageRating = ageRating);
|
||||
|
||||
let totalPages = this.chapter.pages;
|
||||
this.totalPages = this.chapter.pages;
|
||||
if (!this.isChapter) {
|
||||
// Need to account for multiple chapters if this is a volume
|
||||
totalPages = this.utilityService.asVolume(this.data).chapters.map(c => c.pages).reduce((sum, d) => sum + d);
|
||||
this.totalPages = this.utilityService.asVolume(this.data).chapters.map(c => c.pages).reduce((sum, d) => sum + d);
|
||||
}
|
||||
|
||||
this.readerService.getManualTimeToRead(this.chapterMetadata.wordCount, totalPages, this.chapter.files[0].format === MangaFormat.EPUB).subscribe((time) => this.readingTime = time);
|
||||
this.readerService.getManualTimeToRead(this.chapterMetadata.wordCount, this.totalPages, this.chapter.files[0].format === MangaFormat.EPUB).subscribe((time) => this.readingTime = time);
|
||||
});
|
||||
|
||||
|
||||
@ -153,6 +162,7 @@ export class CardDetailDrawerComponent implements OnInit {
|
||||
});
|
||||
|
||||
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this)).filter(item => item.action !== Action.Edit);
|
||||
this.chapterActions.push({title: 'Read', action: Action.Read, callback: this.handleChapterActionCallback.bind(this), requiresAdmin: false});
|
||||
|
||||
if (this.isChapter) {
|
||||
this.chapters.push(this.data as Chapter);
|
||||
@ -245,22 +255,59 @@ export class CardDetailDrawerComponent implements OnInit {
|
||||
case(Action.AddToReadingList):
|
||||
this.actionService.addChapterToReadingList(chapter, this.seriesId);
|
||||
break;
|
||||
case (Action.IncognitoRead):
|
||||
this.readChapter(chapter, true);
|
||||
break;
|
||||
case (Action.Download):
|
||||
this.download(chapter);
|
||||
break;
|
||||
case (Action.Read):
|
||||
this.readChapter(chapter, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
readChapter(chapter: Chapter) {
|
||||
readChapter(chapter: Chapter, incognito: boolean = false) {
|
||||
if (chapter.pages === 0) {
|
||||
this.toastr.error('There are no pages. Kavita was not able to read this archive.');
|
||||
return;
|
||||
}
|
||||
|
||||
const params = this.readerService.getQueryParamsObject(incognito, false);
|
||||
if (chapter.files.length > 0 && chapter.files[0].format === MangaFormat.EPUB) {
|
||||
this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'book', chapter.id]);
|
||||
this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'book', chapter.id], {queryParams: params});
|
||||
} else {
|
||||
this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'manga', chapter.id]);
|
||||
this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'manga', chapter.id], {queryParams: params});
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
|
||||
download(chapter: Chapter) {
|
||||
if (this.downloadInProgress === true) {
|
||||
this.toastr.info('Download is already in progress. Please wait.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.downloadService.downloadChapterSize(chapter.id).pipe(take(1)).subscribe(async (size) => {
|
||||
const wantToDownload = await this.downloadService.confirmSize(size, 'chapter');
|
||||
console.log('want to download: ', wantToDownload);
|
||||
if (!wantToDownload) { return; }
|
||||
this.downloadInProgress = true;
|
||||
this.download$ = this.downloadService.downloadChapter(chapter).pipe(
|
||||
tap(val => {
|
||||
console.log(val);
|
||||
}),
|
||||
takeWhile(val => {
|
||||
return val.state != 'DONE';
|
||||
}),
|
||||
finalize(() => {
|
||||
this.download$ = null;
|
||||
this.downloadInProgress = false;
|
||||
}));
|
||||
this.download$.subscribe(() => {});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,18 +12,23 @@
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-metadata-filter [filterSettings]="filterSettings" [filterOpen]="filterOpen" (applyFilter)="applyMetadataFilter($event)"></app-metadata-filter>
|
||||
<div class="content-container">
|
||||
<div class="card-container">
|
||||
<ng-container [ngTemplateOutlet]="cardTemplate"></ng-container>
|
||||
<div class="viewport-container">
|
||||
<div class="content-container">
|
||||
<ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'top' }"></ng-container>
|
||||
|
||||
<div class="card-container mt-2 mb-2">
|
||||
<ng-container [ngTemplateOutlet]="cardTemplate"></ng-container>
|
||||
</div>
|
||||
|
||||
<ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'bottom' }"></ng-container>
|
||||
</div>
|
||||
|
||||
<ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'bottom' }"></ng-container>
|
||||
|
||||
<ng-container *ngIf="pagination && items.length > 0 && pagination.totalPages > 1" [ngTemplateOutlet]="jumpBar" [ngTemplateOutletContext]="{ id: 'jumpbar' }"></ng-container>
|
||||
</div>
|
||||
<ng-template #cardTemplate>
|
||||
|
||||
<div class="grid row g-0 mb-3" >
|
||||
<div class="grid row g-0" >
|
||||
<div class="card col-auto mt-2 mb-2" *ngFor="let item of items; trackBy:trackByIdentity; index as i" id="jumpbar-index--{{i}}" [attr.jumpbar-index]="i">
|
||||
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
||||
</div>
|
||||
@ -34,9 +39,52 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template #paginationTemplate let-id="id">
|
||||
<ng-container *ngIf="pagination && items.length > 0 && id == 'bottom' && pagination.totalPages > 1 " [ngTemplateOutlet]="jumpBar"></ng-container>
|
||||
|
||||
<div class="d-flex justify-content-center mb-0" *ngIf="pagination && items.length > 0">
|
||||
<ngb-pagination
|
||||
*ngIf="pagination.totalPages > 1"
|
||||
[maxSize]="8"
|
||||
[rotate]="true"
|
||||
[ellipses]="false"
|
||||
[(page)]="pagination.currentPage"
|
||||
[pageSize]="pagination.itemsPerPage"
|
||||
(pageChange)="onPageChange($event)"
|
||||
[collectionSize]="pagination.totalItems">
|
||||
|
||||
<ng-template ngbPaginationPages let-page let-pages="pages" *ngIf="pagination.totalItems / pagination.itemsPerPage > 20">
|
||||
<li class="ngb-custom-pages-item" *ngIf="pagination.totalPages > 1">
|
||||
<div class="d-flex flex-nowrap px-2">
|
||||
<label
|
||||
id="paginationInputLabel-{{id}}"
|
||||
for="paginationInput-{{id}}"
|
||||
class="col-form-label me-2 ms-1 form-label"
|
||||
>Page</label>
|
||||
<input #i
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]*"
|
||||
class="form-control custom-pages-input"
|
||||
id="paginationInput-{{id}}"
|
||||
[value]="page"
|
||||
(keyup.enter)="selectPageStr(i.value)"
|
||||
(blur)="selectPageStr(i.value)"
|
||||
(input)="formatInput($any($event).target)"
|
||||
attr.aria-labelledby="paginationInputLabel-{{id}} paginationDescription-{{id}}"
|
||||
[ngStyle]="{width: (0.5 + pagination.currentPage + '').length + 'rem'} "
|
||||
/>
|
||||
<span id="paginationDescription-{{id}}" class="col-form-label text-nowrap px-2">
|
||||
of {{pagination.totalPages}}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ng-template>
|
||||
|
||||
</ngb-pagination>
|
||||
</div>
|
||||
|
||||
<!-- <ng-container *ngIf="pagination && items.length > 0 && id == 'bottom' && pagination.totalPages > 1 " [ngTemplateOutlet]="jumpBar"></ng-container> -->
|
||||
</ng-template>
|
||||
|
||||
|
||||
<div class="mx-auto" *ngIf="isLoading" style="width: 200px;">
|
||||
<div class="spinner-border text-secondary loading" role="status">
|
||||
<span class="invisible">Loading...</span>
|
||||
|
@ -1,12 +1,23 @@
|
||||
.content-container {
|
||||
display: inline-flex;
|
||||
.viewport-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: calc((var(--vh) *100) - 152px);
|
||||
height: calc((var(--vh) *100) - 162px);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.grid {
|
||||
@ -15,12 +26,17 @@
|
||||
grid-gap: 0.5rem;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
max-height: calc((var(--vh) *100) - 152px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.grid {
|
||||
grid-gap: 0.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.jump-bar {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
@ -7,4 +7,5 @@
|
||||
<button ngbDropdownItem *ngFor="let action of adminActions" (click)="performAction($event, action)">{{action.title}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TODO: If we are not on desktop, then let's open a bottom drawer instead-->
|
||||
</ng-container>
|
@ -41,13 +41,13 @@
|
||||
|
||||
<div class="pagination-area">
|
||||
<!-- Pagination controls and screen hints-->
|
||||
<div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')" [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: 25 + '%')}">
|
||||
<div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')" [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: '25%')}">
|
||||
<div *ngIf="showClickOverlay">
|
||||
<i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}"
|
||||
title="Previous Page" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')" [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: 25 + '%'), 'left': (readerMode === ReaderMode.LeftRight && (this.generalSettingsForm.get('fittingOption')?.value === FITTING_OPTION.ORIGINAL) ? ImageWidth: 'inherit')}">
|
||||
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')" [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: '25%'), 'left': (readerMode === ReaderMode.LeftRight && (this.generalSettingsForm.get('fittingOption')?.value === FITTING_OPTION.ORIGINAL) ? ImageWidth: 'inherit'), 'right': rightPaginationOffset + 'px'}">
|
||||
<div *ngIf="showClickOverlay">
|
||||
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}"
|
||||
title="Next Page" aria-hidden="true"></i>
|
||||
|
@ -249,7 +249,7 @@ img {
|
||||
}
|
||||
|
||||
$pagination-bg: rgba(0, 0, 0, 0);
|
||||
//$pagination-bg: rgba(0, 0, 255, 0.4);
|
||||
//$pagination-bg: rgba(0, 0, 255, 0.4); // DEBUG CODE
|
||||
|
||||
.pagination-area {
|
||||
cursor: pointer;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { DOCUMENT, Location } from '@angular/common';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { take, takeUntil } from 'rxjs/operators';
|
||||
import { debounceTime, take, takeUntil } from 'rxjs/operators';
|
||||
import { User } from '../_models/user';
|
||||
import { AccountService } from '../_services/account.service';
|
||||
import { ReaderService } from '../_services/reader.service';
|
||||
@ -10,7 +10,7 @@ import { NavService } from '../_services/nav.service';
|
||||
import { ReadingDirection } from '../_models/preferences/reading-direction';
|
||||
import { ScalingOption } from '../_models/preferences/scaling-option';
|
||||
import { PageSplitOption } from '../_models/preferences/page-split-option';
|
||||
import { BehaviorSubject, forkJoin, ReplaySubject, Subject } from 'rxjs';
|
||||
import { BehaviorSubject, forkJoin, fromEvent, ReplaySubject, Subject } from 'rxjs';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { Breakpoint, KEY_CODES, UtilityService } from '../shared/_services/utility.service';
|
||||
import { CircularArray } from '../shared/data-structures/circular-array';
|
||||
@ -260,6 +260,11 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
*/
|
||||
backgroundColor: string = '#FFFFFF';
|
||||
|
||||
/**
|
||||
* This is here as absolute layout requires us to calculate a negative right property for the right pagination when there is overflow. This is calculated on scroll.
|
||||
*/
|
||||
rightPaginationOffset = 0;
|
||||
|
||||
getPageUrl = (pageNum: number) => {
|
||||
if (this.bookmarkMode) return this.readerService.getBookmarkPageUrl(this.seriesId, this.user.apiKey, pageNum);
|
||||
return this.readerService.getPageUrl(this.chapterId, pageNum);
|
||||
@ -303,6 +308,14 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
return this.image?.nativeElement.height + 'px';
|
||||
}
|
||||
|
||||
|
||||
get RightPaginationOffset() {
|
||||
if (this.readerMode === ReaderMode.LeftRight && this.generalSettingsForm.get('fittingOption')?.value === FITTING_OPTION.HEIGHT) {
|
||||
return (this.readingArea?.nativeElement?.scrollLeft || 0) * -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
get splitIconClass() {
|
||||
if (this.isSplitLeftToRight()) {
|
||||
return 'left-side';
|
||||
@ -369,8 +382,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.libraryId = parseInt(libraryId, 10);
|
||||
this.seriesId = parseInt(seriesId, 10);
|
||||
this.chapterId = parseInt(chapterId, 10);
|
||||
@ -449,12 +460,23 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (!this.canvas) {
|
||||
return;
|
||||
}
|
||||
this.ctx = this.canvas.nativeElement.getContext('2d', { alpha: false });
|
||||
this.canvasImage.onload = () => this.renderPage();
|
||||
|
||||
fromEvent(this.readingArea.nativeElement, 'scroll').pipe(debounceTime(20), takeUntil(this.onDestroy)).subscribe(evt => {
|
||||
if (this.readerMode === ReaderMode.Webtoon) return;
|
||||
if (this.readerMode === ReaderMode.LeftRight && this.generalSettingsForm.get('fittingOption')?.value === FITTING_OPTION.HEIGHT) {
|
||||
this.rightPaginationOffset = (this.readingArea.nativeElement.scrollLeft) * -1;
|
||||
return;
|
||||
}
|
||||
this.rightPaginationOffset = 0;
|
||||
});
|
||||
this.getWindowDimensions();
|
||||
|
||||
if (this.canvas) {
|
||||
this.ctx = this.canvas.nativeElement.getContext('2d', { alpha: false });
|
||||
this.canvasImage.onload = () => this.renderPage();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -562,7 +584,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
forkJoin({
|
||||
|
@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MangaReaderComponent } from './manga-reader.component';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { NgbButtonsModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { MangaReaderRoutingModule } from './manga-reader.router.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { NgxSliderModule } from '@angular-slider/ngx-slider';
|
||||
@ -19,7 +19,6 @@ import { ReaderSharedModule } from '../reader-shared/reader-shared.module';
|
||||
MangaReaderRoutingModule,
|
||||
ReactiveFormsModule,
|
||||
|
||||
NgbButtonsModule,
|
||||
NgbDropdownModule,
|
||||
NgxSliderModule,
|
||||
SharedModule,
|
||||
|
@ -73,12 +73,12 @@
|
||||
<div class="card-container row g-0">
|
||||
<ng-container *ngFor="let volume of volumes; let idx = index; trackBy: trackByVolumeIdentity">
|
||||
<app-card-item class="col-auto mt-2 mb-2" *ngIf="volume.number != 0" [entity]="volume" [title]="volume.name" (click)="openVolume(volume)"
|
||||
[imageUrl]="imageService.getVolumeCoverImage(volume.id) + '&offset=' + coverImageOffset"
|
||||
[imageUrl]="imageService.getVolumeCoverImage(volume.id)"
|
||||
[read]="volume.pagesRead" [total]="volume.pages" [actions]="volumeActions" (selection)="bulkSelectionService.handleCardSelection('volume', idx, volumes.length, $event)" [selected]="bulkSelectionService.isCardSelected('volume', idx)" [allowSelection]="true"></app-card-item>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let chapter of storyChapters; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-card-item class="col-auto mt-2 mb-2" *ngIf="!chapter.isSpecial" [entity]="chapter" [title]="chapter.title" (click)="openChapter(chapter)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(chapter.id) + '&offset=' + coverImageOffset"
|
||||
[imageUrl]="imageService.getChapterCoverImage(chapter.id)"
|
||||
[read]="chapter.pagesRead" [total]="chapter.pages" [actions]="chapterActions" (selection)="bulkSelectionService.handleCardSelection('chapter', idx, storyChapters.length, $event)" [selected]="bulkSelectionService.isCardSelected('chapter', idx)" [allowSelection]="true"></app-card-item>
|
||||
</ng-container>
|
||||
</div>
|
||||
@ -90,7 +90,7 @@
|
||||
<div class="card-container row g-0">
|
||||
<ng-container *ngFor="let item of volumes; let idx = index; trackBy: trackByVolumeIdentity">
|
||||
<app-card-item class="col-auto mt-2 mb-2" [entity]="item" [title]="item.name" (click)="openVolume(item)"
|
||||
[imageUrl]="imageService.getVolumeCoverImage(item.id) + '&offset=' + coverImageOffset"
|
||||
[imageUrl]="imageService.getVolumeCoverImage(item.id)"
|
||||
[read]="item.pagesRead" [total]="item.pages" [actions]="volumeActions"
|
||||
(selection)="bulkSelectionService.handleCardSelection('volume', idx, volumes.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('volume', idx)" [allowSelection]="true">
|
||||
@ -105,7 +105,7 @@
|
||||
<div class="card-container row g-0">
|
||||
<ng-container *ngFor="let item of chapters; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<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) + '&offset=' + coverImageOffset"
|
||||
[imageUrl]="imageService.getChapterCoverImage(item.id)"
|
||||
[read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions"
|
||||
(selection)="bulkSelectionService.handleCardSelection('chapter', idx, chapters.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('chapter', idx)" [allowSelection]="true"></app-card-item>
|
||||
|
@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NgbModal, NgbNavChangeEvent, NgbOffcanvas } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { forkJoin, Subject } from 'rxjs';
|
||||
import { finalize, take, takeUntil, takeWhile } from 'rxjs/operators';
|
||||
import { finalize, mergeMap, take, takeUntil, takeWhile } from 'rxjs/operators';
|
||||
import { BulkSelectionService } from '../cards/bulk-selection.service';
|
||||
import { CardDetailsModalComponent } from '../cards/_modals/card-details-modal/card-details-modal.component';
|
||||
import { EditSeriesModalComponent } from '../cards/_modals/edit-series-modal/edit-series-modal.component';
|
||||
@ -96,11 +96,6 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
seriesImage: string = '';
|
||||
downloadInProgress: boolean = false;
|
||||
|
||||
/**
|
||||
* Tricks the cover images for volume/chapter cards to update after we update one of them
|
||||
*/
|
||||
coverImageOffset: number = 0;
|
||||
|
||||
/**
|
||||
* If an action is currently being done, don't let the user kick off another action
|
||||
*/
|
||||
@ -357,8 +352,6 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
loadSeries(seriesId: number) {
|
||||
this.coverImageOffset = 0;
|
||||
|
||||
this.seriesService.getMetadata(seriesId).subscribe(metadata => this.seriesMetadata = metadata);
|
||||
this.readingListService.getReadingListsForSeries(seriesId).subscribe(lists => {
|
||||
this.readingLists = lists;
|
||||
@ -567,11 +560,6 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
drawerRef.componentInstance.parentName = this.series?.name;
|
||||
drawerRef.componentInstance.seriesId = this.series?.id;
|
||||
drawerRef.componentInstance.libraryId = this.series?.libraryId;
|
||||
drawerRef.closed.subscribe((result: {coverImageUpdate: boolean}) => {
|
||||
if (result.coverImageUpdate) {
|
||||
this.coverImageOffset += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openEditSeriesModal() {
|
||||
|
@ -6,7 +6,7 @@
|
||||
<div class="row g-0 mb-4 mt-3">
|
||||
<ng-container *ngIf="seriesMetadata.ageRating">
|
||||
<div class="col-lg-1 col-md-4 col-sm-4 col-4 mb-3">
|
||||
<app-icon-and-title [clickable]="true" fontClasses="fas fa-eye" (click)="goTo(FilterQueryParam.AgeRating, seriesMetadata.ageRating)" title="Age Rating">
|
||||
<app-icon-and-title label="Age Rating" [clickable]="true" fontClasses="fas fa-eye" (click)="goTo(FilterQueryParam.AgeRating, seriesMetadata.ageRating)" title="Age Rating">
|
||||
{{metadataService.getAgeRating(this.seriesMetadata.ageRating) | async}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
@ -15,7 +15,7 @@
|
||||
<ng-container *ngIf="series">
|
||||
<ng-container *ngIf="seriesMetadata.releaseYear > 0">
|
||||
<div class="col-lg-1 col-md-4 col-sm-4 col-4 mb-3">
|
||||
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-calendar" title="Release Year">
|
||||
<app-icon-and-title label="Release Year" [clickable]="false" fontClasses="fa-regular fa-calendar" title="Release Year">
|
||||
{{seriesMetadata.releaseYear}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
<ng-container *ngIf="seriesMetadata.language !== null">
|
||||
<div class="col-lg-1 col-md-4 col-sm-4 col-4 mb-3">
|
||||
<app-icon-and-title [clickable]="true" fontClasses="fas fa-language" (click)="goTo(FilterQueryParam.Languages, seriesMetadata.language)" title="Language">
|
||||
<app-icon-and-title label="Language" [clickable]="true" fontClasses="fas fa-language" (click)="goTo(FilterQueryParam.Languages, seriesMetadata.language)" title="Language">
|
||||
{{seriesMetadata.language | defaultValue:'en' | languageName | async}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
@ -33,16 +33,18 @@
|
||||
|
||||
<ng-container>
|
||||
<div class="d-none d-md-block col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
|
||||
<app-icon-and-title [clickable]="true" fontClasses="fa-solid fa-hourglass-empty" (click)="goTo(FilterQueryParam.PublicationStatus, seriesMetadata.publicationStatus)" title="Publication Status ({{seriesMetadata.maxCount}} / {{seriesMetadata.totalCount}})">
|
||||
{{seriesMetadata.publicationStatus | publicationStatus}}
|
||||
</app-icon-and-title>
|
||||
<ng-container *ngIf="seriesMetadata.publicationStatus | publicationStatus as pubStatus">
|
||||
<app-icon-and-title label="Publication" [clickable]="true" fontClasses="fa-solid fa-hourglass-{{pubStatus === 'Ongoing' ? 'empty' : 'end'}}" (click)="goTo(FilterQueryParam.PublicationStatus, seriesMetadata.publicationStatus)" title="Publication Status ({{seriesMetadata.maxCount}} / {{seriesMetadata.totalCount}})">
|
||||
{{pubStatus}}
|
||||
</app-icon-and-title>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="vr m-2 d-none d-lg-block"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="d-none d-md-block col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
|
||||
<app-icon-and-title [clickable]="true" [fontClasses]="'fa ' + utilityService.mangaFormatIcon(series.format)" (click)="goTo(FilterQueryParam.Format, series.format)" title="Format">
|
||||
<app-icon-and-title label="Format" [clickable]="true" [fontClasses]="'fa ' + utilityService.mangaFormatIcon(series.format)" (click)="goTo(FilterQueryParam.Format, series.format)" title="Format">
|
||||
{{utilityService.mangaFormat(series.format)}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
@ -51,7 +53,7 @@
|
||||
|
||||
<ng-container *ngIf="series.latestReadDate && series.latestReadDate !== '' && (series.latestReadDate | date: 'shortDate') !== '1/1/01'">
|
||||
<div class="d-none d-md-block col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
|
||||
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-clock" title="Last Read">
|
||||
<app-icon-and-title label="Last Read" [clickable]="false" fontClasses="fa-regular fa-clock" title="Last Read">
|
||||
{{series.latestReadDate | date:'shortDate'}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
@ -62,7 +64,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 [clickable]="false" fontClasses="fa-solid fa-book-open">
|
||||
<app-icon-and-title label="Word Count" [clickable]="false" fontClasses="fa-solid fa-book-open">
|
||||
{{series.wordCount | compactNumber}} Words
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
@ -72,7 +74,7 @@
|
||||
</ng-container>
|
||||
<ng-template #showPages>
|
||||
<div class="d-none d-md-block col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
|
||||
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-file-lines">
|
||||
<app-icon-and-title label="Print Length" [clickable]="false" fontClasses="fa-regular fa-file-lines">
|
||||
{{series.pages | number:''}} Pages
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
@ -83,7 +85,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 [clickable]="false" fontClasses="fa-regular fa-clock">
|
||||
<app-icon-and-title label="Read Time" [clickable]="false" fontClasses="fa-regular fa-clock">
|
||||
{{readingTime.minHours}}{{readingTime.maxHours !== readingTime.minHours ? ('-' + readingTime.maxHours) : ''}} Hour{{readingTime.minHours > 1 ? 's' : ''}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
@ -93,7 +95,7 @@
|
||||
<ng-container *ngIf="readingTimeLeft.hasProgress && readingTimeLeft.avgHours !== 0 ">
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
<div class="col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
|
||||
<app-icon-and-title [clickable]="false" fontClasses="fa-solid fa-clock">
|
||||
<app-icon-and-title label="Read Left" [clickable]="false" fontClasses="fa-solid fa-clock">
|
||||
~{{readingTimeLeft.avgHours}} Hour{{readingTimeLeft.avgHours > 1 ? 's' : ''}} Left
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
|
@ -1,5 +1,8 @@
|
||||
<div class="d-flex justify-content-center align-self-center align-items-center icon-and-title" [ngClass]="{'clickable': clickable}" [attr.role]="clickable ? 'button' : ''"
|
||||
(click)="handleClick($event)">
|
||||
<div class="label" *ngIf="label && label.length > 0">
|
||||
{{label}}
|
||||
</div>
|
||||
<i class="{{fontClasses}} mx-auto icon" aria-hidden="true" [title]="title"></i>
|
||||
|
||||
<div class="text">
|
||||
|
@ -12,4 +12,10 @@
|
||||
.text {
|
||||
padding-top: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-bottom: 5px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
@ -11,6 +11,7 @@ export class IconAndTitleComponent implements OnInit {
|
||||
*/
|
||||
@Input() clickable: boolean = true;
|
||||
@Input() title: string = '';
|
||||
@Input() label: string = '';
|
||||
/**
|
||||
* Font classes used to display font
|
||||
*/
|
||||
|
@ -12,11 +12,11 @@
|
||||
<app-side-nav-item icon="fa-list-ol" title="Reading Lists" link="/lists/"></app-side-nav-item>
|
||||
<app-side-nav-item icon="fa-bookmark" title="Bookmarks" link="/bookmarks/"></app-side-nav-item>
|
||||
<app-side-nav-item icon="fa-regular fa-rectangle-list" title="All Series" link="/all-series/"></app-side-nav-item>
|
||||
<div class="mb-2 mt-3 ms-2 me-2" *ngIf="libraries.length > 10">
|
||||
<div class="mb-2 mt-3 ms-2 me-2" *ngIf="libraries.length > 10 && !(navService?.sideNavCollapsed$ | async)">
|
||||
<label for="filter" class="form-label visually-hidden">Filter</label>
|
||||
<div class="input-group">
|
||||
<div class="form-group">
|
||||
<input id="filter" autocomplete="off" class="form-control" [(ngModel)]="filterQuery" type="text" aria-describedby="reset-input">
|
||||
<button class="btn btn-outline-secondary" type="button" id="reset-input" (click)="filterQuery = '';">Clear</button>
|
||||
<button type="button" aria-label="Clear" class="btn-close" id="reset-input" (click)="filterQuery = '';"></button>
|
||||
</div>
|
||||
</div>
|
||||
<app-side-nav-item *ngFor="let library of libraries | filter: filterLibrary" [link]="'/library/' + library.id + '/'"
|
||||
|
@ -71,3 +71,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
margin-top: -28px;
|
||||
font-size: 0.8rem;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
}
|
@ -65,7 +65,9 @@ label, select, .clickable {
|
||||
|
||||
// Needed for fullscreen
|
||||
app-root {
|
||||
background-color: inherit;
|
||||
background-color: transparent;
|
||||
scrollbar-width: 14px;
|
||||
scrollbar-color: var(--primary-color-scrollbar);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
|
Loading…
x
Reference in New Issue
Block a user