[Manga Reader] Swipe Support (#1735)

* Fixed a loading indicator that is always on

* Started to add swipe directive

* Implemented the ability to swipe to navigate pages in manga reader.

* Swipe to paginate seems to be working reliably

* Removed a bunch of junk from csproj and added a debug menu for testing on phone to smooth out experience.

* Fixed a bug where reading list detail wouldn't render the set image of the reading list.

* Added some instructions and code to allow connecting to dev instance easier.

* Fixed up paging with keyboard where to ensure that when we hit the end of the scroll, we don't go to the next page instantly, but rather make the user press the key once more.

* Fixed reading list image not properly renderering on reading list detail page.

* Solved the swiping bug, need to play with threshold again.

* Swipe is now working. Need to decide if I'm going to support reversing the direction with reading direction.

* Hooked up swipe with reading direction code

* Cleaned up some direction code to align to a new enum

* Feature complete
This commit is contained in:
Joe Milazzo 2023-01-09 08:48:18 -06:00 committed by GitHub
parent e90ea4758a
commit 45b854e1d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 333 additions and 269 deletions

View File

@ -7,6 +7,7 @@
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<TieredPGO>true</TieredPGO>
<TieredCompilation>true</TieredCompilation>
<ApplicationIcon>../favicon.ico</ApplicationIcon>
</PropertyGroup>
<Target Name="PostBuild" AfterTargets="Build" Condition=" '$(Configuration)' == 'Debug' ">
@ -116,6 +117,7 @@
<None Remove="kavita.log" />
<None Remove="kavita.db" />
<None Remove="covers\**" />
<None Remove="wwwroot\**" />
</ItemGroup>
<ItemGroup>
@ -125,6 +127,7 @@
<Compile Remove="logs\**" />
<Compile Remove="temp\**" />
<Compile Remove="covers\**" />
<Compile Remove="wwwroot\**" />
</ItemGroup>
<ItemGroup>
@ -139,6 +142,7 @@
<EmbeddedResource Remove="config\logs\**" />
<EmbeddedResource Remove="config\temp\**" />
<EmbeddedResource Remove="config\stats\**" />
<EmbeddedResource Remove="wwwroot\**" />
</ItemGroup>
<ItemGroup>
@ -164,178 +168,12 @@
</Content>
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="logs\kavita.json" />
<_ContentIncludedByDefault Remove="wwwroot\3rdpartylicenses.txt" />
<_ContentIncludedByDefault Remove="wwwroot\6.d9925ea83359bb4c7278.js" />
<_ContentIncludedByDefault Remove="wwwroot\6.d9925ea83359bb4c7278.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\7.860cdd6fd9d758e6c210.js" />
<_ContentIncludedByDefault Remove="wwwroot\7.860cdd6fd9d758e6c210.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\8.028f6737a2f0621d40c7.js" />
<_ContentIncludedByDefault Remove="wwwroot\8.028f6737a2f0621d40c7.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\EBGarmond\EBGaramond-Italic-VariableFont_wght.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\EBGarmond\EBGaramond-VariableFont_wght.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\EBGarmond\OFL.txt" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Black.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-BlackItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Bold.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-BoldItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraBold.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraBoldItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraLight.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraLightItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Italic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Light.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-LightItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Medium.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-MediumItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Regular.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-SemiBold.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-SemiBoldItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Thin.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ThinItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\OFL.txt" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Black.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-BlackItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Bold.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-BoldItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Italic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Light.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-LightItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Regular.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Thin.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-ThinItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\OFL.txt" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\LibreBaskerville-Bold.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\LibreBaskerville-Italic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\LibreBaskerville-Regular.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\OFL.txt" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\LibreCaslonText-Bold.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\LibreCaslonText-Italic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\LibreCaslonText-Regular.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\OFL.txt" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Black.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-BlackItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Bold.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-BoldItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Italic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Light.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-LightItalic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Regular.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\OFL.txt" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\NanumGothic-Bold.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\NanumGothic-ExtraBold.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\NanumGothic-Regular.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\OFL.txt" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\OFL.txt" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\Oswald-VariableFont_wght.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\README.txt" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Bold.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-ExtraLight.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Light.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Medium.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Regular.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-SemiBold.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\RocknRoll_One\OFL.txt" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\RocknRoll_One\RocknRollOne-Regular.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder-min.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2-min.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2.dark-min.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2.dark.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder-min.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder.dark-min.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder.dark.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\preset-light.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\themes\dark.scss" />
<_ContentIncludedByDefault Remove="wwwroot\common.ad975892146299f80adb.js" />
<_ContentIncludedByDefault Remove="wwwroot\common.ad975892146299f80adb.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\EBGaramond-VariableFont_wght.2a1da2dbe7a28d63f8cb.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.0fea24969112a781acd2.eot" />
<_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.c967a94cfbe2b06627ff.woff2" />
<_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.dc2cbadd690e1d4b2c9c.woff" />
<_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.e33e2cf6e02cac2ccb77.svg" />
<_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.ec82f282c7f54b637098.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.06b9d19ced8d17f3d5cb.svg" />
<_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.08f9891a6f44d9546678.eot" />
<_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.1008b5226941c24f4468.woff2" />
<_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.1069ea55beaa01060302.woff" />
<_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.1495f578452eb676f730.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.10ecefc282f2761808bf.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.371dbce0dd46bd4d2033.svg" />
<_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.3a24a60e7f9c6574864a.eot" />
<_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.3ceb50e7bcafb577367c.woff2" />
<_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.46fdbd2d897f8824e63c.woff" />
<_ContentIncludedByDefault Remove="wwwroot\favicon.ico" />
<_ContentIncludedByDefault Remove="wwwroot\FiraSans-Regular.1c0bf0728b51cb9f2ddc.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\index.html" />
<_ContentIncludedByDefault Remove="wwwroot\Lato-Regular.9919edff6283018571ad.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\LibreBaskerville-Regular.a27f99ca45522bb3d56d.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\main.44f5c0973044295d8be0.js" />
<_ContentIncludedByDefault Remove="wwwroot\main.44f5c0973044295d8be0.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\Merriweather-Regular.55c73e48e04ec926ebfe.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\NanumGothic-Regular.6c84540de7730f833d6c.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\polyfills.348e08e9d0e910a15938.js" />
<_ContentIncludedByDefault Remove="wwwroot\polyfills.348e08e9d0e910a15938.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\RocknRollOne-Regular.c75da4712d1e65ed1f69.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\runtime.ea545c6916f85411478f.js" />
<_ContentIncludedByDefault Remove="wwwroot\runtime.ea545c6916f85411478f.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\styles.4bd902bb3037f36f2c64.css" />
<_ContentIncludedByDefault Remove="wwwroot\styles.4bd902bb3037f36f2c64.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\vendor.6b2a0912ae80e6fd297f.js" />
<_ContentIncludedByDefault Remove="wwwroot\vendor.6b2a0912ae80e6fd297f.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\10.b727db78581442412e9a.js" />
<_ContentIncludedByDefault Remove="wwwroot\10.b727db78581442412e9a.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\2.fcc031071e80d6837012.js" />
<_ContentIncludedByDefault Remove="wwwroot\2.fcc031071e80d6837012.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\7.c30da7d2e809fa05d1e3.js" />
<_ContentIncludedByDefault Remove="wwwroot\7.c30da7d2e809fa05d1e3.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\8.d4c77a90c95e9861656a.js" />
<_ContentIncludedByDefault Remove="wwwroot\8.d4c77a90c95e9861656a.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\9.489b177dd1a6beeb35ad.js" />
<_ContentIncludedByDefault Remove="wwwroot\9.489b177dd1a6beeb35ad.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Spartan\OFL.txt" />
<_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Spartan\Spartan-VariableFont_wght.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\assets\icons\android-chrome-192x192.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\icons\android-chrome-256x256.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\icons\apple-touch-icon.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\icons\browserconfig.xml" />
<_ContentIncludedByDefault Remove="wwwroot\assets\icons\favicon-16x16.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\icons\favicon-32x32.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\icons\favicon.ico" />
<_ContentIncludedByDefault Remove="wwwroot\assets\icons\mstile-150x150.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\image-reset-cover-min.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\image-reset-cover.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\kavita-book-cropped.png" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\login-bg.jpg" />
<_ContentIncludedByDefault Remove="wwwroot\assets\images\logo.png" />
<_ContentIncludedByDefault Remove="wwwroot\common.fbf71de364f5a1f37413.js" />
<_ContentIncludedByDefault Remove="wwwroot\common.fbf71de364f5a1f37413.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\login-bg.8860e6ff9d2a3598539c.jpg" />
<_ContentIncludedByDefault Remove="wwwroot\main.a3a1e647a39145accff3.js" />
<_ContentIncludedByDefault Remove="wwwroot\main.a3a1e647a39145accff3.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\polyfills.3dda3bf3d087e5d131ba.js" />
<_ContentIncludedByDefault Remove="wwwroot\polyfills.3dda3bf3d087e5d131ba.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\runtime.b9818dfc90f418b3f0a7.js" />
<_ContentIncludedByDefault Remove="wwwroot\runtime.b9818dfc90f418b3f0a7.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\scripts.7d1c78b2763c483bb699.js" />
<_ContentIncludedByDefault Remove="wwwroot\scripts.7d1c78b2763c483bb699.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\site.webmanifest" />
<_ContentIncludedByDefault Remove="wwwroot\Spartan-VariableFont_wght.0427aac0d980a12ae8ba.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\styles.85a58cb3e4a4b1add864.css" />
<_ContentIncludedByDefault Remove="wwwroot\styles.85a58cb3e4a4b1add864.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\vendor.54bf44a9aa720ff8881d.js" />
<_ContentIncludedByDefault Remove="wwwroot\vendor.54bf44a9aa720ff8881d.js.map" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Drawing.Common" />
</ItemGroup>
<ItemGroup>
<Folder Include="config\themes" />
</ItemGroup>
<ItemGroup>
<_DeploymentManifestIconFile Remove="favicon.ico" />
</ItemGroup>
</Project>

View File

@ -54,7 +54,7 @@ public class CollectionController : BaseApiController
[HttpGet("search")]
public async Task<IEnumerable<CollectionTagDto>> SearchTags(string queryString)
{
queryString ??= "";
queryString ??= string.Empty;
queryString = queryString.Replace(@"%", string.Empty);
if (queryString.Length == 0) return await GetAllTags();

View File

@ -37,7 +37,7 @@ public class ImageController : BaseApiController
{
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId));
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
}
@ -53,7 +53,7 @@ public class ImageController : BaseApiController
{
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.LibraryRepository.GetLibraryCoverImageAsync(libraryId));
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
}
@ -69,7 +69,7 @@ public class ImageController : BaseApiController
{
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId));
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
}
@ -85,7 +85,7 @@ public class ImageController : BaseApiController
{
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId));
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
Response.AddCacheHeader(path);
@ -103,7 +103,7 @@ public class ImageController : BaseApiController
{
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.CollectionTagRepository.GetCoverImageAsync(collectionTagId));
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
}
@ -119,7 +119,7 @@ public class ImageController : BaseApiController
{
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ReadingListRepository.GetCoverImageAsync(readingListId));
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
}
@ -143,7 +143,7 @@ public class ImageController : BaseApiController
var bookmarkDirectory =
(await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BookmarkDirectory)).Value;
var file = new FileInfo(Path.Join(bookmarkDirectory, bookmark.FileName));
var format = Path.GetExtension(file.FullName).Replace(".", "");
var format = Path.GetExtension(file.FullName).Replace(".", string.Empty);
return PhysicalFile(file.FullName, "image/" + format, Path.GetFileName(file.FullName));
}
@ -162,7 +162,7 @@ public class ImageController : BaseApiController
var path = Path.Join(_directoryService.TempDirectory, filename);
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"File does not exist");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
}

View File

@ -429,7 +429,7 @@ public class OpdsController : BaseApiController
{
return BadRequest("You must pass a query parameter");
}
query = query.Replace(@"%", "");
query = query.Replace(@"%", string.Empty);
// Get libraries user has access to
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId)).ToList();
@ -828,7 +828,7 @@ public class OpdsController : BaseApiController
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {pageNumber}");
var content = await _directoryService.ReadFileAsync(path);
var format = Path.GetExtension(path).Replace(".", "");
var format = Path.GetExtension(path).Replace(".", string.Empty);
// Calculates SHA1 Hash for byte[]
Response.AddCacheHeader(content);
@ -860,7 +860,7 @@ public class OpdsController : BaseApiController
if (files.Length == 0) return BadRequest("Cannot find icon");
var path = files[0];
var content = await _directoryService.ReadFileAsync(path);
var format = Path.GetExtension(path).Replace(".", "");
var format = Path.GetExtension(path).Replace(".", string.Empty);
return File(content, "image/" + format);
}

View File

@ -93,7 +93,7 @@ public class CacheService : ICacheService
}
catch (Exception ex)
{
_logger.LogError("There was an error calculating image dimensions for {ChapterId}", chapterId);
_logger.LogError(ex, "There was an error calculating image dimensions for {ChapterId}", chapterId);
}
finally
{
@ -164,7 +164,7 @@ public class CacheService : ICacheService
{
var removeNonImages = true;
var fileCount = files.Count;
var extraPath = "";
var extraPath = string.Empty;
var extractDi = _directoryService.FileSystem.DirectoryInfo.FromDirectoryName(extractPath);
if (files.Count > 0 && files[0].Format == MangaFormat.Image)

View File

@ -273,7 +273,7 @@ public class Startup
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials() // For SignalR token query param
.WithOrigins("http://localhost:4200", $"http://{GetLocalIpAddress()}:4200")
.WithOrigins("http://localhost:4200", $"http://{GetLocalIpAddress()}:4200", $"http://{GetLocalIpAddress()}:5000")
.WithExposedHeaders("Content-Disposition", "Pagination"));
}

