Polish Round 1 (#2396)
This commit is contained in:
parent
cf2c43d390
commit
02b002d81a
197 changed files with 1233 additions and 1751 deletions
60
UI/Web/src/app/_pipes/age-rating.pipe.ts
Normal file
60
UI/Web/src/app/_pipes/age-rating.pipe.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import {inject, Pipe, PipeTransform} from '@angular/core';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { AgeRating } from '../_models/metadata/age-rating';
|
||||
import { AgeRatingDto } from '../_models/metadata/age-rating-dto';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'ageRating',
|
||||
standalone: true
|
||||
})
|
||||
export class AgeRatingPipe implements PipeTransform {
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
transform(value: AgeRating | AgeRatingDto | undefined): Observable<string> {
|
||||
if (value === undefined || value === null) return of(this.translocoService.translate('age-rating-pipe.unknown') as string);
|
||||
|
||||
if (value.hasOwnProperty('title')) {
|
||||
return of((value as AgeRatingDto).title);
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case AgeRating.Unknown:
|
||||
return this.translocoService.translate('age-rating-pipe.unknown');
|
||||
case AgeRating.EarlyChildhood:
|
||||
return this.translocoService.translate('age-rating-pipe.early-childhood');
|
||||
case AgeRating.AdultsOnly:
|
||||
return this.translocoService.translate('age-rating-pipe.adults-only');
|
||||
case AgeRating.Everyone:
|
||||
return this.translocoService.translate('age-rating-pipe.everyone');
|
||||
case AgeRating.Everyone10Plus:
|
||||
return this.translocoService.translate('age-rating-pipe.everyone-10-plus');
|
||||
case AgeRating.G:
|
||||
return this.translocoService.translate('age-rating-pipe.g');
|
||||
case AgeRating.KidsToAdults:
|
||||
return this.translocoService.translate('age-rating-pipe.kids-to-adults');
|
||||
case AgeRating.Mature:
|
||||
return this.translocoService.translate('age-rating-pipe.mature');
|
||||
case AgeRating.Mature15Plus:
|
||||
return this.translocoService.translate('age-rating-pipe.ma15-plus');
|
||||
case AgeRating.Mature17Plus:
|
||||
return this.translocoService.translate('age-rating-pipe.mature-17-plus');
|
||||
case AgeRating.RatingPending:
|
||||
return this.translocoService.translate('age-rating-pipe.rating-pending');
|
||||
case AgeRating.Teen:
|
||||
return this.translocoService.translate('age-rating-pipe.teen');
|
||||
case AgeRating.X18Plus:
|
||||
return this.translocoService.translate('age-rating-pipe.x18-plus');
|
||||
case AgeRating.NotApplicable:
|
||||
return this.translocoService.translate('age-rating-pipe.not-applicable');
|
||||
case AgeRating.PG:
|
||||
return this.translocoService.translate('age-rating-pipe.pg');
|
||||
case AgeRating.R18Plus:
|
||||
return this.translocoService.translate('age-rating-pipe.r18-plus');
|
||||
}
|
||||
|
||||
return of(this.translocoService.translate('age-rating-pipe.unknown') as string);
|
||||
}
|
||||
|
||||
}
|
||||
47
UI/Web/src/app/_pipes/bytes.pipe.ts
Normal file
47
UI/Web/src/app/_pipes/bytes.pipe.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'bytes',
|
||||
standalone: true
|
||||
})
|
||||
export class BytesPipe implements PipeTransform {
|
||||
|
||||
/**
|
||||
* Format bytes as human-readable text.
|
||||
*
|
||||
* @param bytes Number of bytes.
|
||||
* @param si True to use metric (SI) units, aka powers of 1000. False to use
|
||||
* binary (IEC), aka powers of 1024.
|
||||
* @param dp Number of decimal places to display.
|
||||
*
|
||||
* @return Formatted string.
|
||||
*
|
||||
* Credit: https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
|
||||
*/
|
||||
transform(bytes: number, si=true, dp=1): string {
|
||||
const thresh = si ? 1000 : 1024;
|
||||
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + ' B';
|
||||
}
|
||||
|
||||
const units = si
|
||||
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||
let u = -1;
|
||||
const r = 10**dp;
|
||||
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
||||
|
||||
const fixed = bytes.toFixed(dp);
|
||||
if ((fixed + '').endsWith('.0')) {
|
||||
return bytes.toFixed(0) + ' ' + units[u];
|
||||
}
|
||||
|
||||
return fixed + ' ' + units[u];
|
||||
}
|
||||
|
||||
}
|
||||
42
UI/Web/src/app/_pipes/cbl-conflict-reason.pipe.ts
Normal file
42
UI/Web/src/app/_pipes/cbl-conflict-reason.pipe.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import {inject, Pipe, PipeTransform} from '@angular/core';
|
||||
import { CblBookResult } from 'src/app/_models/reading-list/cbl/cbl-book-result';
|
||||
import { CblImportReason } from 'src/app/_models/reading-list/cbl/cbl-import-reason.enum';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
const failIcon = '<i aria-hidden="true" class="reading-list-fail--item fa-solid fa-circle-xmark me-1"></i>';
|
||||
const successIcon = '<i aria-hidden="true" class="reading-list-success--item fa-solid fa-circle-check me-1"></i>';
|
||||
|
||||
@Pipe({
|
||||
name: 'cblConflictReason',
|
||||
standalone: true
|
||||
})
|
||||
export class CblConflictReasonPipe implements PipeTransform {
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
transform(result: CblBookResult): string {
|
||||
switch (result.reason) {
|
||||
case CblImportReason.AllSeriesMissing:
|
||||
return failIcon + this.translocoService.translate('cbl-conflict-reason-pipe.all-series-missing');
|
||||
case CblImportReason.ChapterMissing:
|
||||
return failIcon + this.translocoService.translate('cbl-conflict-reason-pipe.chapter-missing', {series: result.series, chapter: result.number});
|
||||
case CblImportReason.EmptyFile:
|
||||
return failIcon + this.translocoService.translate('cbl-conflict-reason-pipe.empty-file');
|
||||
case CblImportReason.NameConflict:
|
||||
return failIcon + this.translocoService.translate('cbl-conflict-reason-pipe.chapter-missing', {readingListName: result.readingListName});
|
||||
case CblImportReason.SeriesCollision:
|
||||
return failIcon + this.translocoService.translate('cbl-conflict-reason-pipe.series-collision', {seriesLink: `<a href="/library/${result.libraryId}/series/${result.seriesId}" target="_blank">${result.series}</a>`});
|
||||
case CblImportReason.SeriesMissing:
|
||||
return failIcon + this.translocoService.translate('cbl-conflict-reason-pipe.series-missing', {series: result.series});
|
||||
case CblImportReason.VolumeMissing:
|
||||
return failIcon + this.translocoService.translate('cbl-conflict-reason-pipe.volume-missing', {series: result.series, volume: result.volume});
|
||||
case CblImportReason.AllChapterMissing:
|
||||
return failIcon + this.translocoService.translate('cbl-conflict-reason-pipe.all-chapter-missing');
|
||||
case CblImportReason.Success:
|
||||
return successIcon + this.translocoService.translate('cbl-conflict-reason-pipe.volume-missing', {series: result.series, volume: result.volume, chapter: result.number});
|
||||
case CblImportReason.InvalidFile:
|
||||
return failIcon + this.translocoService.translate('cbl-conflict-reason-pipe.invalid-file');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
23
UI/Web/src/app/_pipes/cbl-import-result.pipe.ts
Normal file
23
UI/Web/src/app/_pipes/cbl-import-result.pipe.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import {inject, Pipe, PipeTransform} from '@angular/core';
|
||||
import { CblImportResult } from 'src/app/_models/reading-list/cbl/cbl-import-result.enum';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'cblImportResult',
|
||||
standalone: true
|
||||
})
|
||||
export class CblImportResultPipe implements PipeTransform {
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
transform(result: CblImportResult): string {
|
||||
switch (result) {
|
||||
case CblImportResult.Success:
|
||||
return this.translocoService.translate('cbl-import-result-pipe.success');
|
||||
case CblImportResult.Partial:
|
||||
return this.translocoService.translate('cbl-import-result-pipe.partial');
|
||||
case CblImportResult.Fail:
|
||||
return this.translocoService.translate('cbl-import-result-pipe.failure');
|
||||
}
|
||||
}
|
||||
}
|
||||
46
UI/Web/src/app/_pipes/compact-number.pipe.ts
Normal file
46
UI/Web/src/app/_pipes/compact-number.pipe.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import {AccountService} from "../_services/account.service";
|
||||
|
||||
const specialCases = [4, 7, 10, 13];
|
||||
|
||||
@Pipe({
|
||||
name: 'compactNumber',
|
||||
standalone: true
|
||||
})
|
||||
export class CompactNumberPipe implements PipeTransform {
|
||||
|
||||
constructor() {}
|
||||
|
||||
transform(value: number): string {
|
||||
// Weblate allows some non-standard languages, like 'zh_Hans', which should be just 'zh'. So we handle that here
|
||||
const key = localStorage.getItem(AccountService.localeKey)?.replace('_', '-');
|
||||
if (key?.endsWith('Hans')) {
|
||||
return this.transformValue(key?.split('-')[0] || 'en', value);
|
||||
}
|
||||
return this.transformValue(key || 'en', value);
|
||||
}
|
||||
|
||||
private transformValue(locale: string, value: number) {
|
||||
const formatter = new Intl.NumberFormat(locale, {
|
||||
//@ts-ignore
|
||||
notation: 'compact', // https://github.com/microsoft/TypeScript/issues/36533
|
||||
maximumSignificantDigits: 3
|
||||
});
|
||||
|
||||
const formatterForDoublePrecision = new Intl.NumberFormat(locale, {
|
||||
//@ts-ignore
|
||||
notation: 'compact', // https://github.com/microsoft/TypeScript/issues/36533
|
||||
maximumSignificantDigits: 2
|
||||
});
|
||||
|
||||
if (value < 1000) return value + '';
|
||||
if (specialCases.includes((value + '').length)) { // from 4, every 3 will have a case where we need to override
|
||||
return formatterForDoublePrecision.format(value);
|
||||
}
|
||||
|
||||
return formatter.format(value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
31
UI/Web/src/app/_pipes/day-of-week.pipe.ts
Normal file
31
UI/Web/src/app/_pipes/day-of-week.pipe.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import { DayOfWeek } from 'src/app/_services/statistics.service';
|
||||
import {translate} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'dayOfWeek',
|
||||
standalone: true
|
||||
})
|
||||
export class DayOfWeekPipe implements PipeTransform {
|
||||
|
||||
transform(value: DayOfWeek): string {
|
||||
switch(value) {
|
||||
case DayOfWeek.Monday:
|
||||
return translate('day-of-week-pipe.monday');
|
||||
case DayOfWeek.Tuesday:
|
||||
return translate('day-of-week-pipe.tuesday');
|
||||
case DayOfWeek.Wednesday:
|
||||
return translate('day-of-week-pipe.wednesday');
|
||||
case DayOfWeek.Thursday:
|
||||
return translate('day-of-week-pipe.thursday');
|
||||
case DayOfWeek.Friday:
|
||||
return translate('day-of-week-pipe.friday');
|
||||
case DayOfWeek.Saturday:
|
||||
return translate('day-of-week-pipe.saturday');
|
||||
case DayOfWeek.Sunday:
|
||||
return translate('day-of-week-pipe.sunday');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
21
UI/Web/src/app/_pipes/default-date.pipe.ts
Normal file
21
UI/Web/src/app/_pipes/default-date.pipe.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'defaultDate',
|
||||
pure: true,
|
||||
standalone: true
|
||||
})
|
||||
export class DefaultDatePipe implements PipeTransform {
|
||||
|
||||
// TODO: Figure out how to translate Never
|
||||
constructor(private translocoService: TranslocoService) {
|
||||
}
|
||||
transform(value: any, replacementString = 'default-date-pipe.never'): string {
|
||||
if (value === null || value === undefined || value === '' || value === Infinity || Number.isNaN(value) || value === '1/1/01') {
|
||||
return this.translocoService.translate(replacementString);
|
||||
};
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
15
UI/Web/src/app/_pipes/default-value.pipe.ts
Normal file
15
UI/Web/src/app/_pipes/default-value.pipe.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'defaultValue',
|
||||
pure: true,
|
||||
standalone: true
|
||||
})
|
||||
export class DefaultValuePipe implements PipeTransform {
|
||||
|
||||
transform(value: any, replacementString = '—'): string {
|
||||
if (value === null || value === undefined || value === '' || value === Infinity || Number.isNaN(value)) return replacementString;
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
23
UI/Web/src/app/_pipes/device-platform.pipe.ts
Normal file
23
UI/Web/src/app/_pipes/device-platform.pipe.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import {inject, Pipe, PipeTransform} from '@angular/core';
|
||||
import { DevicePlatform } from 'src/app/_models/device/device-platform';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'devicePlatform',
|
||||
standalone: true
|
||||
})
|
||||
export class DevicePlatformPipe implements PipeTransform {
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
transform(value: DevicePlatform): string {
|
||||
switch(value) {
|
||||
case DevicePlatform.Kindle: return 'Kindle';
|
||||
case DevicePlatform.Kobo: return 'Kobo';
|
||||
case DevicePlatform.PocketBook: return 'PocketBook';
|
||||
case DevicePlatform.Custom: return this.translocoService.translate('device-platform-pipe.custom');
|
||||
default: return value + '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
50
UI/Web/src/app/_pipes/filter-comparison.pipe.ts
Normal file
50
UI/Web/src/app/_pipes/filter-comparison.pipe.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { FilterComparison } from 'src/app/_models/metadata/v2/filter-comparison';
|
||||
import {translate} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'filterComparison',
|
||||
standalone: true
|
||||
})
|
||||
export class FilterComparisonPipe implements PipeTransform {
|
||||
|
||||
transform(value: FilterComparison): string {
|
||||
switch (value) {
|
||||
case FilterComparison.BeginsWith:
|
||||
return translate('filter-comparison-pipe.begins-with');
|
||||
case FilterComparison.Contains:
|
||||
return translate('filter-comparison-pipe.contains');
|
||||
case FilterComparison.Equal:
|
||||
return translate('filter-comparison-pipe.equal');
|
||||
case FilterComparison.GreaterThan:
|
||||
return translate('filter-comparison-pipe.greater-than');
|
||||
case FilterComparison.GreaterThanEqual:
|
||||
return translate('filter-comparison-pipe.greater-than-or-equal');
|
||||
case FilterComparison.LessThan:
|
||||
return translate('filter-comparison-pipe.less-than');
|
||||
case FilterComparison.LessThanEqual:
|
||||
return translate('filter-comparison-pipe.less-than-or-equal');
|
||||
case FilterComparison.Matches:
|
||||
return translate('filter-comparison-pipe.matches');
|
||||
case FilterComparison.NotContains:
|
||||
return translate('filter-comparison-pipe.does-not-contain');
|
||||
case FilterComparison.NotEqual:
|
||||
return translate('filter-comparison-pipe.not-equal');
|
||||
case FilterComparison.EndsWith:
|
||||
return translate('filter-comparison-pipe.ends-with');
|
||||
case FilterComparison.IsBefore:
|
||||
return translate('filter-comparison-pipe.is-before');
|
||||
case FilterComparison.IsAfter:
|
||||
return translate('filter-comparison-pipe.is-after');
|
||||
case FilterComparison.IsInLast:
|
||||
return translate('filter-comparison-pipe.is-in-last');
|
||||
case FilterComparison.IsNotInLast:
|
||||
return translate('filter-comparison-pipe.is-not-in-last');
|
||||
case FilterComparison.MustContains:
|
||||
return translate('filter-comparison-pipe.must-contains');
|
||||
default:
|
||||
throw new Error(`Invalid FilterComparison value: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
74
UI/Web/src/app/_pipes/filter-field.pipe.ts
Normal file
74
UI/Web/src/app/_pipes/filter-field.pipe.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { FilterField } from 'src/app/_models/metadata/v2/filter-field';
|
||||
import {translate} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'filterField',
|
||||
standalone: true
|
||||
})
|
||||
export class FilterFieldPipe implements PipeTransform {
|
||||
|
||||
transform(value: FilterField): string {
|
||||
switch (value) {
|
||||
case FilterField.AgeRating:
|
||||
return translate('filter-field-pipe.age-rating');
|
||||
case FilterField.Characters:
|
||||
return translate('filter-field-pipe.characters');
|
||||
case FilterField.CollectionTags:
|
||||
return translate('filter-field-pipe.collection-tags');
|
||||
case FilterField.Colorist:
|
||||
return translate('filter-field-pipe.colorist');
|
||||
case FilterField.CoverArtist:
|
||||
return translate('filter-field-pipe.cover-artist');
|
||||
case FilterField.Editor:
|
||||
return translate('filter-field-pipe.editor');
|
||||
case FilterField.Formats:
|
||||
return translate('filter-field-pipe.formats');
|
||||
case FilterField.Genres:
|
||||
return translate('filter-field-pipe.genres');
|
||||
case FilterField.Inker:
|
||||
return translate('filter-field-pipe.inker');
|
||||
case FilterField.Languages:
|
||||
return translate('filter-field-pipe.languages');
|
||||
case FilterField.Libraries:
|
||||
return translate('filter-field-pipe.libraries');
|
||||
case FilterField.Letterer:
|
||||
return translate('filter-field-pipe.letterer');
|
||||
case FilterField.PublicationStatus:
|
||||
return translate('filter-field-pipe.publication-status');
|
||||
case FilterField.Penciller:
|
||||
return translate('filter-field-pipe.penciller');
|
||||
case FilterField.Publisher:
|
||||
return translate('filter-field-pipe.publisher');
|
||||
case FilterField.ReadProgress:
|
||||
return translate('filter-field-pipe.read-progress');
|
||||
case FilterField.ReadTime:
|
||||
return translate('filter-field-pipe.read-time');
|
||||
case FilterField.ReleaseYear:
|
||||
return translate('filter-field-pipe.release-year');
|
||||
case FilterField.SeriesName:
|
||||
return translate('filter-field-pipe.series-name');
|
||||
case FilterField.Summary:
|
||||
return translate('filter-field-pipe.summary');
|
||||
case FilterField.Tags:
|
||||
return translate('filter-field-pipe.tags');
|
||||
case FilterField.Translators:
|
||||
return translate('filter-field-pipe.translators');
|
||||
case FilterField.UserRating:
|
||||
return translate('filter-field-pipe.user-rating');
|
||||
case FilterField.Writers:
|
||||
return translate('filter-field-pipe.writers');
|
||||
case FilterField.Path:
|
||||
return translate('filter-field-pipe.path');
|
||||
case FilterField.FilePath:
|
||||
return translate('filter-field-pipe.file-path');
|
||||
case FilterField.WantToRead:
|
||||
return translate('filter-field-pipe.want-to-read');
|
||||
case FilterField.ReadingDate:
|
||||
return translate('filter-field-pipe.read-date');
|
||||
default:
|
||||
throw new Error(`Invalid FilterField value: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
19
UI/Web/src/app/_pipes/filter.pipe.ts
Normal file
19
UI/Web/src/app/_pipes/filter.pipe.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'filter',
|
||||
pure: false,
|
||||
standalone: true
|
||||
})
|
||||
export class FilterPipe implements PipeTransform {
|
||||
|
||||
transform(items: any[], callback: (item: any) => boolean): any {
|
||||
if (!items || !callback) {
|
||||
return items;
|
||||
}
|
||||
const ret = items.filter(item => callback(item));
|
||||
if (ret.length === items.length) return items; // This will prevent a re-render
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
22
UI/Web/src/app/_pipes/fitting-icon.pipe.ts
Normal file
22
UI/Web/src/app/_pipes/fitting-icon.pipe.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { FITTING_OPTION } from '../manga-reader/_models/reader-enums';
|
||||
|
||||
@Pipe({
|
||||
name: 'fittingIcon',
|
||||
pure: true,
|
||||
standalone: true,
|
||||
})
|
||||
export class FittingIconPipe implements PipeTransform {
|
||||
|
||||
transform(fit: FITTING_OPTION): string {
|
||||
switch(fit) {
|
||||
case FITTING_OPTION.HEIGHT:
|
||||
return 'fa fa-arrows-alt-v';
|
||||
case FITTING_OPTION.WIDTH:
|
||||
return 'fa fa-arrows-alt-h';
|
||||
case FITTING_OPTION.ORIGINAL:
|
||||
return 'fa fa-expand-arrows-alt';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
16
UI/Web/src/app/_pipes/fullscreen-icon.pipe.ts
Normal file
16
UI/Web/src/app/_pipes/fullscreen-icon.pipe.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Returns the icon for the given state of fullscreen mode
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'fullscreenIcon',
|
||||
standalone: true,
|
||||
})
|
||||
export class FullscreenIconPipe implements PipeTransform {
|
||||
|
||||
transform(isFullscreen: boolean): string {
|
||||
return isFullscreen ? 'fa-compress-alt' : 'fa-expand-alt';
|
||||
}
|
||||
|
||||
}
|
||||
24
UI/Web/src/app/_pipes/language-name.pipe.ts
Normal file
24
UI/Web/src/app/_pipes/language-name.pipe.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { MetadataService } from '../_services/metadata.service';
|
||||
import {shareReplay} from "rxjs/operators";
|
||||
|
||||
@Pipe({
|
||||
name: 'languageName',
|
||||
standalone: true
|
||||
})
|
||||
export class LanguageNamePipe implements PipeTransform {
|
||||
|
||||
constructor(private metadataService: MetadataService) {
|
||||
}
|
||||
|
||||
transform(isoCode: string): Observable<string> {
|
||||
// TODO: See if we can speed this up. It rarely changes and is quite heavy to download on each page
|
||||
return this.metadataService.getAllValidLanguages().pipe(map(lang => {
|
||||
const l = lang.filter(l => l.isoCode === isoCode);
|
||||
if (l.length > 0) return l[0].title;
|
||||
return '';
|
||||
}), shareReplay());
|
||||
}
|
||||
|
||||
}
|
||||
23
UI/Web/src/app/_pipes/layout-mode-icon.pipe.ts
Normal file
23
UI/Web/src/app/_pipes/layout-mode-icon.pipe.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { LayoutMode } from '../manga-reader/_models/layout-mode';
|
||||
|
||||
@Pipe({
|
||||
name: 'layoutModeIcon',
|
||||
standalone: true,
|
||||
})
|
||||
export class LayoutModeIconPipe implements PipeTransform {
|
||||
|
||||
transform(layoutMode: LayoutMode): string {
|
||||
switch (layoutMode) {
|
||||
case LayoutMode.Single:
|
||||
return 'none';
|
||||
case LayoutMode.Double:
|
||||
return 'double';
|
||||
case LayoutMode.DoubleReversed:
|
||||
return 'double-reversed';
|
||||
case LayoutMode.DoubleNoCover:
|
||||
return 'double'; // TODO: Validate
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
28
UI/Web/src/app/_pipes/library-type.pipe.ts
Normal file
28
UI/Web/src/app/_pipes/library-type.pipe.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import {inject, Pipe, PipeTransform} from '@angular/core';
|
||||
import { LibraryType } from '../_models/library';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
/**
|
||||
* Returns the name of the LibraryType
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'libraryType',
|
||||
standalone: true
|
||||
})
|
||||
export class LibraryTypePipe implements PipeTransform {
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
transform(libraryType: LibraryType): string {
|
||||
switch (libraryType) {
|
||||
case LibraryType.Book:
|
||||
return this.translocoService.translate('library-type-pipe.book');
|
||||
case LibraryType.Comic:
|
||||
return this.translocoService.translate('library-type-pipe.comic');
|
||||
case LibraryType.Manga:
|
||||
return this.translocoService.translate('library-type-pipe.manga');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
28
UI/Web/src/app/_pipes/manga-format-icon.pipe.ts
Normal file
28
UI/Web/src/app/_pipes/manga-format-icon.pipe.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { MangaFormat } from '../_models/manga-format';
|
||||
|
||||
/**
|
||||
* Returns the icon class representing the format
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'mangaFormatIcon',
|
||||
standalone: true
|
||||
})
|
||||
export class MangaFormatIconPipe implements PipeTransform {
|
||||
|
||||
transform(format: MangaFormat): string {
|
||||
switch (format) {
|
||||
case MangaFormat.EPUB:
|
||||
return 'fa-book';
|
||||
case MangaFormat.ARCHIVE:
|
||||
return 'fa-file-archive';
|
||||
case MangaFormat.IMAGE:
|
||||
return 'fa-image';
|
||||
case MangaFormat.PDF:
|
||||
return 'fa-file-pdf';
|
||||
case MangaFormat.UNKNOWN:
|
||||
return 'fa-question';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
33
UI/Web/src/app/_pipes/manga-format.pipe.ts
Normal file
33
UI/Web/src/app/_pipes/manga-format.pipe.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import { MangaFormat } from '../_models/manga-format';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
/**
|
||||
* Returns the string name for the format
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'mangaFormat',
|
||||
standalone: true
|
||||
})
|
||||
export class MangaFormatPipe implements PipeTransform {
|
||||
|
||||
constructor(private translocoService: TranslocoService) {}
|
||||
|
||||
transform(format: MangaFormat): string {
|
||||
switch (format) {
|
||||
case MangaFormat.EPUB:
|
||||
return this.translocoService.translate('manga-format-pipe.epub');
|
||||
case MangaFormat.ARCHIVE:
|
||||
return this.translocoService.translate('manga-format-pipe.archive');
|
||||
case MangaFormat.IMAGE:
|
||||
return this.translocoService.translate('manga-format-pipe.image');
|
||||
case MangaFormat.PDF:
|
||||
return this.translocoService.translate('manga-format-pipe.pdf');
|
||||
case MangaFormat.UNKNOWN:
|
||||
return this.translocoService.translate('manga-format-pipe.unknown');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
41
UI/Web/src/app/_pipes/person-role.pipe.ts
Normal file
41
UI/Web/src/app/_pipes/person-role.pipe.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import {inject, Pipe, PipeTransform} from '@angular/core';
|
||||
import { PersonRole } from '../_models/metadata/person';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'personRole',
|
||||
standalone: true
|
||||
})
|
||||
export class PersonRolePipe implements PipeTransform {
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
transform(value: PersonRole): string {
|
||||
switch (value) {
|
||||
case PersonRole.Artist:
|
||||
return this.translocoService.translate('person-role-pipe.artist');
|
||||
case PersonRole.Character:
|
||||
return this.translocoService.translate('person-role-pipe.character');
|
||||
case PersonRole.Colorist:
|
||||
return this.translocoService.translate('person-role-pipe.colorist');
|
||||
case PersonRole.CoverArtist:
|
||||
return this.translocoService.translate('person-role-pipe.cover-artist');
|
||||
case PersonRole.Editor:
|
||||
return this.translocoService.translate('person-role-pipe.editor');
|
||||
case PersonRole.Inker:
|
||||
return this.translocoService.translate('person-role-pipe.inker');
|
||||
case PersonRole.Letterer:
|
||||
return this.translocoService.translate('person-role-pipe.letterer');
|
||||
case PersonRole.Penciller:
|
||||
return this.translocoService.translate('person-role-pipe.penciller');
|
||||
case PersonRole.Publisher:
|
||||
return this.translocoService.translate('person-role-pipe.publisher');
|
||||
case PersonRole.Writer:
|
||||
return this.translocoService.translate('person-role-pipe.writer');
|
||||
case PersonRole.Other:
|
||||
return this.translocoService.translate('person-role-pipe.other');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
23
UI/Web/src/app/_pipes/provider-image.pipe.ts
Normal file
23
UI/Web/src/app/_pipes/provider-image.pipe.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {ScrobbleProvider} from "../_services/scrobbling.service";
|
||||
|
||||
@Pipe({
|
||||
name: 'providerImage',
|
||||
standalone: true
|
||||
})
|
||||
export class ProviderImagePipe implements PipeTransform {
|
||||
|
||||
transform(value: ScrobbleProvider): string {
|
||||
switch (value) {
|
||||
case ScrobbleProvider.AniList:
|
||||
return 'assets/images/ExternalServices/AniList.png';
|
||||
case ScrobbleProvider.Mal:
|
||||
return 'assets/images/ExternalServices/MAL.png';
|
||||
case ScrobbleProvider.GoogleBooks:
|
||||
return 'assets/images/ExternalServices/GoogleBooks.png';
|
||||
case ScrobbleProvider.Kavita:
|
||||
return 'assets/images/logo-32.png';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
23
UI/Web/src/app/_pipes/provider-name.pipe.ts
Normal file
23
UI/Web/src/app/_pipes/provider-name.pipe.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import {ScrobbleProvider} from "../_services/scrobbling.service";
|
||||
|
||||
@Pipe({
|
||||
name: 'providerName',
|
||||
standalone: true
|
||||
})
|
||||
export class ProviderNamePipe implements PipeTransform {
|
||||
|
||||
transform(value: ScrobbleProvider): string {
|
||||
switch (value) {
|
||||
case ScrobbleProvider.AniList:
|
||||
return 'AniList';
|
||||
case ScrobbleProvider.Mal:
|
||||
return 'MAL';
|
||||
case ScrobbleProvider.Kavita:
|
||||
return 'Kavita';
|
||||
case ScrobbleProvider.GoogleBooks:
|
||||
return 'Google Books';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
29
UI/Web/src/app/_pipes/publication-status.pipe.ts
Normal file
29
UI/Web/src/app/_pipes/publication-status.pipe.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import { PublicationStatus } from '../_models/metadata/publication-status';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'publicationStatus',
|
||||
standalone: true
|
||||
})
|
||||
export class PublicationStatusPipe implements PipeTransform {
|
||||
constructor(private translocoService: TranslocoService) {}
|
||||
|
||||
transform(value: PublicationStatus): string {
|
||||
switch (value) {
|
||||
case PublicationStatus.OnGoing:
|
||||
return this.translocoService.translate('publication-status-pipe.ongoing');
|
||||
case PublicationStatus.Hiatus:
|
||||
return this.translocoService.translate('publication-status-pipe.hiatus');
|
||||
case PublicationStatus.Completed:
|
||||
return this.translocoService.translate('publication-status-pipe.completed');
|
||||
case PublicationStatus.Cancelled:
|
||||
return this.translocoService.translate('publication-status-pipe.cancelled');
|
||||
case PublicationStatus.Ended:
|
||||
return this.translocoService.translate('publication-status-pipe.ended');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
23
UI/Web/src/app/_pipes/reader-mode-icon.pipe.ts
Normal file
23
UI/Web/src/app/_pipes/reader-mode-icon.pipe.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { ReaderMode } from 'src/app/_models/preferences/reader-mode';
|
||||
|
||||
@Pipe({
|
||||
name: 'readerModeIcon',
|
||||
standalone: true,
|
||||
})
|
||||
export class ReaderModeIconPipe implements PipeTransform {
|
||||
|
||||
transform(readerMode: ReaderMode): string {
|
||||
switch(readerMode) {
|
||||
case ReaderMode.LeftRight:
|
||||
return 'fa-exchange-alt';
|
||||
case ReaderMode.UpDown:
|
||||
return 'fa-exchange-alt fa-rotate-90';
|
||||
case ReaderMode.Webtoon:
|
||||
return 'fa-arrows-alt-v';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
47
UI/Web/src/app/_pipes/relationship.pipe.ts
Normal file
47
UI/Web/src/app/_pipes/relationship.pipe.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import {inject, Pipe, PipeTransform} from '@angular/core';
|
||||
import { RelationKind } from '../_models/series-detail/relation-kind';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'relationship',
|
||||
standalone: true
|
||||
})
|
||||
export class RelationshipPipe implements PipeTransform {
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
transform(relationship: RelationKind | undefined): string {
|
||||
if (relationship === undefined) return '';
|
||||
switch (relationship) {
|
||||
case RelationKind.Adaptation:
|
||||
return this.translocoService.translate('relationship-pipe.adaptation');
|
||||
case RelationKind.AlternativeSetting:
|
||||
return this.translocoService.translate('relationship-pipe.alternative-setting');
|
||||
case RelationKind.AlternativeVersion:
|
||||
return this.translocoService.translate('relationship-pipe.alternative-version');
|
||||
case RelationKind.Character:
|
||||
return this.translocoService.translate('relationship-pipe.character');
|
||||
case RelationKind.Contains:
|
||||
return this.translocoService.translate('relationship-pipe.contains');
|
||||
case RelationKind.Doujinshi:
|
||||
return this.translocoService.translate('relationship-pipe.doujinshi');
|
||||
case RelationKind.Other:
|
||||
return this.translocoService.translate('relationship-pipe.other');
|
||||
case RelationKind.Prequel:
|
||||
return this.translocoService.translate('relationship-pipe.prequel');
|
||||
case RelationKind.Sequel:
|
||||
return this.translocoService.translate('relationship-pipe.sequel');
|
||||
case RelationKind.SideStory:
|
||||
return this.translocoService.translate('relationship-pipe.side-story');
|
||||
case RelationKind.SpinOff:
|
||||
return this.translocoService.translate('relationship-pipe.spin-off');
|
||||
case RelationKind.Parent:
|
||||
return this.translocoService.translate('relationship-pipe.parent');
|
||||
case RelationKind.Edition:
|
||||
return this.translocoService.translate('relationship-pipe.edition');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
18
UI/Web/src/app/_pipes/safe-html.pipe.ts
Normal file
18
UI/Web/src/app/_pipes/safe-html.pipe.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { Pipe, PipeTransform, SecurityContext } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
|
||||
@Pipe({
|
||||
name: 'safeHtml',
|
||||
pure: true,
|
||||
standalone: true
|
||||
})
|
||||
export class SafeHtmlPipe implements PipeTransform {
|
||||
private readonly dom: DomSanitizer = inject(DomSanitizer);
|
||||
constructor() {}
|
||||
|
||||
transform(value: string): unknown {
|
||||
return this.dom.sanitize(SecurityContext.HTML, value);
|
||||
}
|
||||
|
||||
}
|
||||
17
UI/Web/src/app/_pipes/safe-style.pipe.ts
Normal file
17
UI/Web/src/app/_pipes/safe-style.pipe.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
|
||||
@Pipe({
|
||||
name: 'safeStyle',
|
||||
standalone: true
|
||||
})
|
||||
export class SafeStylePipe implements PipeTransform {
|
||||
private readonly sanitizer: DomSanitizer = inject(DomSanitizer);
|
||||
constructor(){}
|
||||
|
||||
transform(style: string) {
|
||||
return this.sanitizer.bypassSecurityTrustStyle(style);
|
||||
}
|
||||
|
||||
}
|
||||
15
UI/Web/src/app/_pipes/sentence-case.pipe.ts
Normal file
15
UI/Web/src/app/_pipes/sentence-case.pipe.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'sentenceCase',
|
||||
standalone: true
|
||||
})
|
||||
export class SentenceCasePipe implements PipeTransform {
|
||||
|
||||
transform(value: string | null): string {
|
||||
if (value === null || value === undefined) return '';
|
||||
|
||||
return value.charAt(0).toUpperCase() + value.substring(1);
|
||||
}
|
||||
|
||||
}
|
||||
26
UI/Web/src/app/_pipes/site-theme-provider.pipe.ts
Normal file
26
UI/Web/src/app/_pipes/site-theme-provider.pipe.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import {inject, Pipe, PipeTransform} from '@angular/core';
|
||||
import { ThemeProvider } from 'src/app/_models/preferences/site-theme';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
|
||||
@Pipe({
|
||||
name: 'siteThemeProvider',
|
||||
standalone: true
|
||||
})
|
||||
export class SiteThemeProviderPipe implements PipeTransform {
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
transform(provider: ThemeProvider | undefined | null): string {
|
||||
if (provider === null || provider === undefined) return '';
|
||||
switch(provider) {
|
||||
case ThemeProvider.System:
|
||||
return this.translocoService.translate('site-theme-provider-pipe.system');
|
||||
case ThemeProvider.User:
|
||||
return this.translocoService.translate('site-theme-provider-pipe.user');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
34
UI/Web/src/app/_pipes/sort-field.pipe.ts
Normal file
34
UI/Web/src/app/_pipes/sort-field.pipe.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import {SortField} from "../_models/metadata/series-filter";
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'sortField',
|
||||
standalone: true
|
||||
})
|
||||
export class SortFieldPipe implements PipeTransform {
|
||||
|
||||
constructor(private translocoService: TranslocoService) {
|
||||
}
|
||||
|
||||
transform(value: SortField): string {
|
||||
switch (value) {
|
||||
case SortField.SortName:
|
||||
return this.translocoService.translate('sort-field-pipe.sort-name');
|
||||
case SortField.Created:
|
||||
return this.translocoService.translate('sort-field-pipe.created');
|
||||
case SortField.LastModified:
|
||||
return this.translocoService.translate('sort-field-pipe.last-modified');
|
||||
case SortField.LastChapterAdded:
|
||||
return this.translocoService.translate('sort-field-pipe.last-chapter-added');
|
||||
case SortField.TimeToRead:
|
||||
return this.translocoService.translate('sort-field-pipe.time-to-read');
|
||||
case SortField.ReleaseYear:
|
||||
return this.translocoService.translate('sort-field-pipe.release-year');
|
||||
case SortField.ReadProgress:
|
||||
return this.translocoService.translate('sort-field-pipe.read-progress');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
15
UI/Web/src/app/_pipes/stream-name.pipe.ts
Normal file
15
UI/Web/src/app/_pipes/stream-name.pipe.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import {translate} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'streamName',
|
||||
standalone: true,
|
||||
pure: true
|
||||
})
|
||||
export class StreamNamePipe implements PipeTransform {
|
||||
|
||||
transform(value: string): unknown {
|
||||
return translate('stream-pipe.' + value);
|
||||
}
|
||||
|
||||
}
|
||||
132
UI/Web/src/app/_pipes/time-ago.pipe.ts
Normal file
132
UI/Web/src/app/_pipes/time-ago.pipe.ts
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import {ChangeDetectorRef, NgZone, OnDestroy, Pipe, PipeTransform} from '@angular/core';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
/**
|
||||
* 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,
|
||||
standalone: true
|
||||
})
|
||||
export class TimeAgoPipe implements PipeTransform, OnDestroy {
|
||||
|
||||
private timer: number | null = null;
|
||||
constructor(private readonly changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
|
||||
private translocoService: TranslocoService) {}
|
||||
|
||||
transform(value: string) {
|
||||
|
||||
if (value === '' || value === null || value === undefined || value.split('T')[0] === '0001-01-01') {
|
||||
return this.translocoService.translate('time-ago-pipe.never');
|
||||
}
|
||||
|
||||
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 this.translocoService.translate('time-ago-pipe.just-now');
|
||||
}
|
||||
if (seconds <= 90) {
|
||||
return this.translocoService.translate('time-ago-pipe.min-ago');
|
||||
}
|
||||
if (minutes <= 45) {
|
||||
return this.translocoService.translate('time-ago-pipe.mins-ago', {value: minutes});
|
||||
}
|
||||
if (minutes <= 90) {
|
||||
return this.translocoService.translate('time-ago-pipe.hour-ago');
|
||||
}
|
||||
if (hours <= 22) {
|
||||
return this.translocoService.translate('time-ago-pipe.hours-ago', {value: hours});
|
||||
}
|
||||
if (hours <= 36) {
|
||||
return this.translocoService.translate('time-ago-pipe.day-ago');
|
||||
}
|
||||
if (days <= 25) {
|
||||
return this.translocoService.translate('time-ago-pipe.days-ago', {value: days});
|
||||
}
|
||||
if (days <= 45) {
|
||||
return this.translocoService.translate('time-ago-pipe.month-ago');
|
||||
}
|
||||
if (days <= 345) {
|
||||
return this.translocoService.translate('time-ago-pipe.months-ago', {value: months});
|
||||
}
|
||||
if (days <= 545) {
|
||||
return this.translocoService.translate('time-ago-pipe.year-ago');
|
||||
}
|
||||
return this.translocoService.translate('time-ago-pipe.years-ago', {value: years});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
31
UI/Web/src/app/_pipes/time-duration.pipe.ts
Normal file
31
UI/Web/src/app/_pipes/time-duration.pipe.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import {inject, Pipe, PipeTransform} from '@angular/core';
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
/**
|
||||
* Converts hours -> days, months, years, etc
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'timeDuration',
|
||||
standalone: true
|
||||
})
|
||||
export class TimeDurationPipe implements PipeTransform {
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
transform(hours: number): string {
|
||||
if (hours === 0)
|
||||
return this.translocoService.translate('time-duration-pipe.hours', {value: hours});
|
||||
if (hours < 1) {
|
||||
return this.translocoService.translate('time-duration-pipe.minutes', {value: (hours * 60).toFixed(1)});
|
||||
} else if (hours < 24) {
|
||||
return this.translocoService.translate('time-duration-pipe.hours', {value: hours.toFixed(1)});
|
||||
} else if (hours < 720) {
|
||||
return this.translocoService.translate('time-duration-pipe.days', {value: (hours / 24).toFixed(1)});
|
||||
} else if (hours < 8760) {
|
||||
return this.translocoService.translate('time-duration-pipe.months', {value: (hours / 720).toFixed(1)});
|
||||
} else {
|
||||
return this.translocoService.translate('time-duration-pipe.years', {value: (hours / 8760).toFixed(1)});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
37
UI/Web/src/app/_pipes/utc-to-local-time.pipe.ts
Normal file
37
UI/Web/src/app/_pipes/utc-to-local-time.pipe.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
type UtcToLocalTimeFormat = 'full' | 'short' | 'shortDate' | 'shortTime';
|
||||
|
||||
// FULL = 'full', // 'EEE, MMMM d, y, h:mm:ss a zzzz' - Monday, June 15, 2015 at 9:03:01 AM GMT+01:00
|
||||
// SHORT = 'short', // 'd/M/yy, h:mm - 15/6/15, 9:03
|
||||
// SHORT_DATE = 'shortDate', // 'd/M/yy' - 15/6/15
|
||||
// SHORT_TIME = 'shortTime', // 'h:mm' - 9:03
|
||||
|
||||
|
||||
@Pipe({
|
||||
name: 'utcToLocalTime',
|
||||
standalone: true
|
||||
})
|
||||
export class UtcToLocalTimePipe implements PipeTransform {
|
||||
|
||||
transform(utcDate: string, format: UtcToLocalTimeFormat = 'short'): string {
|
||||
const browserLanguage = navigator.language;
|
||||
const dateTime = DateTime.fromISO(utcDate, { zone: 'utc' }).toLocal().setLocale(browserLanguage);
|
||||
|
||||
switch (format) {
|
||||
case 'short':
|
||||
return dateTime.toLocaleString(DateTime.DATETIME_SHORT);
|
||||
case 'shortDate':
|
||||
return dateTime.toLocaleString(DateTime.DATE_MED);
|
||||
case 'shortTime':
|
||||
return dateTime.toLocaleString(DateTime.TIME_SIMPLE);
|
||||
case 'full':
|
||||
return dateTime.toString();
|
||||
default:
|
||||
console.error('No logic in place for utc date format, format: ', format);
|
||||
return utcDate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue