Fit Split to Screen (#769)

* Updated readme with new host information and new feature site.

* Implemented basic fit to screen splitting option for manga reader such that the reader will try to fit the whole cover on the screen via scaling it.

Updated a bunch of defaults in the preferences to give a better experience for first installs.

* Refactored the stat scheduling code slightly to clean it up and have better logging.

* Replaced @import with @use to lower css bundling.

* Changed up the defaults for the reading preferences to give a better experience. Fixed a duplicate render on automatic scaling due to emitting a valuechange with automatic scaling changing fit.

Implemented basic form of fit to screen. Still needs some tweaking and optimization.

* Update link to new feature server and update kavita homepage to use www.

* Updated the serverInfo to match backend. Tweaked some of the css for the changelog

* Added publish date for changelog

* First page works except for tablet

* I'm stumped, taking a break

* Hide the arrow for nav events

* Ensure specials in reading lists don't have their extensions visible

* Testing out removing no-connection

* Fixed a bug in infinite scroller where next chapter spacer when clicked would emit for prev chapter load. Fixed an issue where next/prev chapter loaders would execute when they shouldn't.

* Fit Split is working in all cases as of this code. New optimization is still needed.

* Fit to screen is now working well

* Updated the bookmark effect to look much better

* Updated new issue template to inform users to request features on our site.

* Removed an empty migration
This commit is contained in:
Joseph Milazzo 2021-11-18 08:55:52 -06:00 committed by GitHub
parent 199398df95
commit 3bfbd042a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 227 additions and 108 deletions

View File

@ -7,6 +7,10 @@ assignees: ''
---
**If this is a feature request, request [here](https://feats.kavitareader.com/) instead. Feature requests will be deleted from Github.**
**Describe the bug**
A clear and concise description of what the bug is.

View File

@ -1,4 +1,6 @@
namespace API.DTOs.Update
using System;
namespace API.DTOs.Update
{
/// <summary>
/// Update Notification denoting a new release available for user to update to
@ -34,5 +36,9 @@
/// Is this a pre-release
/// </summary>
public bool IsPrerelease { get; init; }
/// <summary>
/// Date of the publish
/// </summary>
public string PublishDate { get; init; }
}
}

View File

@ -16,7 +16,7 @@ namespace API.Entities
/// <summary>
/// Manga Reader Option: Which side of a split image should we show first
/// </summary>
public PageSplitOption PageSplitOption { get; set; } = PageSplitOption.SplitRightToLeft;
public PageSplitOption PageSplitOption { get; set; } = PageSplitOption.FitSplit;
/// <summary>
/// Manga Reader Option: How the manga reader should perform paging or reading of the file
/// <example>
@ -25,14 +25,15 @@ namespace API.Entities
/// </example>
/// </summary>
public ReaderMode ReaderMode { get; set; }
/// <summary>
/// Manga Reader Option: Allow the menu to close after 6 seconds without interaction
/// </summary>
public bool AutoCloseMenu { get; set; }
public bool AutoCloseMenu { get; set; } = true;
/// <summary>
/// Book Reader Option: Should the background color be dark
/// </summary>
public bool BookReaderDarkMode { get; set; } = false;
public bool BookReaderDarkMode { get; set; } = true;
/// <summary>
/// Book Reader Option: Override extra Margin
/// </summary>
@ -62,10 +63,10 @@ namespace API.Entities
/// UI Site Global Setting: Whether the UI should render in Dark mode or not.
/// </summary>
public bool SiteDarkMode { get; set; } = true;
public AppUser AppUser { get; set; }
public int AppUserId { get; set; }
}
}
}

View File

@ -4,6 +4,7 @@
{
SplitLeftToRight = 0,
SplitRightToLeft = 1,
NoSplit = 2
NoSplit = 2,
FitSplit = 3
}
}
}

View File

@ -17,6 +17,6 @@ namespace API.Interfaces
void RefreshSeriesMetadata(int libraryId, int seriesId, bool forceUpdate = false);
void ScanSeries(int libraryId, int seriesId, bool forceUpdate = false);
void CancelStatsTasks();
void RunStatCollection();
Task RunStatCollection();
}
}

View File