View File

@ -47,7 +47,7 @@ The easiest way to get started is to visit our Wiki which has up-to-date informa
install methods and platforms.
[https://wiki.kavitareader.com/en/install](https://wiki.kavitareader.com/en/install)
**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.**
**Note: Kavita is under heavy development and is being updated all the time, so the tag for bleeding edge builds is `:nightly`. The `:latest` tag will be the latest stable release.**
## Feature Requests
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 before you submit an idea.
@ -100,5 +100,5 @@ being paid to help secure Kavita, please give them a try.
### License
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
* Copyright 2020-2022
* Copyright 2020-2023

View File

@ -5,6 +5,7 @@ This project was generated with [Angular CLI](https://github.com/angular/angular
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
Your backend must be served on port 5000.
## Code scaffolding
@ -24,6 +25,7 @@ Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.
Run `npx playwright test --reporter=line` or `npx playwright test` to run e2e tests.
## Further help
## Connecting to your dev server via your phone
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
ng serve --host 0.0.0.0
and update environment.ts to your local ip.

View File

@ -6735,6 +6735,24 @@
"integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==",
"dev": true
},
"ag-swipe-core": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/ag-swipe-core/-/ag-swipe-core-1.0.2.tgz",
"integrity": "sha512-NNONbrEbsmu6wsl7E07eGYVZw8Wx7hOok2TlhQLU/50EUhmI3Vpg8EDz0rWhV/HrfUAoEd4LxBvLAeT9DswQDw==",
"requires": {
"rxjs": "^7.5.5"
},
"dependencies": {
"rxjs": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz",
"integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==",
"requires": {
"tslib": "^2.1.0"
}
}
}
},
"agent-base": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
@ -12788,6 +12806,15 @@
"tslib": "^2.0.0"
}
},
"ng-swipe": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ng-swipe/-/ng-swipe-2.0.1.tgz",
"integrity": "sha512-y4w2d719VK1u6KUlNqhHVevzT+yR30bnTTLkFNEsVG3Gp5+oZhUnflVNWfzIw+O8GCjZqVLelwla/jOkqUclmQ==",
"requires": {
"ag-swipe-core": "^1.0.0",
"tslib": "^2.3.0"
}
},
"ngx-color-picker": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-12.0.0.tgz",

