mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Misc UI Tweaks (#1692)
* Added a timeAgo pipe which shows live updates for a few areas. * Fixed some wording on stats page. Changed Total People count to just work on distinct names and not count multiple for different roles. * Tweaked the compact number so it only shows one decimal * Fixed a bug
This commit is contained in:
parent
20441f5ec3
commit
4548dcb1eb
@ -240,13 +240,21 @@ public class StatisticService : IStatisticService
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
.AsEnumerable();
|
||||
|
||||
var distinctPeople = _context.Person
|
||||
.AsSplitQuery()
|
||||
.AsEnumerable()
|
||||
.GroupBy(sm => sm.NormalizedName)
|
||||
.Select(sm => sm.Key)
|
||||
.Distinct()
|
||||
.Count();
|
||||
|
||||
return new ServerStatistics()
|
||||
{
|
||||
ChapterCount = await _context.Chapter.CountAsync(),
|
||||
SeriesCount = await _context.Series.CountAsync(),
|
||||
TotalFiles = await _context.MangaFile.CountAsync(),
|
||||
TotalGenres = await _context.Genre.CountAsync(),
|
||||
TotalPeople = await _context.Person.CountAsync(),
|
||||
TotalPeople = distinctPeople,
|
||||
TotalSize = await _context.MangaFile.SumAsync(m => m.Bytes),
|
||||
TotalTags = await _context.Tag.CountAsync(),
|
||||
VolumeCount = await _context.Volume.Where(v => v.Number != 0).CountAsync(),
|
||||
|
@ -21,7 +21,7 @@
|
||||
Last Scanned:
|
||||
<span *ngIf="library.lastScanned == '0001-01-01T00:00:00'; else activeDate">Never</span>
|
||||
<ng-template #activeDate>
|
||||
{{library.lastScanned | date: 'short'}}
|
||||
{{library.lastScanned | timeAgo}}
|
||||
</ng-template>
|
||||
</div>
|
||||
</li>
|
||||
|
@ -52,7 +52,7 @@
|
||||
<ng-container *ngIf="series.latestReadDate && series.latestReadDate !== '' && (series.latestReadDate | date: 'shortDate') !== '1/1/01'">
|
||||
<div class="d-none d-md-block col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
|
||||
<app-icon-and-title label="Last Read" [clickable]="false" fontClasses="fa-regular fa-clock" title="Last Read">
|
||||
{{series.latestReadDate | date:'shortDate'}}
|
||||
{{series.latestReadDate | timeAgo}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
|
@ -7,12 +7,26 @@ const formatter = new Intl.NumberFormat('en-GB', {
|
||||
maximumSignificantDigits: 3
|
||||
});
|
||||
|
||||
const formatterForDoublePercision = new Intl.NumberFormat('en-GB', {
|
||||
//@ts-ignore
|
||||
notation: 'compact', // https://github.com/microsoft/TypeScript/issues/36533
|
||||
maximumSignificantDigits: 2
|
||||
});
|
||||
|
||||
const specialCases = [4, 7, 10, 13];
|
||||
|
||||
@Pipe({
|
||||
name: 'compactNumber'
|
||||
})
|
||||
export class CompactNumberPipe implements PipeTransform {
|
||||
|
||||
transform(value: number): string {
|
||||
|
||||
if (value < 1000) return value + '';
|
||||
if (specialCases.includes((value + '').length)) { // from 4, every 3 will have a case where we need to override
|
||||
return formatterForDoublePercision.format(value);
|
||||
}
|
||||
|
||||
return formatter.format(value);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import { LibraryTypePipe } from './library-type.pipe';
|
||||
import { SafeStylePipe } from './safe-style.pipe';
|
||||
import { DefaultDatePipe } from './default-date.pipe';
|
||||
import { BytesPipe } from './bytes.pipe';
|
||||
import { TimeAgoPipe } from './time-ago.pipe';
|
||||
|
||||
|
||||
|
||||
@ -37,6 +38,7 @@ import { BytesPipe } from './bytes.pipe';
|
||||
SafeStylePipe,
|
||||
DefaultDatePipe,
|
||||
BytesPipe,
|
||||
TimeAgoPipe,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -57,7 +59,8 @@ import { BytesPipe } from './bytes.pipe';
|
||||
LibraryTypePipe,
|
||||
SafeStylePipe,
|
||||
DefaultDatePipe,
|
||||
BytesPipe
|
||||
BytesPipe,
|
||||
TimeAgoPipe
|
||||
]
|
||||
})
|
||||
export class PipeModule { }
|
||||
|
123
UI/Web/src/app/pipe/time-ago.pipe.ts
Normal file
123
UI/Web/src/app/pipe/time-ago.pipe.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { ChangeDetectorRef, NgZone, OnDestroy, Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
|
||||
Copyright (c) 2016 Andrew Poyntz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
This code was taken from https://github.com/AndrewPoyntz/time-ago-pipe/blob/master/time-ago.pipe.ts
|
||||
and modified
|
||||
*/
|
||||
|
||||
@Pipe({
|
||||
name: 'timeAgo',
|
||||
pure: false
|
||||
})
|
||||
export class TimeAgoPipe implements PipeTransform, OnDestroy {
|
||||
|
||||
private timer: number | null = null;
|
||||
constructor(private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone) {}
|
||||
transform(value:string) {
|
||||
this.removeTimer();
|
||||
const d = new Date(value);
|
||||
const now = new Date();
|
||||
const seconds = Math.round(Math.abs((now.getTime() - d.getTime()) / 1000));
|
||||
const timeToUpdate = (Number.isNaN(seconds)) ? 1000 : this.getSecondsUntilUpdate(seconds) * 1000;
|
||||
|
||||
this.timer = this.ngZone.runOutsideAngular(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.setTimeout(() => {
|
||||
this.ngZone.run(() => this.changeDetectorRef.markForCheck());
|
||||
}, timeToUpdate);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const minutes = Math.round(Math.abs(seconds / 60));
|
||||
const hours = Math.round(Math.abs(minutes / 60));
|
||||
const days = Math.round(Math.abs(hours / 24));
|
||||
const months = Math.round(Math.abs(days/30.416));
|
||||
const years = Math.round(Math.abs(days/365));
|
||||
|
||||
if (Number.isNaN(seconds)){
|
||||
return '';
|
||||
}
|
||||
|
||||
if (seconds <= 45) {
|
||||
return 'just now';
|
||||
}
|
||||
if (seconds <= 90) {
|
||||
return 'a minute ago';
|
||||
}
|
||||
if (minutes <= 45) {
|
||||
return minutes + ' minutes ago';
|
||||
}
|
||||
if (minutes <= 90) {
|
||||
return 'an hour ago';
|
||||
}
|
||||
if (hours <= 22) {
|
||||
return hours + ' hours ago';
|
||||
}
|
||||
if (hours <= 36) {
|
||||
return 'a day ago';
|
||||
}
|
||||
if (days <= 25) {
|
||||
return days + ' days ago';
|
||||
}
|
||||
if (days <= 45) {
|
||||
return 'a month ago';
|
||||
}
|
||||
if (days <= 345) {
|
||||
return months + ' months ago';
|
||||
}
|
||||
if (days <= 545) {
|
||||
return 'a year ago';
|
||||
}
|
||||
return years + ' years ago';
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.removeTimer();
|
||||
}
|
||||
|
||||
private removeTimer() {
|
||||
if (this.timer) {
|
||||
window.clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private getSecondsUntilUpdate(seconds:number) {
|
||||
const min = 60;
|
||||
const hr = min * 60;
|
||||
const day = hr * 24;
|
||||
if (seconds < min) { // less than 1 min, update every 2 secs
|
||||
return 2;
|
||||
} else if (seconds < hr) { // less than an hour, update every 30 secs
|
||||
return 30;
|
||||
} else if (seconds < day) { // less then a day, update every 5 mins
|
||||
return 300;
|
||||
} else { // update every hour
|
||||
return 3600;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #tooltip>Non Classified means Kavita has not scanned some files. This occurs on old files existing prior to v0.7. You may need to run a forced scan via Library settings.</ng-template>
|
||||
<ng-template #tooltip>Not Classified means Kavita has not scanned some files. This occurs on old files existing prior to v0.7. You may need to run a forced scan via Library settings modal.</ng-template>
|
||||
|
||||
|
||||
<ng-container *ngIf="files$ | async as files">
|
||||
|
@ -29,7 +29,7 @@
|
||||
<ng-container>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title label="Last Active" [clickable]="false" fontClasses="fa-regular fa-calendar" title="Last Active">
|
||||
{{lastActive | date:'short'}}
|
||||
{{lastActive | timeAgo}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
@ -7,7 +7,7 @@
|
||||
"name": "GPL-3.0",
|
||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||
},
|
||||
"version": "0.6.1.11"
|
||||
"version": "0.6.1.12"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user