@ -29,7 +29,7 @@ namespace API.Services.HostedServices
// These methods will automatically check if stat collection is disabled to prevent sending any data regardless
// of when setting was changed
await taskScheduler.ScheduleStatsTasks();
taskScheduler.RunStatCollection();
await taskScheduler.RunStatCollection();
}
catch (Exception)
{

View File

@ -23,7 +23,6 @@ namespace API.Services
private readonly IStatsService _statsService;
private readonly IVersionUpdaterService _versionUpdaterService;
private const string SendDataTask = "finalize-stats";
public static BackgroundJobServer Client => new BackgroundJobServer();
private static readonly Random Rnd = new Random();
@ -89,19 +88,27 @@ namespace API.Services
}
_logger.LogDebug("Scheduling stat collection daily");
RecurringJob.AddOrUpdate(SendDataTask, () => _statsService.Send(), Cron.Daily(Rnd.Next(0, 22)), TimeZoneInfo.Local);
RecurringJob.AddOrUpdate("report-stats", () => _statsService.Send(), Cron.Daily(Rnd.Next(0, 22)), TimeZoneInfo.Local);
}
public void CancelStatsTasks()
{
_logger.LogDebug("Cancelling/Removing StatsTasks");
RecurringJob.RemoveIfExists(SendDataTask);
RecurringJob.RemoveIfExists("report-stats");
}
public void RunStatCollection()
/// <summary>
/// First time run stat collection. Executes immediately on a background thread. Does not block.
/// </summary>
public async Task RunStatCollection()
{
_logger.LogInformation("Enqueuing stat collection");
var allowStatCollection = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).AllowStatCollection;
if (!allowStatCollection)
{
_logger.LogDebug("User has opted out of stat collection, not sending stats");
return;
}
BackgroundJob.Enqueue(() => _statsService.Send());
}

View File

@ -38,6 +38,11 @@ namespace API.Services.Tasks
/// </summary>
// ReSharper disable once InconsistentNaming
public string Html_Url { get; init; }
/// <summary>
/// Date Release was Published
/// </summary>
// ReSharper disable once InconsistentNaming
public string Published_At { get; init; }
}
public class UntrustedCertClientFactory : DefaultHttpClientFactory
@ -109,7 +114,8 @@ namespace API.Services.Tasks
UpdateBody = _markdown.Transform(update.Body.Trim()),
UpdateTitle = update.Name,
UpdateUrl = update.Html_Url,
IsDocker = new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker
IsDocker = new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker,
PublishDate = update.Published_At
};
}

View File