View File

@ -39,6 +39,7 @@
"file-saver": "^2.0.5",
"lazysizes": "^5.3.2",
"ng-circle-progress": "^1.6.0",
"ng-swipe": "^2.0.1",
"ngx-color-picker": "^12.0.0",
"ngx-extended-pdf-viewer": "^15.0.0",
"ngx-file-drop": "^14.0.1",

View File

@ -993,7 +993,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
getPageWidth() {
if (this.readingSectionElemRef == null) return 0;
const margin = (this.readingSectionElemRef.nativeElement.clientWidth*(parseInt(this.pageStyles['margin-left'], 10) / 100))*2;
const margin = (this.readingSectionElemRef.nativeElement.clientWidth * (parseInt(this.pageStyles['margin-left'], 10) / 100)) * 2;
const columnGap = 20;
return this.readingSectionElemRef.nativeElement.clientWidth - margin + columnGap;
}

View File

@ -47,7 +47,7 @@
</div>
</ng-template>
<app-loading [loading]="true"></app-loading>
<app-loading [loading]="isLoading"></app-loading>
<ng-template #jumpBar>
<div class="jump-bar">

View File

@ -28,7 +28,7 @@
</div>
<app-loading [loading]="isLoading"></app-loading>
<div class="reading-area"
appSwipe (swipeEvent)="onSwipeEvent($event)"
ngSwipe (swipeEnd)="onSwipeEnd($event)" (swipeMove)="onSwipeMove($event)"
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : 'calc(var(--vh)*100)'}" #readingArea>
<ng-container *ngIf="readerMode !== ReaderMode.Webtoon; else webtoon">
@ -43,14 +43,14 @@
<!-- Pagination controls and screen hints-->
<div class="pagination-area">
<div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')"
<div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, KeyDirection.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')"
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, KeyDirection.Left)"
[ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: '25%'),
'left': 'inherit',
'right': RightPaginationOffset + 'px'}">

