Polish Round 1 (#2396)

This commit is contained in:
Joe Milazzo 2023-11-04 12:29:10 -05:00 committed by GitHub
parent cf2c43d390
commit 02b002d81a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
197 changed files with 1233 additions and 1751 deletions

View 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);
}
}

View 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];
}
}

View 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');
}
}
}

View 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');
}
}
}

View 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);
}
}

View 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');
}
}
}

View 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;
}
}

View 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;
}
}

View 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 + '';
}
}
}

View 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}`);
}
}
}

View 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}`);
}
}
}

View 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;
}
}

View 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';
}
}
}

View 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';
}
}

View 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());
}
}

View 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
}
}
}

View 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 '';
}
}
}

View 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';
}
}
}

View 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 '';
}
}
}

View 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 '';
}
}
}

View 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';
}
}
}

View 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';
}
}
}

View 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 '';
}
}
}

View 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 '';
}
}
}

View 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 '';
}
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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 '';
}
}
}

View 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');
}
}
}

View 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);
}
}

View 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;
}
}
}

View 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)});
}
}
}

View 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;
}
}
}