@ -3,7 +3,7 @@
We're always looking for people to help make Kavita even better, there are a number of ways to contribute.
## Documentation ##
Setup guides, FAQ, the more information we have on the [wiki](https://github.com/Kareadita/Kavita/wiki) the better.
Setup guides, FAQ, the more information we have on the [wiki](https://wiki.kavitareader.com/) the better.
## Development ##

View File

@ -80,15 +80,15 @@ services:
**Note: Kavita is under heavy development and is being updated all the time, so the tag for current builds is `:nightly`. The `:latest` tag will be the latest stable release.**
## Feature Requests
Got a great idea? Throw it up on the FeatHub or vote on another idea. Please check the [Project Board](https://github.com/Kareadita/Kavita/projects) first for a list of planned features.
[![Feature Requests](https://feathub.com/Kareadita/Kavita?format=svg)](https://feathub.com/Kareadita/Kavita)
Got a great idea? Throw it up on our [Feature Request site](https://feats.kavitareader.com/) or vote on another idea. Please check the [Project Board](https://github.com/Kareadita/Kavita/projects) first for a list of planned features.
## Contributors
This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
<a href="https://github.com/Kareadita/Kavita/graphs/contributors"><img src="https://opencollective.com/kavita/contributors.svg?width=890&button=false" /></a>
<a href="https://github.com/Kareadita/Kavita/graphs/contributors">
<img src="https://opencollective.com/kavita/contributors.svg?width=890&button=false&avatarHeight=42" />
</a>
## Donate
@ -99,7 +99,7 @@ expenses related to Kavita. Back us through [OpenCollective](https://opencollect
Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Kavita#backer)
<img src="https://opencollective.com/Kavita/backers.svg?width=890"></a>
<img src="https://opencollective.com/kavita/backers.svg?width=890&avatarHeight=42"></a>
## Sponsors
@ -116,9 +116,6 @@ Thank you to [<img src="/Logo/jetbrains.svg" alt="" width="32"> JetBrains](http:
* [<img src="/Logo/rider.svg" alt="" width="32"> Rider](http://www.jetbrains.com/rider/)
* [<img src="/Logo/dottrace.svg" alt="" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
## Sentry
Thank you to [<img src="/Logo/sentry.svg" alt="" width="64">](https://sentry.io/welcome/) for providing us with free license to their software.
### License
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)

View File

@ -46,10 +46,10 @@ export class ErrorInterceptor implements HttpInterceptor {
}
// If we are not on no-connection, redirect there and save current url so when we refersh, we redirect back there
if (this.router.url !== '/no-connection') {
localStorage.setItem(this.urlKey, this.router.url);
this.router.navigateByUrl('/no-connection');
}
// if (this.router.url !== '/no-connection') {
// localStorage.setItem(this.urlKey, this.router.url);
// this.router.navigateByUrl('/no-connection');
// }
break;
}
return throwError(error);

View File

@ -5,4 +5,5 @@ export interface UpdateVersionEvent {
updateTitle: string;
updateUrl: string;
isDocker: boolean;
publishDate: string;
}

View File

@ -1,5 +1,18 @@
export enum PageSplitOption {
/**
* Renders the left side of the image then the right side
*/
SplitLeftToRight = 0,
/**
* Renders the right side of the image then the left side
*/
SplitRightToLeft = 1,
NoSplit = 2
/**
* Don't split and show the image in original size
*/
NoSplit = 2,
/**
* Don't split and scale the image to fit screen space
*/
FitSplit = 3
}

View File

@ -26,5 +26,5 @@ export interface Preferences {
export const readingDirections = [{text: 'Left to Right', value: ReadingDirection.LeftToRight}, {text: 'Right to Left', value: ReadingDirection.RightToLeft}];
export const scalingOptions = [{text: 'Automatic', value: ScalingOption.Automatic}, {text: 'Fit to Height', value: ScalingOption.FitToHeight}, {text: 'Fit to Width', value: ScalingOption.FitToWidth}, {text: 'Original', value: ScalingOption.Original}];
export const pageSplitOptions = [{text: 'Right to Left', value: PageSplitOption.SplitRightToLeft}, {text: 'Left to Right', value: PageSplitOption.SplitLeftToRight}, {text: 'No Split', value: PageSplitOption.NoSplit}];
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: READER_MODE.MANGA_LR}, {text: 'Up to Down', value: READER_MODE.MANGA_UD}, {text: 'Webtoon', value: READER_MODE.WEBTOON}];

View File

@ -1,8 +1,9 @@
export interface ServerInfo {
os: string;
dotNetVersion: string;
dotnetVersion: string;
runTimeVersion: string;
kavitaVersion: string;
buildBranch: string;
culture: string;
NumOfCores: number;
installId: string;
isDocker: boolean;
}

View File

@ -1,15 +1,19 @@
<ng-container *ngFor="let update of updates; let indx = index;">
<div class="changelog">
<ng-container *ngFor="let update of updates; let indx = index;">
<div class="card w-100 mb-2" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">{{update.updateTitle}}&nbsp;
<h4 class="card-title">{{update.updateTitle}}&nbsp;
<span class="badge badge-secondary" *ngIf="update.updateVersion === update.currentVersion">Installed</span>
<span class="badge badge-secondary" *ngIf="update.updateVersion > update.currentVersion">Available</span>
</h5>
</h4>
<h6 class="card-subtitle mb-2 text-muted">Published: {{update.publishDate | date: 'short'}}</h6>
<pre class="card-text update-body" [innerHtml]="update.updateBody | safeHtml"></pre>
<a *ngIf="!update.isDocker" href="{{update.updateUrl}}" class="btn btn-{{indx === 0 ? 'primary' : 'secondary'}} float-right" target="_blank">Download</a>
</div>
</div>
</ng-container>
</ng-container>
</div>
<div class="spinner-border text-secondary" *ngIf="isLoading" role="status">

View File

@ -2,4 +2,20 @@
width: 100%;
word-wrap: break-word;
white-space: pre-wrap;
}
}
::ng-deep .changelog {
h1 {
font-size: 26px;
}
p, ul {
margin-bottom: 0px;
}
}

View File

@ -33,8 +33,8 @@
<dt>Version</dt>
<dd>{{serverInfo.kavitaVersion}}</dd>
<dt>.NET Version</dt>
<dd>{{serverInfo.dotNetVersion}}</dd>
<dt>Install ID</dt>
<dd>{{serverInfo.installId}}</dd>
</dl>
</div>
@ -43,7 +43,7 @@
<div>
<div class="row">
<div class="col-4">Home page:</div>
<div class="col"><a href="https://kavitareader.com" target="_blank">kavitareader.com</a></div>
<div class="col"><a href="https://www.kavitareader.com" target="_blank">kavitareader.com</a></div>
</div>
<div class="row">
<div class="col-4">Wiki:</div>
@ -63,7 +63,6 @@
</div>
<div class="row">
<div class="col-4">Feature Requests:</div>
<div class="col"><a href="https://feathub.com/Kareadita/Kavita" target="_blank">Feathub</a><br/>
<a href="https://github.com/Kareadita/Kavita/issues" target="_blank">Github issues</a></div>
<div class="col"><a href="https://feats.kavitareader.com" target="_blank">https://feats.kavitareader.com</a><br/>
</div>
</div>

View File

@ -1,8 +1,8 @@
@import "../../../theme/colors";
@use "../../../theme/colors";
.bulk-select {
background-color: $dark-form-background-no-opacity;
border-bottom: 2px solid $primary-color;
background-color: colors.$dark-form-background-no-opacity;
border-bottom: 2px solid colors.$primary-color;
color: white;
}
@ -11,5 +11,5 @@
}
.highlight {
color: $primary-color !important;
color: colors.$primary-color !important;
}

View File

@ -1,4 +1,4 @@
@import '../../../theme/colors';
@use '../../../theme/colors';
$image-height: 230px;
$image-width: 160px;
@ -14,7 +14,7 @@ $image-width: 160px;
}
.selected {
outline: 5px solid $primary-color;
outline: 5px solid colors.$primary-color;
outline-width: medium;
outline-offset: -1px;
}
@ -22,7 +22,7 @@ $image-width: 160px;
ngx-file-drop ::ng-deep > div {
// styling for the outer drop box
width: 100%;
border: 2px solid $primary-color;
border: 2px solid colors.$primary-color;
border-radius: 5px;
height: 100px;
margin: auto;

View File

@ -28,7 +28,7 @@
<ng-container *ngFor="let item of webtoonImages | async; let index = index;">
<img src="{{item.src}}" style="display: block" class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}}" *ngIf="pageNum >= pageNum - bufferPages && pageNum <= pageNum + bufferPages" rel="nofollow" alt="image" (load)="onImageLoad($event)" id="page-{{item.page}}" [attr.page]="item.page" ondragstart="return false;" onselectstart="return false;">
</ng-container>
<div *ngIf="atBottom" class="spacer bottom" role="alert" (click)="loadPrevChapter.emit()">
<div *ngIf="atBottom" class="spacer bottom" role="alert" (click)="loadNextChapter.emit()">
<div>
<button class="btn btn-icon mx-auto">
<i class="fa fa-angle-double-down animate" aria-hidden="true"></i>

View File

@ -102,9 +102,7 @@
<div class="col-6">
<div class="form-group">
<select class="form-control" id="page-splitting" formControlName="pageSplitOption">
<option [value]="1">Right to Left</option>
<option [value]="0">Left to Right</option>
<option [value]="2">None</option>
<option *ngFor="let opt of pageSplitOptions" [value]="opt.value">{{opt.text}}</option>
</select>
</div>
</div>

View File

@ -1,4 +1,4 @@
@import '../../theme/colors';
@use '../../theme/colors';
$center-width: 50%;
$side-width: 25%;
@ -178,7 +178,7 @@ canvas {
height: 2px;
}
.custom-slider .ngx-slider .ngx-slider-selection {
background: $primary-color;
background: colors.$primary-color;
}
.custom-slider .ngx-slider .ngx-slider-pointer {
@ -186,7 +186,7 @@ canvas {
height: 16px;
top: auto; /* to remove the default positioning */
bottom: 0;
background-color: $primary-color; // #333;
background-color: colors.$primary-color; // #333;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
@ -217,7 +217,7 @@ canvas {
}
.custom-slider .ngx-slider .ngx-slider-tick.ngx-slider-selected {
background: $primary-color;
background: colors.$primary-color;
}
}
@ -237,19 +237,14 @@ canvas {
.bookmark-effect {
animation: bookmark 1s cubic-bezier(0.165, 0.84, 0.44, 1);
animation: bookmark 0.7s cubic-bezier(0.165, 0.84, 0.44, 1);
}
@keyframes bookmark {
0%, 100% {
filter: opacity(1);
border: 0px;
}
50% {
filter: opacity(0.25);
border: 5px solid colors.$primary-color;
}
}
// DEBUG
.active-image {
border: 5px solid red;
}
}

View File

@ -12,7 +12,7 @@ import { ScalingOption } from '../_models/preferences/scaling-option';
import { PageSplitOption } from '../_models/preferences/page-split-option';
import { forkJoin, ReplaySubject, Subject } from 'rxjs';
import { ToastrService } from 'ngx-toastr';
import { KEY_CODES, UtilityService } from '../shared/_services/utility.service';
import { KEY_CODES, UtilityService, Breakpoint } from '../shared/_services/utility.service';
import { CircularArray } from '../shared/data-structures/circular-array';
import { MemberService } from '../_services/member.service';
import { Stack } from '../shared/data-structures/stack';
@ -20,7 +20,7 @@ import { ChangeContext, LabelType, Options } from '@angular-slider/ngx-slider';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { ChapterInfo } from './_models/chapter-info';
import { COLOR_FILTER, FITTING_OPTION, PAGING_DIRECTION, SPLIT_PAGE_PART } from './_models/reader-enums';
import { scalingOptions } from '../_models/preferences/preferences';
import { pageSplitOptions, scalingOptions } from '../_models/preferences/preferences';
import { READER_MODE } from '../_models/preferences/reader-mode';
import { MangaFormat } from '../_models/manga-format';
import { LibraryService } from '../_services/library.service';
@ -96,12 +96,14 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
scalingOptions = scalingOptions;
readingDirection = ReadingDirection.LeftToRight;
scalingOption = ScalingOption.FitToHeight;
pageSplitOption = PageSplitOption.SplitRightToLeft;
pageSplitOption = PageSplitOption.FitSplit;
currentImageSplitPart: SPLIT_PAGE_PART = SPLIT_PAGE_PART.NO_SPLIT;
pagingDirection: PAGING_DIRECTION = PAGING_DIRECTION.FORWARD;
colorMode: COLOR_FILTER = COLOR_FILTER.NONE;
autoCloseMenu: boolean = true;
readerMode: READER_MODE = READER_MODE.MANGA_LR;
pageSplitOptions = pageSplitOptions;
isLoading = true;
@ -266,6 +268,10 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return ReadingDirection;
}
get PageSplitOption(): typeof PageSplitOption {
return PageSplitOption;
}
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService,
public readerService: ReaderService, private location: Location,
private formBuilder: FormBuilder, private navService: NavService,
@ -313,7 +319,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.generalSettingsForm = this.formBuilder.group({
autoCloseMenu: this.autoCloseMenu,
pageSplitOption: this.pageSplitOption + '',
pageSplitOption: this.pageSplitOption,
fittingOption: this.translateScalingOption(this.scalingOption)
});
@ -321,8 +327,8 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.generalSettingsForm.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe((changes: SimpleChanges) => {
this.autoCloseMenu = this.generalSettingsForm.get('autoCloseMenu')?.value;
// On change of splitting, re-render the page if the page is already split
const needsSplitting = this.canvasImage.width > this.canvasImage.height;
const needsSplitting = this.isCoverImage();
// If we need to split on a menu change, then we need to re-render.
if (needsSplitting) {
this.loadPage();
}
@ -619,15 +625,20 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
isSplitLeftToRight() {
return (this.generalSettingsForm?.get('pageSplitOption')?.value + '') === (PageSplitOption.SplitLeftToRight + '');
return parseInt(this.generalSettingsForm?.get('pageSplitOption')?.value, 10) === PageSplitOption.SplitLeftToRight;
}
/**
*
* @returns If the current model reflects no split of fit split
*/
isNoSplit() {
return (this.generalSettingsForm?.get('pageSplitOption')?.value + '') === (PageSplitOption.NoSplit + '');
const splitValue = parseInt(this.generalSettingsForm?.get('pageSplitOption')?.value, 10);
return splitValue === PageSplitOption.NoSplit || splitValue === PageSplitOption.FitSplit;
}
updateSplitPage() {
const needsSplitting = this.canvasImage.width > this.canvasImage.height;
const needsSplitting = this.isCoverImage();
if (!needsSplitting || this.isNoSplit()) {
this.currentImageSplitPart = SPLIT_PAGE_PART.NO_SPLIT;
return;
@ -739,6 +750,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
loadNextChapter() {
if (this.nextPageDisabled) { return; }
if (this.nextChapterDisabled) { return; }
this.isLoading = true;
if (this.nextChapterId === CHAPTER_ID_NOT_FETCHED || this.nextChapterId === this.chapterId) {
this.readerService.getNextChapter(this.seriesId, this.volumeId, this.chapterId, this.readingListId).pipe(take(1)).subscribe(chapterId => {
@ -752,6 +764,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
loadPrevChapter() {
if (this.prevPageDisabled) { return; }
if (this.prevChapterDisabled) { return; }
this.isLoading = true;
this.continuousChaptersStack.pop();
const prevChapter = this.continuousChaptersStack.peek();
@ -819,21 +832,23 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (needsScaling) {
this.canvas.nativeElement.width = isSafari ? 4_096 : 16_384;
this.canvas.nativeElement.height = isSafari ? 4_096 : 16_384;
} else if (this.isCoverImage()) {
//this.canvas.nativeElement.width = this.canvasImage.width / 2;
//this.canvas.nativeElement.height = this.canvasImage.height;
} else {
this.canvas.nativeElement.width = this.canvasImage.width;
this.canvas.nativeElement.height = this.canvasImage.height;
}
}
return true;
}
renderPage() {
if (this.ctx && this.canvas) {
this.canvasImage.onload = null;
if (!this.setCanvasSize()) return;
this.setCanvasSize();
const needsSplitting = this.canvasImage.width > this.canvasImage.height;
const needsSplitting = this.isCoverImage();
this.updateSplitPage();
if (needsSplitting && this.currentImageSplitPart === SPLIT_PAGE_PART.LEFT_PART) {
@ -844,31 +859,39 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.ctx.drawImage(this.canvasImage, 0, 0, this.canvasImage.width, this.canvasImage.height, -this.canvasImage.width / 2, 0, this.canvasImage.width, this.canvasImage.height);
} else {
if (!this.firstPageRendered && this.scalingOption === ScalingOption.Automatic) {
let newScale = this.generalSettingsForm.get('fittingOption')?.value;
this.updateScalingForFirstPageRender();
}
// Fit Split on a page that needs splitting
if (this.shouldRenderAsFitSplit()) {
const windowWidth = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
const windowHeight = window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;
const widthRatio = windowWidth / this.canvasImage.width;
const heightRatio = windowHeight / this.canvasImage.height;
// Given that we now have image dimensions, assuming this isn't a split image,
// Try to reset one time based on who's dimension (width/height) is smaller
if (widthRatio < heightRatio) {
newScale = FITTING_OPTION.WIDTH;
} else if (widthRatio > heightRatio) {
newScale = FITTING_OPTION.HEIGHT;
|| document.documentElement.clientHeight
|| document.body.clientHeight;
// If the user's screen is wider than the image, just pretend this is no split, as it will render nicer
this.canvas.nativeElement.width = windowWidth;
this.canvas.nativeElement.height = windowHeight;
const ratio = this.canvasImage.width / this.canvasImage.height;
let newWidth = windowWidth;
let newHeight = newWidth / ratio;
if (newHeight > windowHeight) {
newHeight = windowHeight;
newWidth = newHeight * ratio;
}
this.generalSettingsForm.get('fittingOption')?.setValue(newScale);
this.firstPageRendered = true;
// Optimization: When the screen is larger than newWidth, allow no split rendering to occur for a better fit
if (windowWidth > newWidth) {
this.ctx.drawImage(this.canvasImage, 0, 0);
} else {
this.ctx.drawImage(this.canvasImage, 0, 0, newWidth, newHeight);
}
} else {
this.ctx.drawImage(this.canvasImage, 0, 0);
}
this.ctx.drawImage(this.canvasImage, 0, 0);
}
// Reset scroll on non HEIGHT Fits
if (this.getFit() !== FITTING_OPTION.HEIGHT) {
window.scrollTo(0, 0);
@ -878,6 +901,41 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.isLoading = false;
}
updateScalingForFirstPageRender() {
const windowWidth = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
const windowHeight = window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;
const needsSplitting = this.isCoverImage();
let newScale = this.generalSettingsForm.get('fittingOption')?.value;
const widthRatio = windowWidth / (this.canvasImage.width / (needsSplitting ? 2 : 1));
const heightRatio = windowHeight / (this.canvasImage.height);
// Given that we now have image dimensions, assuming this isn't a split image,
// Try to reset one time based on who's dimension (width/height) is smaller
if (widthRatio < heightRatio) {
newScale = FITTING_OPTION.WIDTH;
} else if (widthRatio > heightRatio) {
newScale = FITTING_OPTION.HEIGHT;
}
this.firstPageRendered = true;
this.generalSettingsForm.get('fittingOption')?.setValue(newScale, {emitEvent: false});
}
isCoverImage() {
return this.canvasImage.width > this.canvasImage.height;
}
shouldRenderAsFitSplit() {
if (!this.isCoverImage() || parseInt(this.generalSettingsForm?.get('pageSplitOption')?.value, 10) !== PageSplitOption.FitSplit) return false;
return true;
}
prefetch() {
let index = 1;

View File

@ -38,6 +38,7 @@ export class NavEventsToggleComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();
this.progressEventsSource.complete();
}
ngOnInit(): void {

View File

@ -1,4 +1,4 @@
@import '~bootstrap/scss/mixins/_breakpoints.scss';
@import '~bootstrap/scss/mixins/_breakpoints.scss'; // TODO: Use @forwards for this?
$primary-color: white;
$bg-color: rgb(22, 27, 34);

View File

@ -136,7 +136,12 @@ export class ReadingListDetailComponent implements OnInit {
return 'Volume ' + this.utilityService.cleanSpecialTitle(item.chapterNumber);
}
return this.utilityService.formatChapterName(this.libraryTypes[item.libraryId], true, true) + item.chapterNumber;
let chapterNum = item.chapterNumber;
if (!item.chapterNumber.match(/^\d+$/)) {
chapterNum = this.utilityService.cleanSpecialTitle(item.chapterNumber);
}
return this.utilityService.formatChapterName(this.libraryTypes[item.libraryId], true, true) + chapterNum;
}
orderUpdated(event: IndexUpdateEvent) {

View File

@ -1,5 +1,3 @@
@import '../../../theme/colors';
$bg-color: #c9c9c9;
$bdr-color: #f2f2f2;

View File

@ -1,4 +1,4 @@
@import "../../theme/_colors.scss";
@use "../../theme/colors";
.login {
display: flex;
@ -36,7 +36,7 @@
}
.card {
background-color: $primary-color;
background-color: colors.$primary-color;
color: #fff;
cursor: pointer;
min-width: 300px;

View File

@ -1,6 +1,6 @@
// All dark style overrides should live here
@import "../../theme/colors";
@use "../../theme/colors";
.bg-dark {
color: $dark-text-color;
@ -32,6 +32,10 @@
box-shadow: inset 0px 0px 8px 1px $dark-form-background !important;
}
.text-muted {
color: #d7d7d7 !important;
}
.breadcrumb {
background-color: $dark-item-accent-bg;
@ -181,6 +185,10 @@
background-color: $dark-form-background-no-opacity;
}
.bs-popover-bottom > .arrow::after, .bs-popover-bottom > .arrow::before {
border-bottom-color: transparent;
}
}

View File

@ -1,6 +1,6 @@
// Import colors for overrides of bootstrap theme
@import './theme/_colors.scss';
@import './theme/_toastr.scss';
@import './theme/colors';
@import './theme/toastr';
// Bootstrap must be after _colors since we define the colors there
@import '~bootstrap/scss/bootstrap';