diff --git a/API/Controllers/StatsController.cs b/API/Controllers/StatsController.cs index 4bafd4831..a53c306f8 100644 --- a/API/Controllers/StatsController.cs +++ b/API/Controllers/StatsController.cs @@ -108,16 +108,17 @@ public class StatsController : BaseApiController /// Returns reading history events for a give or all users, broken up by day, and format /// /// If 0, defaults to all users, else just userId + /// If 0, defaults to all time, else just those days asked for /// [HttpGet("reading-count-by-day")] [ResponseCache(CacheProfileName = "Statistics")] - public async Task>>> ReadCountByDay(int userId = 0) + public async Task>>> ReadCountByDay(int userId = 0, int days = 0) { var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); var isAdmin = User.IsInRole(PolicyConstants.AdminRole); if (!isAdmin && userId != user.Id) return BadRequest(); - return Ok(await _statService.ReadCountByDay(userId)); + return Ok(await _statService.ReadCountByDay(userId, days)); } diff --git a/API/DTOs/Statistics/UserReadStatistics.cs b/API/DTOs/Statistics/UserReadStatistics.cs index 8ebf4226d..274f4c29c 100644 --- a/API/DTOs/Statistics/UserReadStatistics.cs +++ b/API/DTOs/Statistics/UserReadStatistics.cs @@ -13,12 +13,9 @@ public class UserReadStatistics /// Total time spent reading based on estimates /// public long TimeSpentReading { get; set; } - /// - /// A list of genres mapped with genre and number of series that fall into said genre - /// - public ICollection> FavoriteGenres { get; set; } - public long ChaptersRead { get; set; } public DateTime LastActive { get; set; } - public long AvgHoursPerWeekSpentReading { get; set; } + public double AvgHoursPerWeekSpentReading { get; set; } + public IEnumerable> PercentReadPerLibrary { get; set; } + } diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 146799605..0b3e924bf 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -322,7 +322,7 @@ public class SeriesRepository : ISeriesRepository .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); - var justYear = Regex.Match(searchQuery, @"\d{4}").Value; + var justYear = Regex.Match(searchQuery, @"\d{4}", RegexOptions.None, Services.Tasks.Scanner.Parser.Parser.RegexTimeout).Value; var hasYearInQuery = !string.IsNullOrEmpty(justYear); var yearComparison = hasYearInQuery ? int.Parse(justYear) : 0; diff --git a/API/Services/BookService.cs b/API/Services/BookService.cs index 32761105b..1373990fa 100644 --- a/API/Services/BookService.cs +++ b/API/Services/BookService.cs @@ -535,8 +535,8 @@ public class BookService : IBookService private static string EscapeTags(string content) { - content = Regex.Replace(content, @")", ""); - content = Regex.Replace(content, @")", ""); + content = Regex.Replace(content, @")", "", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout); + content = Regex.Replace(content, @")", "", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout); return content; } @@ -995,12 +995,12 @@ public class BookService : IBookService } // Remove comments from CSS - body = Regex.Replace(body, @"/\*[\d\D]*?\*/", string.Empty); + body = Regex.Replace(body, @"/\*[\d\D]*?\*/", string.Empty, RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout); - body = Regex.Replace(body, @"[a-zA-Z]+#", "#"); - body = Regex.Replace(body, @"[\n\r]+\s*", string.Empty); - body = Regex.Replace(body, @"\s+", " "); - body = Regex.Replace(body, @"\s?([:,;{}])\s?", "$1"); + body = Regex.Replace(body, @"[a-zA-Z]+#", "#", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout); + body = Regex.Replace(body, @"[\n\r]+\s*", string.Empty, RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout); + body = Regex.Replace(body, @"\s+", " ", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout); + body = Regex.Replace(body, @"\s?([:,;{}])\s?", "$1", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout); try { body = body.Replace(";}", "}"); @@ -1010,7 +1010,7 @@ public class BookService : IBookService //Swallow exception. Some css don't have style rules ending in ';' } - body = Regex.Replace(body, @"([\s:]0)(px|pt|%|em)", "$1"); + body = Regex.Replace(body, @"([\s:]0)(px|pt|%|em)", "$1", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout); return body; diff --git a/API/Services/StatisticService.cs b/API/Services/StatisticService.cs index 1ab88dd37..7db7709c1 100644 --- a/API/Services/StatisticService.cs +++ b/API/Services/StatisticService.cs @@ -26,7 +26,7 @@ public interface IStatisticService Task GetFileBreakdown(); Task> GetTopUsers(int days); Task> GetReadingHistory(int userId); - Task>> ReadCountByDay(int userId = 0); + Task>> ReadCountByDay(int userId = 0, int days = 0); } /// @@ -81,7 +81,34 @@ public class StatisticService : IStatisticService .Select(p => p.LastModified) .FirstOrDefaultAsync(); - //var + // Reading Progress by Library Name + + // First get the total pages per library + var totalPageCountByLibrary = _context.Chapter + .Join(_context.Volume, c => c.VolumeId, v => v.Id, (chapter, volume) => new { chapter, volume }) + .Join(_context.Series, g => g.volume.SeriesId, s => s.Id, (g, series) => new { g.chapter, series }) + .AsEnumerable() + .GroupBy(g => g.series.LibraryId) + .ToDictionary(g => g.Key, g => g.Sum(c => c.chapter.Pages)); + // + // + var totalProgressByLibrary = await _context.AppUserProgresses + .Where(p => p.AppUserId == userId) + .Where(p => p.LibraryId > 0) + .GroupBy(p => p.LibraryId) + .Select(g => new StatCount + { + Count = g.Key, + Value = g.Sum(p => p.PagesRead) / (float) totalPageCountByLibrary[g.Key] + }) + .ToListAsync(); + + + var averageReadingTimePerWeek = _context.AppUserProgresses + .Where(p => p.AppUserId == userId) + .Join(_context.Chapter, p => p.ChapterId, c => c.Id, + (p, c) => (p.PagesRead / (float) c.Pages) * c.AvgHoursToRead) + .Average() / 7; return new UserReadStatistics() { @@ -89,6 +116,8 @@ public class StatisticService : IStatisticService TimeSpentReading = timeSpentReading, ChaptersRead = chaptersRead, LastActive = lastActive, + PercentReadPerLibrary = totalProgressByLibrary, + AvgHoursPerWeekSpentReading = averageReadingTimePerWeek }; } @@ -297,7 +326,7 @@ public class StatisticService : IStatisticService .ToListAsync(); } - public async Task>> ReadCountByDay(int userId = 0) + public async Task>> ReadCountByDay(int userId = 0, int days = 0) { var query = _context.AppUserProgresses .AsSplitQuery() @@ -314,7 +343,13 @@ public class StatisticService : IStatisticService query = query.Where(x => x.appUserProgresses.AppUserId == userId); } - return await query.GroupBy(x => new + if (days > 0) + { + var date = DateTime.Now.AddDays(days * -1); + query = query.Where(x => x.appUserProgresses.LastModified >= date && x.appUserProgresses.Created >= date); + } + + var results = await query.GroupBy(x => new { Day = x.appUserProgresses.Created.Date, x.series.Format @@ -327,6 +362,23 @@ public class StatisticService : IStatisticService }) .OrderBy(d => d.Value) .ToListAsync(); + + if (results.Count > 0) + { + var minDay = results.Min(d => d.Value); + for (var date = minDay; date < DateTime.Now; date = date.AddDays(1)) + { + if (results.Any(d => d.Value == date)) continue; + results.Add(new PagesReadOnADayCount() + { + Format = MangaFormat.Unknown, + Value = date, + Count = 0 + }); + } + } + + return results; } public async Task> GetTopUsers(int days) diff --git a/API/Services/Tasks/Scanner/Parser/Parser.cs b/API/Services/Tasks/Scanner/Parser/Parser.cs index 3c6d0cca4..a81ebfa29 100644 --- a/API/Services/Tasks/Scanner/Parser/Parser.cs +++ b/API/Services/Tasks/Scanner/Parser/Parser.cs @@ -911,7 +911,7 @@ public static class Parser { try { - if (!Regex.IsMatch(range, @"^[\d\-.]+$")) + if (!Regex.IsMatch(range, @"^[\d\-.]+$", MatchOptions, RegexTimeout)) { return (float) 0.0; } @@ -929,7 +929,7 @@ public static class Parser { try { - if (!Regex.IsMatch(range, @"^[\d\-.]+$")) + if (!Regex.IsMatch(range, @"^[\d\-.]+$", MatchOptions, RegexTimeout)) { return (float) 0.0; } diff --git a/UI/Web/src/app/_services/statistics.service.ts b/UI/Web/src/app/_services/statistics.service.ts index e65ec4856..4543384c7 100644 --- a/UI/Web/src/app/_services/statistics.service.ts +++ b/UI/Web/src/app/_services/statistics.service.ts @@ -82,7 +82,7 @@ export class StatisticsService { return this.httpClient.get(this.baseUrl + 'stats/server/file-breakdown'); } - getReadCountByDay(userId: number = 0) { - return this.httpClient.get>(this.baseUrl + 'stats/reading-count-by-day?userId=' + userId); + getReadCountByDay(userId: number = 0, days: number = 0) { + return this.httpClient.get>(this.baseUrl + 'stats/reading-count-by-day?userId=' + userId + '&days=' + days); } } diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html index fa187c05a..da686e63b 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html @@ -56,7 +56,7 @@
- diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss index 89727299c..5ebd517e9 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss @@ -83,6 +83,11 @@ .active { font-weight: bold; } + + &.disabled { + color: lightgrey; + cursor: not-allowed; + } } } diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts index 0825e8d48..81811ec86 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts @@ -116,14 +116,17 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges { this.jumpBarKeysToRender = [...this.jumpBarKeys]; this.resizeJumpBar(); - if (!this.hasResumedJumpKey && this.jumpBarKeysToRender.length > 0) { - const resumeKey = this.jumpbarService.getResumeKey(this.router.url); - if (resumeKey === '') return; - const keys = this.jumpBarKeysToRender.filter(k => k.key === resumeKey); - if (keys.length < 1) return; - - this.hasResumedJumpKey = true; - setTimeout(() => this.scrollTo(keys[0]), 100); + // Don't resume jump key when there is a custom sort order, as it won't work + if (this.hasCustomSort()) { + if (!this.hasResumedJumpKey && this.jumpBarKeysToRender.length > 0) { + const resumeKey = this.jumpbarService.getResumeKey(this.router.url); + if (resumeKey === '') return; + const keys = this.jumpBarKeysToRender.filter(k => k.key === resumeKey); + if (keys.length < 1) return; + + this.hasResumedJumpKey = true; + setTimeout(() => this.scrollTo(keys[0]), 100); + } } } @@ -133,6 +136,10 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges { this.onDestory.complete(); } + hasCustomSort() { + return this.filter.sortOptions !== null || this.filterSettings.presets?.sortOptions !== null; + } + performAction(action: ActionItem) { if (typeof action.callback === 'function') { action.callback(action, undefined); @@ -147,6 +154,8 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges { scrollTo(jumpKey: JumpKey) { + if (this.hasCustomSort()) return; + let targetIndex = 0; for(var i = 0; i < this.jumpBarKeys.length; i++) { if (this.jumpBarKeys[i].key === jumpKey.key) break; diff --git a/UI/Web/src/app/cards/edit-series-relation/edit-series-relation.component.html b/UI/Web/src/app/cards/edit-series-relation/edit-series-relation.component.html index 0701e67f5..98344f32c 100644 --- a/UI/Web/src/app/cards/edit-series-relation/edit-series-relation.component.html +++ b/UI/Web/src/app/cards/edit-series-relation/edit-series-relation.component.html @@ -10,8 +10,8 @@
-
-
+
+
{{item.name}} ({{libraryNames[item.libraryId]}}) @@ -21,13 +21,13 @@
-
+
- +
diff --git a/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.ts b/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.ts index cfc3ec99d..b759d5f68 100644 --- a/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.ts +++ b/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.ts @@ -251,4 +251,8 @@ export class CanvasRendererComponent implements OnInit, AfterViewInit, OnDestroy this.imageHeight.emit(this.canvas.nativeElement.height); this.cdRef.markForCheck(); } + + getBookmarkPageCount(): number { + return 1; + } } diff --git a/UI/Web/src/app/manga-reader/_components/double-renderer/double-renderer.component.ts b/UI/Web/src/app/manga-reader/_components/double-renderer/double-renderer.component.ts index 4243531c0..302036754 100644 --- a/UI/Web/src/app/manga-reader/_components/double-renderer/double-renderer.component.ts +++ b/UI/Web/src/app/manga-reader/_components/double-renderer/double-renderer.component.ts @@ -282,6 +282,10 @@ export class DoubleRendererComponent implements OnInit, OnDestroy, ImageRenderer } reset(): void {} + getBookmarkPageCount(): number { + return this.shouldRenderDouble() ? 2 : 1; + } + debugLog(message: string, extraData?: any) { if (!(this.debugMode & DEBUG_MODES.Logs)) return; diff --git a/UI/Web/src/app/manga-reader/_components/double-reverse-renderer/double-reverse-renderer.component.ts b/UI/Web/src/app/manga-reader/_components/double-reverse-renderer/double-reverse-renderer.component.ts index f0db97b85..08b236a70 100644 --- a/UI/Web/src/app/manga-reader/_components/double-reverse-renderer/double-reverse-renderer.component.ts +++ b/UI/Web/src/app/manga-reader/_components/double-reverse-renderer/double-reverse-renderer.component.ts @@ -296,6 +296,10 @@ export class DoubleReverseRendererComponent implements OnInit, OnDestroy, ImageR } reset(): void {} + getBookmarkPageCount(): number { + return this.shouldRenderDouble() ? 2 : 1; + } + debugLog(message: string, extraData?: any) { if (!(this.debugMode & DEBUG_MODES.Logs)) return; diff --git a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts index 9a43f5c7b..f16bcb3c2 100644 --- a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts +++ b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts @@ -627,11 +627,14 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { * Gets a page from cache else gets a brand new Image * @param pageNum Page Number to load * @param forceNew Forces to fetch a new image - * @param chapterId ChapterId to fetch page from. Defaults to current chapterId + * @param chapterId ChapterId to fetch page from. Defaults to current chapterId. Not used when in bookmark mode * @returns */ getPage(pageNum: number, chapterId: number = this.chapterId, forceNew: boolean = false) { - let img = this.cachedImages.find(img => this.readerService.imageUrlToPageNum(img.src) === pageNum + + let img = undefined; + if (this.bookmarkMode) img = this.cachedImages.find(img => this.readerService.imageUrlToPageNum(img.src) === pageNum); + else img = this.cachedImages.find(img => this.readerService.imageUrlToPageNum(img.src) === pageNum && (this.readerService.imageUrlToChapterId(img.src) == chapterId || this.readerService.imageUrlToChapterId(img.src) === -1) ); if (!img || forceNew) { @@ -1273,7 +1276,8 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { if (this.bookmarkMode) return; const pageNum = this.pageNum; - const isDouble = this.layoutMode === LayoutMode.Double || this.layoutMode === LayoutMode.DoubleReversed; + const isDouble = Math.max(this.canvasRenderer.getBookmarkPageCount(), this.singleRenderer.getBookmarkPageCount(), + this.doubleRenderer.getBookmarkPageCount(), this.doubleReverseRenderer.getBookmarkPageCount()) > 1; if (this.CurrentPageBookmarked) { let apis = [this.readerService.unbookmark(this.seriesId, this.volumeId, this.chapterId, pageNum)]; diff --git a/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.ts b/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.ts index 89228bf7b..7f5137289 100644 --- a/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.ts +++ b/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.ts @@ -143,4 +143,8 @@ export class SingleRendererComponent implements OnInit, OnDestroy, ImageRenderer return 1; } reset(): void {} + + getBookmarkPageCount(): number { + return 1; + } } diff --git a/UI/Web/src/app/manga-reader/_models/renderer.ts b/UI/Web/src/app/manga-reader/_models/renderer.ts index 52da21122..c6c2da799 100644 --- a/UI/Web/src/app/manga-reader/_models/renderer.ts +++ b/UI/Web/src/app/manga-reader/_models/renderer.ts @@ -55,4 +55,8 @@ export interface ImageRenderer { * This should reset any needed state, but not unset the image. */ reset(): void; + /** + * Returns the number of pages that are currently rendererd on screen and thus should be bookmarked. + */ + getBookmarkPageCount(): number; } \ No newline at end of file diff --git a/UI/Web/src/app/metadata-filter/metadata-filter.component.ts b/UI/Web/src/app/metadata-filter/metadata-filter.component.ts index d3e85596d..c45963c4d 100644 --- a/UI/Web/src/app/metadata-filter/metadata-filter.component.ts +++ b/UI/Web/src/app/metadata-filter/metadata-filter.component.ts @@ -3,7 +3,7 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap'; import { distinctUntilChanged, forkJoin, map, Observable, of, ReplaySubject, Subject, takeUntil } from 'rxjs'; import { FilterUtilitiesService } from '../shared/_services/filter-utilities.service'; -import { UtilityService } from '../shared/_services/utility.service'; +import { Breakpoint, UtilityService } from '../shared/_services/utility.service'; import { TypeaheadSettings } from '../typeahead/_models/typeahead-settings'; import { CollectionTag } from '../_models/collection-tag'; import { Genre } from '../_models/metadata/genre'; @@ -630,6 +630,11 @@ export class MetadataFilterComponent implements OnInit, OnDestroy { apply() { this.applyFilter.emit({filter: this.filter, isFirst: this.updateApplied === 0}); this.updateApplied++; + + if (this.utilityService.getActiveBreakpoint() === Breakpoint.Mobile) { + this.toggleSelected(); + } + this.cdRef.markForCheck(); } diff --git a/UI/Web/src/app/statistics/_components/read-by-day-and/read-by-day-and.component.html b/UI/Web/src/app/statistics/_components/read-by-day-and/read-by-day-and.component.html index 6e5dad93f..7534b3a9d 100644 --- a/UI/Web/src/app/statistics/_components/read-by-day-and/read-by-day-and.component.html +++ b/UI/Web/src/app/statistics/_components/read-by-day-and/read-by-day-and.component.html @@ -4,8 +4,8 @@

Top Readers

-
-
+ +
+
+ + +
diff --git a/UI/Web/src/app/statistics/_components/read-by-day-and/read-by-day-and.component.ts b/UI/Web/src/app/statistics/_components/read-by-day-and/read-by-day-and.component.ts index 49d101d8a..b66d247a4 100644 --- a/UI/Web/src/app/statistics/_components/read-by-day-and/read-by-day-and.component.ts +++ b/UI/Web/src/app/statistics/_components/read-by-day-and/read-by-day-and.component.ts @@ -6,6 +6,7 @@ import { Member } from 'src/app/_models/auth/member'; import { MemberService } from 'src/app/_services/member.service'; import { StatisticsService } from 'src/app/_services/statistics.service'; import { PieDataItem } from '../../_models/pie-data-item'; +import { TimePeriods } from '../top-readers/top-readers.component'; const options: Intl.DateTimeFormatOptions = { month: "short", day: "numeric" }; const mangaFormatPipe = new MangaFormatPipe(); @@ -26,14 +27,16 @@ export class ReadByDayAndComponent implements OnInit, OnDestroy { view: [number, number] = [0, 400]; formGroup: FormGroup = new FormGroup({ 'users': new FormControl(-1, []), + 'days': new FormControl(TimePeriods[0].value, []), }); users$: Observable | undefined; data$: Observable>; + timePeriods = TimePeriods; private readonly onDestroy = new Subject(); constructor(private statService: StatisticsService, private memberService: MemberService) { - this.data$ = this.formGroup.get('users')!.valueChanges.pipe( - switchMap(uId => this.statService.getReadCountByDay(uId)), + this.data$ = this.formGroup.valueChanges.pipe( + switchMap(_ => this.statService.getReadCountByDay(this.formGroup.get('users')!.value, this.formGroup.get('days')!.value)), map(data => { const gList = data.reduce((formats, entry) => { const formatTranslated = mangaFormatPipe.transform(entry.format); diff --git a/UI/Web/src/app/statistics/_components/top-readers/top-readers.component.ts b/UI/Web/src/app/statistics/_components/top-readers/top-readers.component.ts index 4ec98d5ee..841919d61 100644 --- a/UI/Web/src/app/statistics/_components/top-readers/top-readers.component.ts +++ b/UI/Web/src/app/statistics/_components/top-readers/top-readers.component.ts @@ -4,6 +4,8 @@ import { Observable, Subject, takeUntil, switchMap, shareReplay } from 'rxjs'; import { StatisticsService } from 'src/app/_services/statistics.service'; import { TopUserRead } from '../../_models/top-reads'; +export const TimePeriods: Array<{title: string, value: number}> = [{title: 'This Week', value: new Date().getDay() || 1}, {title: 'Last 7 Days', value: 7}, {title: 'Last 30 Days', value: 30}, {title: 'Last 90 Days', value: 90}, {title: 'Last Year', value: 365}, {title: 'All Time', value: 0}]; + @Component({ selector: 'app-top-readers', templateUrl: './top-readers.component.html', @@ -13,7 +15,7 @@ import { TopUserRead } from '../../_models/top-reads'; export class TopReadersComponent implements OnInit, OnDestroy { formGroup: FormGroup; - timePeriods: Array<{title: string, value: number}> = [{title: 'Last 7 Days', value: 7}, {title: 'Last 30 Days', value: 30}, {title: 'Last 90 Days', value: 90}, {title: 'Last Year', value: 365}, {title: 'All Time', value: 0}]; + timePeriods = TimePeriods; users$: Observable; private readonly onDestroy = new Subject(); diff --git a/UI/Web/src/app/statistics/_components/user-stats-info-cards/user-stats-info-cards.component.html b/UI/Web/src/app/statistics/_components/user-stats-info-cards/user-stats-info-cards.component.html index 687602d26..addaf57a9 100644 --- a/UI/Web/src/app/statistics/_components/user-stats-info-cards/user-stats-info-cards.component.html +++ b/UI/Web/src/app/statistics/_components/user-stats-info-cards/user-stats-info-cards.component.html @@ -17,6 +17,15 @@
+ +
+ + {{avgHoursPerWeekSpentReading | compactNumber}} hours + +
+
+
+
diff --git a/UI/Web/src/app/statistics/_components/user-stats/user-stats.component.html b/UI/Web/src/app/statistics/_components/user-stats/user-stats.component.html index c9e76bc9a..3560e85d5 100644 --- a/UI/Web/src/app/statistics/_components/user-stats/user-stats.component.html +++ b/UI/Web/src/app/statistics/_components/user-stats/user-stats.component.html @@ -14,6 +14,10 @@
+
+ +
+