Release Shakeout (#1266)

* Fixed an issue with fit to screen where spread images would fail to generate a paging area long enough.

* Fixed pagination placement on original scaling

* Fixed an issue with webtoon reader not reporting scroll events due to a fix from manga reader.

* Fixing select on black book-reader theme

* Fixing canvas split centering

* Fixed a bug with white mode in book reader not rendering correctly. When bookmarking new pages after previously have viewing bookmarks for a series, ensure we clear out the temp cache else your new files wont be visible till next day.

* Use grid on related tab

* Clear bookmarks was not hooked up. Bulk add to collection didn't have label hidden

* Fixed bug where filter might stay open between pages

* Fixed typo on relationship for Adaptation

* Contains was missing from series relation modal

* Tweaked some methods and wording on reading list page

* Cleaned up the phrasing when we abort a scan.

* Fixed issue where typeahead wasn't reopening and it wasn't filtering selected options

* Fixed some typeahead bugs and decreased interval for docker health check

* Cleaned up and fixed some logic with receiving cover image update events

Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
Joseph Milazzo 2022-05-20 17:50:17 -05:00 committed by GitHub
parent 49d8a7c6ca
commit a062341564
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 90 additions and 144 deletions

View File

@ -1,25 +0,0 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

View File

@ -150,8 +150,7 @@ namespace API.Controllers
{ {
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
var totalPages = await _cacheService.CacheBookmarkForSeries(user.Id, seriesId); var totalPages = await _cacheService.CacheBookmarkForSeries(user.Id, seriesId);
// TODO: Change Includes to None from LinkedSeries branch var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.None);
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
return Ok(new BookmarkInfoDto() return Ok(new BookmarkInfoDto()
{ {
@ -172,11 +171,6 @@ namespace API.Controllers
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress"); if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
// var series = new List<SeriesDto>()
// {await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(markReadDto.SeriesId, user.Id)};
// await _unitOfWork.SeriesRepository.AddSeriesModifiers(user.Id, series);
// await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
// MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName, markReadDto.SeriesId, series[0], series[0].Pages));
return Ok(); return Ok();
} }
@ -194,16 +188,6 @@ namespace API.Controllers
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress"); if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
// Should I do this for every chapter? Maybe in a background task?
// foreach (var chapterId in await
// _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new List<int>() {markReadDto.SeriesId}))
// {
// await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
// MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName, chapterId, MessageFactoryEntityTypes.Chapter, 0));
// }
//
// await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
// MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName, markReadDto.SeriesId, MessageFactoryEntityTypes.Series, 0));
return Ok(); return Ok();
} }
@ -580,6 +564,7 @@ namespace API.Controllers
if (await _bookmarkService.BookmarkPage(user, bookmarkDto, path)) if (await _bookmarkService.BookmarkPage(user, bookmarkDto, path))
{ {
BackgroundJob.Enqueue(() => _cacheService.CleanupBookmarkCache(bookmarkDto.SeriesId));
return Ok(); return Ok();
} }
@ -599,6 +584,7 @@ namespace API.Controllers
if (await _bookmarkService.RemoveBookmarkPage(user, bookmarkDto)) if (await _bookmarkService.RemoveBookmarkPage(user, bookmarkDto))
{ {
BackgroundJob.Enqueue(() => _cacheService.CleanupBookmarkCache(bookmarkDto.SeriesId));
return Ok(); return Ok();
} }

View File

@ -102,6 +102,8 @@ namespace API.Controllers
if (_unitOfWork.HasChanges()) if (_unitOfWork.HasChanges())
{ {
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
MessageFactory.CoverUpdateEvent(series.Id, MessageFactoryEntityTypes.Series), false);
await _unitOfWork.CommitAsync(); await _unitOfWork.CommitAsync();
return Ok(); return Ok();
} }
@ -245,6 +247,10 @@ namespace API.Controllers
if (_unitOfWork.HasChanges()) if (_unitOfWork.HasChanges())
{ {
await _unitOfWork.CommitAsync(); await _unitOfWork.CommitAsync();
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
MessageFactory.CoverUpdateEvent(chapter.VolumeId, MessageFactoryEntityTypes.Volume), false);
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
MessageFactory.CoverUpdateEvent(chapter.Id, MessageFactoryEntityTypes.Chapter), false);
return Ok(); return Ok();
} }

