Jump Bar Testing (#1302)

* Implemented a basic jump bar for the library view. This currently just interacts with existing pagination controls and is not inlined with infinite scroll yet. This is a first pass implementation.

* Refactored time estimates into the reading service.

* Cleaned up when the jump bar is shown to mimic pagination controls

* Cleanup up code in reader service.

* Scroll to card when selecting a jump key that is shown on the current page.

* Ensure estimated times always has the smaller number on left hand side.

* Fixed a bug with a missing vertical rule

* Fixed an off by 1 pixel for search overlay
This commit is contained in:
Joseph Milazzo 2022-05-30 16:50:12 -05:00 committed by GitHub
parent 64c0b5a71e
commit 742cfd3293
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 319 additions and 120 deletions

View file

@ -7,6 +7,7 @@ using API.Comparators;
using API.Data;
using API.Data.Repositories;
using API.DTOs;
using API.DTOs.Reader;
using API.Entities;
using API.Extensions;
using API.SignalR;
@ -28,6 +29,7 @@ public interface IReaderService
Task<ChapterDto> GetContinuePoint(int seriesId, int userId);
Task MarkChaptersUntilAsRead(AppUser user, int seriesId, float chapterNumber);
Task MarkVolumesUntilAsRead(AppUser user, int seriesId, int volumeNumber);
HourEstimateRangeDto GetTimeEstimate(long wordCount, int pageCount, bool isEpub, bool hasProgress = false);
}
public class ReaderService : IReaderService
@ -168,7 +170,7 @@ public class ReaderService : IReaderService
var progresses = user.Progresses.Where(x => x.ChapterId == chapter.Id && x.AppUserId == user.Id).ToList();
if (progresses.Count > 1)
{
user.Progresses = new List<AppUserProgress>()
user.Progresses = new List<AppUserProgress>
{
user.Progresses.First()
};
@ -478,7 +480,7 @@ public class ReaderService : IReaderService
/// <param name="chapterNumber"></param>
public async Task MarkChaptersUntilAsRead(AppUser user, int seriesId, float chapterNumber)
{
var volumes = await _unitOfWork.VolumeRepository.GetVolumesForSeriesAsync(new List<int>() { seriesId }, true);
var volumes = await _unitOfWork.VolumeRepository.GetVolumesForSeriesAsync(new List<int> { seriesId }, true);
foreach (var volume in volumes.OrderBy(v => v.Number))
{
var chapters = volume.Chapters
@ -490,10 +492,57 @@ public class ReaderService : IReaderService
public async Task MarkVolumesUntilAsRead(AppUser user, int seriesId, int volumeNumber)
{
var volumes = await _unitOfWork.VolumeRepository.GetVolumesForSeriesAsync(new List<int>() { seriesId }, true);
var volumes = await _unitOfWork.VolumeRepository.GetVolumesForSeriesAsync(new List<int> { seriesId }, true);
foreach (var volume in volumes.OrderBy(v => v.Number).Where(v => v.Number <= volumeNumber && v.Number > 0))
{
MarkChaptersAsRead(user, volume.SeriesId, volume.Chapters);
}
}
public HourEstimateRangeDto GetTimeEstimate(long wordCount, int pageCount, bool isEpub, bool hasProgress = false)
{
if (isEpub)
{
var minHours = Math.Max((int) Math.Round((wordCount / MinWordsPerHour)), 1);
var maxHours = Math.Max((int) Math.Round((wordCount / MaxWordsPerHour)), 1);
if (maxHours < minHours)
{
return new HourEstimateRangeDto
{
MinHours = maxHours,
MaxHours = minHours,
AvgHours = (int) Math.Round((wordCount / AvgWordsPerHour)),
HasProgress = hasProgress
};
}
return new HourEstimateRangeDto
{
MinHours = minHours,
MaxHours = maxHours,
AvgHours = (int) Math.Round((wordCount / AvgWordsPerHour)),
HasProgress = hasProgress
};
}
var minHoursPages = Math.Max((int) Math.Round((pageCount / MinPagesPerMinute / 60F)), 1);
var maxHoursPages = Math.Max((int) Math.Round((pageCount / MaxPagesPerMinute / 60F)), 1);
if (maxHoursPages < minHoursPages)
{
return new HourEstimateRangeDto
{
MinHours = maxHoursPages,
MaxHours = minHoursPages,
AvgHours = (int) Math.Round((pageCount / AvgPagesPerMinute / 60F)),
HasProgress = hasProgress
};
}
return new HourEstimateRangeDto
{
MinHours = minHoursPages,
MaxHours = maxHoursPages,
AvgHours = (int) Math.Round((pageCount / AvgPagesPerMinute / 60F)),
HasProgress = hasProgress
};
}
}

View file

@ -8,9 +8,9 @@ using API.Data;
using API.DTOs;
using API.DTOs.CollectionTags;
using API.DTOs.Metadata;
using API.DTOs.SeriesDetail;
using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.Helpers;
using API.SignalR;
using Microsoft.Extensions.Logging;
@ -98,7 +98,7 @@ public class SeriesService : ISeriesService
series.Metadata.SummaryLocked = true;
}
if (series.Metadata.Language != updateSeriesMetadataDto.SeriesMetadata.Language)
if (series.Metadata.Language != updateSeriesMetadataDto.SeriesMetadata?.Language)
{
series.Metadata.Language = updateSeriesMetadataDto.SeriesMetadata?.Language;
series.Metadata.LanguageLocked = true;
@ -112,7 +112,7 @@ public class SeriesService : ISeriesService
});
series.Metadata.Genres ??= new List<Genre>();
UpdateGenreList(updateSeriesMetadataDto.SeriesMetadata.Genres, series, allGenres, (genre) =>
UpdateGenreList(updateSeriesMetadataDto.SeriesMetadata?.Genres, series, allGenres, (genre) =>
{
series.Metadata.Genres.Add(genre);
}, () => series.Metadata.GenresLocked = true);
@ -521,11 +521,11 @@ public class SeriesService : ISeriesService
/// <summary>
/// Should we show the given chapter on the UI. We only show non-specials and non-zero chapters.
/// </summary>
/// <param name="c"></param>
/// <param name="chapter"></param>
/// <returns></returns>
private static bool ShouldIncludeChapter(ChapterDto c)
private static bool ShouldIncludeChapter(ChapterDto chapter)
{
return !c.IsSpecial && !c.Number.Equals(Parser.Parser.DefaultChapter);
return !chapter.IsSpecial && !chapter.Number.Equals(Parser.Parser.DefaultChapter);
}
public static void RenameVolumeName(ChapterDto firstChapter, VolumeDto volume, LibraryType libraryType)