View File

@ -1,7 +1,7 @@
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, combineLatest, debounceTime, distinctUntilChanged, filter, forkJoin, fromEvent, map, merge, Observable, of, ReplaySubject, Subject, take, takeUntil, tap } from 'rxjs';
import { BehaviorSubject, debounceTime, distinctUntilChanged, forkJoin, fromEvent, map, merge, Observable, ReplaySubject, Subject, take, takeUntil, tap } from 'rxjs';
import { LabelType, ChangeContext, Options } from '@angular-slider/ngx-slider';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { FormGroup, FormBuilder, FormControl } from '@angular/forms';
@ -31,6 +31,7 @@ import { DoubleRendererComponent } from '../double-renderer/double-renderer.comp
import { DoubleReverseRendererComponent } from '../double-reverse-renderer/double-reverse-renderer.component';
import { SingleRendererComponent } from '../single-renderer/single-renderer.component';
import { ChapterInfo } from '../../_models/chapter-info';
import { SwipeEvent } from 'ng-swipe';
const PREFETCH_PAGES = 10;
@ -298,6 +299,43 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
*/
rightPaginationOffset = 0;
/**
* Previous amount of scroll left. Used for swipe to paginate functionaliy.
*/
prevScrollLeft = 0;
/**
* Previous amount of scroll top. Used for swipe to paginate functionaliy.
*/
prevScrollTop = 0;
prevIsHorizontalScrollLeft = true;
prevIsVerticalScrollLeft = true;
/**
* Has the user scrolled to the far right side. This is used for swipe to next page and must ensure user is at end of scroll then on next swipe, will move pages.
*/
hasHitRightScroll = false;
/**
* Has the user scrolled once for the current page
*/
hasScrolledX: boolean = false;
/**
* Has the user scrolled once in the Y axis for the current page
*/
hasScrolledY: boolean = false;
/**
* Has the user scrolled to far left size. This doesn't include starting from no scroll
*/
hasHitZeroScroll: boolean = false;
/**
* Has the user scrolled to the far top of the screen
*/
hasHitZeroTopScroll: boolean = false;
/**
* Has the user scrolled to the far bottom of the screen
*/
hasHitBottomTopScroll: boolean = false;
// Renderer interaction
readerSettings$!: Observable<ReaderSetting>;
private currentImage: Subject<HTMLImageElement | null> = new ReplaySubject(1);
@ -352,6 +390,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
get KeyDirection() { return KeyDirection; }
get ReaderMode() { return ReaderMode; }
get LayoutMode() { return LayoutMode; }
get ReadingDirection() { return ReadingDirection; }
@ -359,6 +398,13 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
get Breakpoint() { return Breakpoint; }
get FITTING_OPTION() { return FITTING_OPTION; }
get FittingOption() { return this.generalSettingsForm.get('fittingOption')?.value || FITTING_OPTION.HEIGHT; }
get ReadingAreaWidth() {
return this.readingArea?.nativeElement.scrollWidth - this.readingArea?.nativeElement.clientWidth;
}
get ReadingAreaHeight() {
return this.readingArea?.nativeElement.scrollHeight - this.readingArea?.nativeElement.clientHeight;
}
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService,
public readerService: ReaderService, private formBuilder: FormBuilder, private navService: NavService,
@ -515,10 +561,17 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.cdRef.markForCheck();
});
fromEvent(this.readingArea.nativeElement, 'click').pipe(debounceTime(200)).subscribe((event: MouseEvent | any) => {
fromEvent(this.readingArea.nativeElement, 'click').pipe(debounceTime(200), takeUntil(this.onDestroy)).subscribe((event: MouseEvent | any) => {
if (event.detail > 1) return;
this.toggleMenu();
});
fromEvent(this.readingArea.nativeElement, 'scroll').pipe(debounceTime(200), takeUntil(this.onDestroy)).subscribe((event: MouseEvent | any) => {
this.prevScrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0;
this.prevScrollTop = this.readingArea?.nativeElement?.scrollTop || 0;
this.hasScrolledX = true;
this.hasScrolledY = true;
});
}
ngOnDestroy() {
@ -531,6 +584,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (this.goToPageEvent !== undefined) this.goToPageEvent.complete();
}
@HostListener('window:resize', ['$event'])
@HostListener('window:orientationchange', ['$event'])
onResize() {
@ -647,9 +701,28 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return img;
}
// if there is scroll room and on original, then don't paginate
isHorizontalScrollLeft() {
const scrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0;
// if scrollLeft is 0 and this.ReadingAreaWidth is 0, then there is no scroll needed
// if they equal each other, it means we are at the end of the scroll area
if (scrollLeft === 0 && this.ReadingAreaWidth === 0) return false;
if (scrollLeft === this.ReadingAreaWidth) return false;
return scrollLeft < this.ReadingAreaWidth;
}
isVerticalScrollLeft() {
const scrollTop = this.readingArea?.nativeElement?.scrollTop || 0;
return scrollTop < this.ReadingAreaHeight;
}
/**
* Is there any room to scroll in the direction we are giving? If so, return false. Otherwise return true.
* @param direction
* @returns
*/
checkIfPaginationAllowed(direction: KeyDirection) {
// This is not used atm due to the complexity it adds with keyboard.
if (this.readingArea === undefined || this.readingArea.nativeElement === undefined) return true;
const scrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0;
@ -657,22 +730,30 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
switch (direction) {
case KeyDirection.Right:
if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollLeft < this.readingArea?.nativeElement.scrollWidth - this.readingArea?.nativeElement.clientWidth) {
if (this.prevIsHorizontalScrollLeft && !this.isHorizontalScrollLeft()) { return true; }
this.prevIsHorizontalScrollLeft = this.isHorizontalScrollLeft();
if (this.isHorizontalScrollLeft()) {
return false;
}
break;
case KeyDirection.Left:
if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollLeft > 0) {
this.prevIsHorizontalScrollLeft = this.isHorizontalScrollLeft();
if (scrollLeft > 0 || this.prevScrollLeft > 0) {
return false;
}
break;
case KeyDirection.Up:
if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollTop > 0) {
this.prevIsVerticalScrollLeft = this.isVerticalScrollLeft();
if (scrollTop > 0|| this.prevScrollTop > 0) {
return false;
}
break;
case KeyDirection.Down:
if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollTop < this.readingArea?.nativeElement.scrollHeight - this.readingArea?.nativeElement.clientHeight) {
if (this.prevIsVerticalScrollLeft && !this.isVerticalScrollLeft()) { return true; }
this.prevIsVerticalScrollLeft = this.isVerticalScrollLeft();
if (this.isVerticalScrollLeft()) {
return false;
}
break;
@ -681,17 +762,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return true;
}
// This is menu code
clickOverlayClass(side: 'right' | 'left') {
if (!this.showClickOverlay) {
return '';
}
if (this.readingDirection === ReadingDirection.LeftToRight) {
return side === 'right' ? 'highlight' : 'highlight-2';
}
return side === 'right' ? 'highlight-2' : 'highlight';
}
init() {
this.nextChapterId = CHAPTER_ID_NOT_FETCHED;
@ -878,21 +949,169 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
}
onSwipeEvent(event: any) {
resetSwipeModifiers() {
this.prevScrollLeft = 0;
this.prevScrollTop = 0;
this.hasScrolledX = false;
this.hasScrolledY = false;
this.hasHitRightScroll = false;
this.hasHitZeroScroll = false;
this.hasHitBottomTopScroll = false;
this.hasHitZeroTopScroll = false;
}
handlePageChange(event: any, direction: string) {
/**
* This executes BEFORE fromEvent('scroll')
* @param event
* @returns
*/
onSwipeMove(_: SwipeEvent) {
this.prevScrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0;
this.prevScrollTop = this.readingArea?.nativeElement?.scrollTop || 0
}
triggerSwipePagination(direction: KeyDirection) {
if (this.readingDirection === ReadingDirection.LeftToRight) {
if (direction === KeyDirection.Right)
this.readingDirection === ReadingDirection.LeftToRight ? this.nextPage() : this.prevPage();
}
switch(direction) {
case KeyDirection.Down:
this.nextPage();
break;
case KeyDirection.Right:
this.readingDirection === ReadingDirection.LeftToRight ? this.nextPage() : this.prevPage();
break;
case KeyDirection.Up:
this.prevPage();
break;
case KeyDirection.Left:
this.readingDirection === ReadingDirection.LeftToRight ? this.prevPage() : this.nextPage();
break;
}
}
onSwipeEnd(event: SwipeEvent) {
const threshold = .12;
// Positive number means swiping right/down, negative means left
switch (this.readerMode) {
case ReaderMode.Webtoon: break;
case ReaderMode.LeftRight:
{
if (event.direction !== 'x') return;
const scrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0;
const direction = event.distance < 0 ? KeyDirection.Right : KeyDirection.Left;
if (!this.checkIfPaginationAllowed(direction)) {
return;
}
// We just came from a swipe where pagination was required and we are now at the end of the swipe, so make the user do it once more
if (direction === KeyDirection.Right) {
this.hasHitZeroScroll = false;
if (!this.hasHitRightScroll && this.checkIfPaginationAllowed(direction)) {
this.hasHitRightScroll = true;
return;
}
} else if (direction === KeyDirection.Left) {
this.hasHitRightScroll = false;
// If we have not scrolled then let the user page back
if (scrollLeft === 0 && this.prevScrollLeft === 0) {
if (!this.hasScrolledX || this.hasHitZeroScroll) {
this.triggerSwipePagination(direction);
return;
}
this.hasHitZeroScroll = true;
return;
}
}
if (!this.hasHitRightScroll) {
return;
}
console.log('Next page triggered');
this.triggerSwipePagination(direction);
break;
}
case ReaderMode.UpDown:
{
if (event.direction !== 'y') return;
const direction = event.distance < 0 ? KeyDirection.Down : KeyDirection.Up;
const scrollTop = this.readingArea?.nativeElement?.scrollTop || 0;
if (!this.checkIfPaginationAllowed(direction)) return;
if (direction === KeyDirection.Down) {
this.hasHitZeroTopScroll = false;
if (!this.hasHitBottomTopScroll && this.checkIfPaginationAllowed(direction)) {
this.hasHitBottomTopScroll = true;
return;
}
} else if (direction === KeyDirection.Up) {
this.hasHitBottomTopScroll = false;
// If we have not scrolled then let the user page back
if (scrollTop === 0 && this.prevScrollTop === 0) {
if (!this.hasScrolledY || this.hasHitZeroTopScroll) {
this.triggerSwipePagination(direction);
return;
}
this.hasHitZeroTopScroll = true;
return;
}
}
if (!this.hasHitBottomTopScroll) {
return;
}
console.log('Next page triggered');
this.triggerSwipePagination(direction);
break;
const height = (this.readingArea?.nativeElement.scrollHeight === this.readingArea?.nativeElement.clientHeight)
? this.readingArea?.nativeElement.clientHeight : this.ReadingAreaHeight;
if (direction === KeyDirection.Down && this.readingArea?.nativeElement?.scrollTop === height && this.prevScrollTop != 0) {
this.prevScrollTop = 0;
return;
}
if (direction === KeyDirection.Up && this.readingArea?.nativeElement?.scrollTop === 0 && this.prevScrollTop != 0) {
this.prevScrollTop = 0;
return;
}
const thresholdMet = Math.abs(event.distance) >= height * threshold;
if (!thresholdMet) return;
this.triggerSwipePagination(direction);
}
}
}
handlePageChange(event: any, direction: KeyDirection) {
if (this.readerMode === ReaderMode.Webtoon) {
if (direction === 'right') {
if (direction === KeyDirection.Right) {
this.nextPage(event);
} else {
this.prevPage(event);
}
return;
}
if (direction === 'right') {
if (direction === KeyDirection.Right) {
this.readingDirection === ReadingDirection.LeftToRight ? this.nextPage(event) : this.prevPage(event);
} else if (direction === 'left') {
} else if (direction === KeyDirection.Left) {
this.readingDirection === ReadingDirection.LeftToRight ? this.prevPage(event) : this.nextPage(event);
}
}
@ -903,6 +1122,8 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
event.preventDefault();
}
this.resetSwipeModifiers();
this.pagingDirectionSubject.next(PAGING_DIRECTION.FORWARD);
const pageAmount = Math.max(this.canvasRenderer.getPageAmount(PAGING_DIRECTION.FORWARD), this.singleRenderer.getPageAmount(PAGING_DIRECTION.FORWARD),
@ -925,6 +1146,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
event.stopPropagation();
event.preventDefault();
}
this.resetSwipeModifiers();
this.pagingDirectionSubject.next(PAGING_DIRECTION.BACKWARDS);
@ -1030,16 +1254,13 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
renderPage() {
const page = [this.canvasImage];
// After switching from webtoon mode, these are all undefined
this.canvasRenderer?.renderPage(page);
this.singleRenderer?.renderPage(page);
this.doubleRenderer?.renderPage(page);
this.doubleReverseRenderer?.renderPage(page);
if (this.FittingOption !== FITTING_OPTION.HEIGHT) {
// Originally this was only for fit to height, but when swiping was introduced, it made more sense to do it always to reset to the same view
this.readingArea.nativeElement.scroll(0,0);
}
this.cdRef.markForCheck();
}
@ -1244,6 +1465,18 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.render();
}
// This is menu code
clickOverlayClass(side: 'right' | 'left') {
if (!this.showClickOverlay) {
return '';
}
if (this.readingDirection === ReadingDirection.LeftToRight) {
return side === 'right' ? 'highlight' : 'highlight-2';
}
return side === 'right' ? 'highlight-2' : 'highlight';
}
// This is menu only code
promptForPage() {
const goToPageNum = window.prompt('There are ' + this.maxPages + ' pages. What page would you like to go to?', '');

View File

@ -11,13 +11,13 @@ import { PipeModule } from '../pipe/pipe.module';
import { FullscreenIconPipe } from './_pipes/fullscreen-icon.pipe';
import { LayoutModeIconPipe } from './_pipes/layout-mode-icon.pipe';
import { ReaderModeIconPipe } from './_pipes/reader-mode-icon.pipe';
import { SwipeDirective } from './swipe.directive';
import { CanvasRendererComponent } from './_components/canvas-renderer/canvas-renderer.component';
import { SingleRendererComponent } from './_components/single-renderer/single-renderer.component';
import { DoubleRendererComponent } from './_components/double-renderer/double-renderer.component';
import { DoubleReverseRendererComponent } from './_components/double-reverse-renderer/double-reverse-renderer.component';
import { MangaReaderComponent } from './_components/manga-reader/manga-reader.component';
import { FittingIconPipe } from './_pipes/fitting-icon.pipe';
import { SwipeModule } from 'ng-swipe';
@NgModule({
declarations: [
@ -26,7 +26,6 @@ import { FittingIconPipe } from './_pipes/fitting-icon.pipe';
FullscreenIconPipe,
ReaderModeIconPipe,
LayoutModeIconPipe,
SwipeDirective,
CanvasRendererComponent,
SingleRendererComponent,
DoubleRendererComponent,
@ -43,6 +42,8 @@ import { FittingIconPipe } from './_pipes/fitting-icon.pipe';
NgxSliderModule,
SharedModule,
ReaderSharedModule,
SwipeModule
],
exports: [
MangaReaderComponent

View File

@ -1,39 +0,0 @@
import { Directive, ElementRef, EventEmitter, HostListener, Input, Output } from '@angular/core';
import { fromEvent, map, Observable } from 'rxjs';
/**
* Repsonsible for triggering a swipe event
*/
@Directive({
selector: '[appSwipe]'
})
export class SwipeDirective {
@Input() threshold: number = 10;
@Output() swipeEvent: EventEmitter<any> = new EventEmitter<any>();
touchStarts$!: Observable<void>;
touchMoves$!: Observable<void>;
touchEnds$!: Observable<void>;
touchCancels$!: Observable<TouchEvent>;
@HostListener('touchstart') onTouchStart(event: TouchEvent) {
//console.log('Touch Start: ', event);
}
@HostListener('touchend') onTouchEnd(event: TouchEvent) {
//console.log('Touch End: ', event);
}
constructor(private el: ElementRef) {
this.touchStarts$ = fromEvent<TouchEvent>(el.nativeElement, 'touchstart').pipe(map(this.getTouchCoordinates));
this.touchMoves$ = fromEvent<TouchEvent>(el.nativeElement, 'touchmove').pipe(map(this.getTouchCoordinates));
this.touchEnds$ = fromEvent<TouchEvent>(el.nativeElement, 'touchend').pipe(map(this.getTouchCoordinates));
this.touchCancels$ = fromEvent<TouchEvent>(el.nativeElement, 'touchcancel');
}
getTouchCoordinates(event: TouchEvent) {
}
}

View File

@ -37,11 +37,10 @@ export class ReadingListDetailComponent implements OnInit {
downloadInProgress: boolean = false;
readingListSummary: string = '';
readingListImage: string = '';
libraryTypes: {[key: number]: LibraryType} = {};
readingListImage: string = '';
get MangaFormat(): typeof MangaFormat {
return MangaFormat;
}
@ -60,6 +59,7 @@ export class ReadingListDetailComponent implements OnInit {
return;
}
this.listId = parseInt(listId, 10);
this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId));
forkJoin([
this.libraryService.getLibraries(),
@ -134,6 +134,7 @@ export class ReadingListDetailComponent implements OnInit {
// Reload information around list
this.readingList = readingList;
this.readingListSummary = (this.readingList.summary === null ? '' : this.readingList.summary).replace(/\n/g, '<br>');
this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId));
this.cdRef.markForCheck();
});
break;

View File

@ -7,7 +7,7 @@
"name": "GPL-3.0",
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
},
"version": "0.6.1.22"
"version": "0.6.1.24"
},
"servers": [
{