View File

@ -1,40 +0,0 @@
#This Dockerfile pulls the latest git commit and builds Kavita from source
FROM mcr.microsoft.com/dotnet/sdk:6.0-focal AS builder
ENV DEBIAN_FRONTEND=noninteractive
ARG TARGETPLATFORM
#Installs nodejs and npm
RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
#Builds app based on platform
COPY build_target.sh /build_target.sh
RUN /build_target.sh
#Production image
FROM ubuntu:focal
#Move the output files to where they need to be
COPY --from=builder /Projects/Kavita/_output/build/Kavita /kavita
#Installs program dependencies
RUN apt-get update \
&& apt-get install -y libicu-dev libssl1.1 pwgen \
&& rm -rf /var/lib/apt/lists/*
#Creates the manga storage directory
RUN mkdir /manga /kavita/data
RUN cp /kavita/appsettings.Development.json /kavita/appsettings.json \
&& sed -i 's/Data source=kavita.db/Data source=data\/kavita.db/g' /kavita/appsettings.json
COPY entrypoint.sh /entrypoint.sh
EXPOSE 5000
WORKDIR /kavita
ENTRYPOINT ["/bin/bash"]
CMD ["/entrypoint.sh"]

View File

@ -32,6 +32,7 @@ namespace API.Services
string GetCachedEpubFile(int chapterId, Chapter chapter); string GetCachedEpubFile(int chapterId, Chapter chapter);
public void ExtractChapterFiles(string extractPath, IReadOnlyList<MangaFile> files); public void ExtractChapterFiles(string extractPath, IReadOnlyList<MangaFile> files);
Task<int> CacheBookmarkForSeries(int userId, int seriesId); Task<int> CacheBookmarkForSeries(int userId, int seriesId);
void CleanupBookmarkCache(int bookmarkDtoSeriesId);
} }
public class CacheService : ICacheService public class CacheService : ICacheService
{ {
@ -240,5 +241,17 @@ namespace API.Services
_directoryService.Flatten(destDirectory); _directoryService.Flatten(destDirectory);
return files.Count; return files.Count;
} }
/// <summary>
/// Clears a cached bookmarks for a series id folder
/// </summary>
/// <param name="seriesId"></param>
public void CleanupBookmarkCache(int seriesId)
{
var destDirectory = _directoryService.FileSystem.Path.Join(_directoryService.CacheDirectory, seriesId + "_bookmarks");
if (!_directoryService.Exists(destDirectory)) return;
_directoryService.ClearAndDeleteDirectory(destDirectory);
}
} }
} }

View File

@ -182,7 +182,7 @@ namespace API.Services.Tasks
/// </summary> /// </summary>
public Task CleanupBookmarks() public Task CleanupBookmarks()
{ {
// This is disabled for now while we test and validate a new method of deleting bookmarks // TODO: This is disabled for now while we test and validate a new method of deleting bookmarks
return Task.CompletedTask; return Task.CompletedTask;
// Search all files in bookmarks/ except bookmark files and delete those // Search all files in bookmarks/ except bookmark files and delete those
// var bookmarkDirectory = // var bookmarkDirectory =

View File

@ -215,12 +215,12 @@ public class ScannerService : IScannerService
// That way logging and UI informing is all in one place with full context // That way logging and UI informing is all in one place with full context
_logger.LogError("Some of the root folders for the library are empty. " + _logger.LogError("Some of the root folders for the library are empty. " +
"Either your mount has been disconnected or you are trying to delete all series in the library. " + "Either your mount has been disconnected or you are trying to delete all series in the library. " +
"Scan will be aborted. " + "Scan has be aborted. " +
"Check that your mount is connected or change the library's root folder and rescan"); "Check that your mount is connected or change the library's root folder and rescan");
await _eventHub.SendMessageAsync(MessageFactory.Error, MessageFactory.ErrorEvent( $"Some of the root folders for the library, {libraryName}, are empty.", await _eventHub.SendMessageAsync(MessageFactory.Error, MessageFactory.ErrorEvent( $"Some of the root folders for the library, {libraryName}, are empty.",
"Either your mount has been disconnected or you are trying to delete all series in the library. " + "Either your mount has been disconnected or you are trying to delete all series in the library. " +
"Scan will be aborted. " + "Scan has be aborted. " +
"Check that your mount is connected or change the library's root folder and rescan")); "Check that your mount is connected or change the library's root folder and rescan"));
return false; return false;

View File

@ -29,7 +29,7 @@ EXPOSE 5000
WORKDIR /kavita WORKDIR /kavita
HEALTHCHECK --interval=300s --timeout=15s --start-period=30s --retries=3 CMD curl --fail http://localhost:5000 || exit 1 HEALTHCHECK --interval=30s --timeout=15s --start-period=30s --retries=3 CMD curl --fail http://localhost:5000 || exit 1
ENTRYPOINT [ "/bin/bash" ] ENTRYPOINT [ "/bin/bash" ]
CMD ["/entrypoint.sh"] CMD ["/entrypoint.sh"]

View File

@ -2,6 +2,7 @@ import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { UtilityService } from '../shared/_services/utility.service';
import { PaginatedResult } from '../_models/pagination'; import { PaginatedResult } from '../_models/pagination';
import { ReadingList, ReadingListItem } from '../_models/reading-list'; import { ReadingList, ReadingListItem } from '../_models/reading-list';
import { ActionItem } from './action-factory.service'; import { ActionItem } from './action-factory.service';
@ -13,7 +14,7 @@ export class ReadingListService {
baseUrl = environment.apiUrl; baseUrl = environment.apiUrl;
constructor(private httpClient: HttpClient) { } constructor(private httpClient: HttpClient, private utilityService: UtilityService) { }
getReadingList(readingListId: number) { getReadingList(readingListId: number) {
return this.httpClient.get<ReadingList>(this.baseUrl + 'readinglist?readingListId=' + readingListId); return this.httpClient.get<ReadingList>(this.baseUrl + 'readinglist?readingListId=' + readingListId);
@ -21,11 +22,11 @@ export class ReadingListService {
getReadingLists(includePromoted: boolean = true, pageNum?: number, itemsPerPage?: number) { getReadingLists(includePromoted: boolean = true, pageNum?: number, itemsPerPage?: number) {
let params = new HttpParams(); let params = new HttpParams();
params = this._addPaginationIfExists(params, pageNum, itemsPerPage); params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
return this.httpClient.post<PaginatedResult<ReadingList[]>>(this.baseUrl + 'readinglist/lists?includePromoted=' + includePromoted, {}, {observe: 'response', params}).pipe( return this.httpClient.post<PaginatedResult<ReadingList[]>>(this.baseUrl + 'readinglist/lists?includePromoted=' + includePromoted, {}, {observe: 'response', params}).pipe(
map((response: any) => { map((response: any) => {
return this._cachePaginatedResults(response, new PaginatedResult<ReadingList[]>()); return this.utilityService.createPaginatedResult(response, new PaginatedResult<ReadingList[]>());
}) })
); );
} }
@ -86,29 +87,4 @@ export class ReadingListService {
if (readingList?.promoted && !isAdmin) return false; if (readingList?.promoted && !isAdmin) return false;
return true; return true;
} }
_addPaginationIfExists(params: HttpParams, pageNum?: number, itemsPerPage?: number) {
// TODO: Move to utility service
if (pageNum !== null && pageNum !== undefined && itemsPerPage !== null && itemsPerPage !== undefined) {
params = params.append('pageNumber', pageNum + '');
params = params.append('pageSize', itemsPerPage + '');
}
return params;
}
_cachePaginatedResults(response: any, paginatedVariable: PaginatedResult<any[]>) {
// TODO: Move to utility service
if (response.body === null) {
paginatedVariable.result = [];
} else {
paginatedVariable.result = response.body;
}
const pageHeader = response.headers.get('Pagination');
if (pageHeader !== null) {
paginatedVariable.pagination = JSON.parse(pageHeader);
}
return paginatedVariable;
}
} }

View File

@ -18,6 +18,7 @@ export class ToggleService {
.pipe(filter(event => event instanceof NavigationStart)) .pipe(filter(event => event instanceof NavigationStart))
.subscribe((event) => { .subscribe((event) => {
this.toggleState = false; this.toggleState = false;
this.toggleStateSource.next(this.toggleState);
}); });
this.toggleStateSource.next(false); this.toggleStateSource.next(false);
} }
@ -26,7 +27,6 @@ export class ToggleService {
this.toggleState = !this.toggleState; this.toggleState = !this.toggleState;
this.toggleStateSource.pipe(take(1)).subscribe(state => { this.toggleStateSource.pipe(take(1)).subscribe(state => {
this.toggleState = !state; this.toggleState = !state;
console.log('Toggle: ', this.toggleState)
this.toggleStateSource.next(this.toggleState); this.toggleStateSource.next(this.toggleState);
}); });

View File

@ -47,6 +47,15 @@ export const BookBlackTheme = `
--btn-disabled-text-color: white; --btn-disabled-text-color: white;
--btn-disabled-border-color: #6c757d; --btn-disabled-border-color: #6c757d;
/* Inputs */
--input-bg-color: #343a40;
--input-bg-readonly-color: #434648;
--input-focused-border-color: #ccc;
--input-text-color: #fff;
--input-placeholder-color: #aeaeae;
--input-border-color: #ccc;
--input-focus-boxshadow-color: rgb(255 255 255 / 50%);
/* Nav (Tabs) */ /* Nav (Tabs) */
--nav-tab-border-color: rgba(44, 118, 88, 0.7); --nav-tab-border-color: rgba(44, 118, 88, 0.7);
--nav-tab-text-color: var(--body-text-color); --nav-tab-text-color: var(--body-text-color);

View File

@ -94,7 +94,8 @@
</div> </div>
<!-- TODO: move this inline style into a class --> <!-- TODO: move this inline style into a class -->
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;"> <div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
<label id="fullscreen" class="form-label">Fullscreen&nbsp;<i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="fullscreenTooltip" role="button" tabindex="1" aria-describedby="fullscreen-help"></i></label> <label id="fullscreen" class="form-label">Fullscreen&nbsp;<i class="fa fa-info-circle" aria-hidden="true" placement="top"
[ngbTooltip]="fullscreenTooltip" role="button" tabindex="1" aria-describedby="fullscreen-help"></i></label>
<ng-template #fullscreenTooltip>Put reader in fullscreen mode</ng-template> <ng-template #fullscreenTooltip>Put reader in fullscreen mode</ng-template>
<span class="visually-hidden" id="fullscreen-help"> <span class="visually-hidden" id="fullscreen-help">
<ng-container [ngTemplateOutlet]="fullscreenTooltip"></ng-container> <ng-container [ngTemplateOutlet]="fullscreenTooltip"></ng-container>

View File

@ -1,6 +1,12 @@
.controls { .controls {
margin: 0.25rem 0 0.25rem; margin: 0.25rem 0 0.25rem;
.form-select {
option{
background-color: var(--input-bg-color);
}
}
.form-label { .form-label {
margin: 0; margin: 0;
} }

View File

@ -284,6 +284,7 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
} }
toggleFullscreen() { toggleFullscreen() {
this.isFullscreen = !this.isFullscreen;
this.fullscreen.emit(); this.fullscreen.emit();
} }
} }

View File

@ -64,9 +64,7 @@ export class BookmarksComponent implements OnInit, OnDestroy {
async handleAction(action: Action, series: Series) { async handleAction(action: Action, series: Series) {
switch (action) { switch (action) {
case(Action.Delete): case(Action.Delete):
if (!await this.confirmService.confirm('Are you sure you want to clear all bookmarks for ' + series.name + '? This cannot be undone.')) { this.clearBookmarks(series);
break;
}
break; break;
case(Action.DownloadBookmark): case(Action.DownloadBookmark):
this.downloadBookmarks(series); this.downloadBookmarks(series);

View File

@ -30,7 +30,7 @@
<div style="width: 100%;"> <div style="width: 100%;">
<div class="d-flex"> <div class="d-flex">
<div class="col-9 col-lg-10"> <div class="col-9 col-lg-10">
<label class="visually-hidden" class="form-label" for="add-rlist">Collection</label> <label class="form-label visually-hidden" for="add-rlist">Collection</label>
<input width="100%" #title ngbAutofocus type="text" class="form-control mb-2" id="add-rlist" formControlName="title"> <input width="100%" #title ngbAutofocus type="text" class="form-control mb-2" id="add-rlist" formControlName="title">
</div> </div>
<div class="col-2"> <div class="col-2">

View File

@ -11,7 +11,3 @@
.highlight { .highlight {
color: var(--bulk-selection-highlight-text-color) !important; color: var(--bulk-selection-highlight-text-color) !important;
} }
::ng-deep button i.fa {
color: var(--bulk-selection-text-color);
}

View File

@ -55,6 +55,8 @@ export class EditSeriesRelationComponent implements OnInit, OnDestroy {
this.setupRelationRows(relations.alternativeSettings, RelationKind.AlternativeSetting); this.setupRelationRows(relations.alternativeSettings, RelationKind.AlternativeSetting);
this.setupRelationRows(relations.alternativeVersions, RelationKind.AlternativeVersion); this.setupRelationRows(relations.alternativeVersions, RelationKind.AlternativeVersion);
this.setupRelationRows(relations.doujinshis, RelationKind.Doujinshi); this.setupRelationRows(relations.doujinshis, RelationKind.Doujinshi);
this.setupRelationRows(relations.contains, RelationKind.Contains);
this.setupRelationRows(relations.parent, RelationKind.Parent);
}); });
this.libraryService.getLibraryNames().subscribe(names => { this.libraryService.getLibraryNames().subscribe(names => {

View File

@ -31,9 +31,9 @@
</div> </div>
</ng-container> </ng-container>
<div (click)="toggleMenu()" class="reading-area" [ngStyle]="{'background-color': backgroundColor}" #readingArea> <div (click)="toggleMenu()" class="reading-area" [ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : 'calc(var(--vh)*100)'}" #readingArea>
<ng-container *ngIf="readerMode !== ReaderMode.Webtoon; else webtoon"> <ng-container *ngIf="readerMode !== ReaderMode.Webtoon; else webtoon">
<div [ngClass]="{'d-none': !renderWithCanvas }"> <div class="image-container" [ngClass]="{'d-none': !renderWithCanvas }">
<canvas #content class="{{getFittingOptionClass()}}" <canvas #content class="{{getFittingOptionClass()}}"
ondragstart="return false;" onselectstart="return false;"> ondragstart="return false;" onselectstart="return false;">
</canvas> </canvas>
@ -47,7 +47,7 @@
title="Previous Page" aria-hidden="true"></i> title="Previous Page" aria-hidden="true"></i>
</div> </div>
</div> </div>
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')" [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: 25 + '%')}"> <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 *ngIf="showClickOverlay"> <div *ngIf="showClickOverlay">
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}" <i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}"
title="Next Page" aria-hidden="true"></i> title="Next Page" aria-hidden="true"></i>

View File

@ -20,7 +20,7 @@ img {
.reading-area { .reading-area {
position: relative; position: relative;
overflow: auto; overflow: auto;
height: calc(var(--vh)*100); //height: calc(var(--vh)*100); // this needs to be applied on the DOM because it breaks infinite scroller
} }
.image-container { .image-container {
@ -248,6 +248,9 @@ img {
width: 100%; width: 100%;
} }
//$pagination-bg: rgba(0, 0, 0, 0);
$pagination-bg: rgba(0, 0, 255, 0.4);
.pagination-area { .pagination-area {
cursor: pointer; cursor: pointer;
z-index: 2; z-index: 2;
@ -262,7 +265,7 @@ img {
right: 0px; right: 0px;
top: 0px; top: 0px;
width: $side-width; width: $side-width;
background: rgba(0, 0, 0, 0); background: $pagination-bg;
} }
.top { .top {
@ -270,7 +273,7 @@ img {
right: 0px; right: 0px;
top: 0px; top: 0px;
width: 100%; width: 100%;
background: rgba(0, 0, 0, 0); background: $pagination-bg;
} }
.left { .left {
@ -278,7 +281,7 @@ img {
left: 0px; left: 0px;
top: 0px; top: 0px;
width: $side-width; width: $side-width;
background: rgba(0, 0, 0, 0); background: $pagination-bg;
} }
.bottom { .bottom {
@ -286,7 +289,7 @@ img {
left: 0px; left: 0px;
bottom: 0px; bottom: 0px;
width: 100%; width: 100%;
background: rgba(0, 0, 0, 0); background: $pagination-bg;
} }
} }

View File

@ -283,11 +283,23 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return this.bookmarks.hasOwnProperty(this.pageNum); return this.bookmarks.hasOwnProperty(this.pageNum);
} }
get WindowWidth() {
return this.readingArea?.nativeElement.scrollWidth + 'px';
}
get WindowHeight() { get WindowHeight() {
return this.readingArea?.nativeElement.scrollHeight + 'px'; return this.readingArea?.nativeElement.scrollHeight + 'px';
} }
get ImageWidth() {
return this.image?.nativeElement.width + 'px';
}
get ImageHeight() { get ImageHeight() {
// If we are a cover image and implied fit to screen, then we need to take screen height rather than image height
if (this.isCoverImage()) {
return this.WindowHeight;
}
return this.image?.nativeElement.height + 'px'; return this.image?.nativeElement.height + 'px';
} }
@ -1018,7 +1030,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (!this.ctx || !this.canvas) { return; } if (!this.ctx || !this.canvas) { return; }
this.canvasImage.onload = null; this.canvasImage.onload = null;
console.log('canvasImage: ', this.canvasImage?.height);
this.setCanvasSize(); this.setCanvasSize();
@ -1055,8 +1066,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|| document.documentElement.clientHeight || document.documentElement.clientHeight
|| document.body.clientHeight; || document.body.clientHeight;
console.log(windowHeight);
const needsSplitting = this.isCoverImage(); const needsSplitting = this.isCoverImage();
let newScale = this.generalSettingsForm.get('fittingOption')?.value; let newScale = this.generalSettingsForm.get('fittingOption')?.value;
const widthRatio = windowWidth / (this.canvasImage.width / (needsSplitting ? 2 : 1)); const widthRatio = windowWidth / (this.canvasImage.width / (needsSplitting ? 2 : 1));

View File

@ -10,7 +10,7 @@ export class RelationshipPipe implements PipeTransform {
if (relationship === undefined) return ''; if (relationship === undefined) return '';
switch (relationship) { switch (relationship) {
case RelationKind.Adaptation: case RelationKind.Adaptation:
return 'Adaptaion'; return 'Adaptation';
case RelationKind.AlternativeSetting: case RelationKind.AlternativeSetting:
return 'Alternative Setting'; return 'Alternative Setting';
case RelationKind.AlternativeVersion: case RelationKind.AlternativeVersion:

View File

@ -20,7 +20,7 @@
<button class="btn btn-primary" title="Read" (click)="read()"> <button class="btn btn-primary" title="Read" (click)="read()">
<span> <span>
<i class="fa fa-book-open" aria-hidden="true"></i> <i class="fa fa-book-open" aria-hidden="true"></i>
<span class="read-btn--text">&nbsp;Read</span> <!-- IDEA: We can provide them the ability to read/continue like we do with a series --> <span class="read-btn--text">&nbsp;Read</span>
</span> </span>
</button> </button>
</div> </div>
@ -47,7 +47,7 @@
<div *ngIf="items.length === 0"> <div *ngIf="items.length === 0">
No chapters added Nothing added
</div> </div>
<app-dragable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" (itemRemove)="itemRemoved($event)" [accessibilityMode]="accessibilityMode"> <app-dragable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" (itemRemove)="itemRemoved($event)" [accessibilityMode]="accessibilityMode">

View File

@ -132,9 +132,9 @@
<li [ngbNavItem]="TabID.Related" *ngIf="hasRelations"> <li [ngbNavItem]="TabID.Related" *ngIf="hasRelations">
<a ngbNavLink>Related</a> <a ngbNavLink>Related</a>
<ng-template ngbNavContent> <ng-template ngbNavContent>
<div class="row g-0"> <div class="card-container row g-0">
<ng-container *ngFor="let item of relations; let idx = index; trackBy: trackByRelatedSeriesIdentiy"> <ng-container *ngFor="let item of relations; let idx = index; trackBy: trackByRelatedSeriesIdentiy">
<app-series-card class="col-auto" [data]="item.series" [libraryId]="item.series.libraryId" [relation]="item.relation"></app-series-card> <app-series-card class="col-auto mt-2 mb-2" [data]="item.series" [libraryId]="item.series.libraryId" [relation]="item.relation"></app-series-card>
</ng-container> </ng-container>
</div> </div>
</ng-template> </ng-template>

View File

@ -51,10 +51,13 @@ export class ImageComponent implements OnChanges, OnDestroy {
if (this.imageUrl === undefined || this.imageUrl === null || this.imageUrl === '') return; if (this.imageUrl === undefined || this.imageUrl === null || this.imageUrl === '') return;
const enityType = this.imageService.getEntityTypeFromUrl(this.imageUrl); const enityType = this.imageService.getEntityTypeFromUrl(this.imageUrl);
if (enityType === updateEvent.entityType) { if (enityType === updateEvent.entityType) {
const tokens = this.imageUrl.split('?')[1].split('&random='); const tokens = this.imageUrl.split('?')[1].split('&');
//...seriesId=123&random= //...seriesId=123&random=
const id = tokens[0].replace(enityType + 'Id=', ''); let id = tokens[0].replace(enityType + 'Id=', '');
if (id.includes('&')) {
id = id.split('&')[0];
}
if (id === (updateEvent.id + '')) { if (id === (updateEvent.id + '')) {
this.imageUrl = this.imageService.randomize(this.imageUrl); this.imageUrl = this.imageService.randomize(this.imageUrl);
} }

View File

@ -217,7 +217,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
}), }),
map(val => val.trim()), map(val => val.trim()),
auditTime(this.settings.debounce), auditTime(this.settings.debounce),
distinctUntilChanged(), // ?!: BUG Doesn't trigger the search to run when filtered array changes //distinctUntilChanged(), // ?!: BUG Doesn't trigger the search to run when filtered array changes
filter(val => { filter(val => {
// If minimum filter characters not met, do not filter // If minimum filter characters not met, do not filter
if (this.settings.minCharacters === 0) return true; if (this.settings.minCharacters === 0) return true;
@ -405,6 +405,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
openDropdown() { openDropdown() {
setTimeout(() => { setTimeout(() => {
this.typeaheadControl.setValue(this.typeaheadControl.value); this.typeaheadControl.setValue(this.typeaheadControl.value);
this.hasFocus = true;
}); });
} }
@ -454,6 +455,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
} }
updateShowAddItem(options: any[]) { updateShowAddItem(options: any[]) {
// ?! BUG This will still technicially allow you to add the same thing as a previously added item. (Code will just toggle it though)
this.showAddItem = this.settings.addIfNonExisting && this.typeaheadControl.value.trim() this.showAddItem = this.settings.addIfNonExisting && this.typeaheadControl.value.trim()
&& this.typeaheadControl.value.trim().length >= Math.max(this.settings.minCharacters, 1) && this.typeaheadControl.value.trim().length >= Math.max(this.settings.minCharacters, 1)
&& this.typeaheadControl.dirty && this.typeaheadControl.dirty