mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-20 08:33:11 +00:00
487 lines
12 KiB
TypeScript
487 lines
12 KiB
TypeScript
/*
|
||
* Copyright (C) 2020-2021 StApps
|
||
* This program is free software: you can redistribute it and/or modify it
|
||
* under the terms of the GNU General Public License as published by the Free
|
||
* Software Foundation, version 3.
|
||
*
|
||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
* more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License along with
|
||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
import {Injectable, OnDestroy, Pipe, PipeTransform} from '@angular/core';
|
||
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
|
||
import moment from 'moment';
|
||
import {Subscription} from 'rxjs';
|
||
import {logger} from '../_helpers/ts-logger';
|
||
// TODO: use import for opening_hours when the change is published with a new update
|
||
// see https://github.com/opening-hours/opening_hours.js/pull/407
|
||
// eslint-disable-next-line @typescript-eslint/no-var-requires, unicorn/prefer-module
|
||
const ohFunction = require('opening_hours');
|
||
|
||
@Injectable()
|
||
@Pipe({
|
||
name: 'join',
|
||
pure: true,
|
||
})
|
||
export class ArrayJoinPipe implements PipeTransform {
|
||
value = '';
|
||
|
||
transform(anArray: unknown[] | unknown, separator: string | unknown): string {
|
||
if (typeof separator !== 'string' || separator.length <= 0) {
|
||
return this.value;
|
||
}
|
||
|
||
if (!Array.isArray(anArray)) {
|
||
throw new SyntaxError(
|
||
`Wrong parameter in ArrayJoinPipe. Expected a valid Array, received: ${anArray}`,
|
||
);
|
||
}
|
||
|
||
this.value = anArray.join(separator);
|
||
|
||
return this.value;
|
||
}
|
||
}
|
||
|
||
@Injectable()
|
||
@Pipe({
|
||
name: 'sentencecase',
|
||
pure: true,
|
||
})
|
||
export class SentenceCasePipe implements PipeTransform {
|
||
value = '';
|
||
|
||
transform(aString: string | unknown): string {
|
||
if (typeof aString !== 'string') {
|
||
throw new SyntaxError(
|
||
`Wrong parameter in StringSplitPipe. Expected a valid String, received: ${aString}`,
|
||
);
|
||
}
|
||
|
||
this.value = aString.slice(0, 1).toUpperCase() + aString.slice(1);
|
||
|
||
return this.value;
|
||
}
|
||
}
|
||
|
||
@Injectable()
|
||
@Pipe({
|
||
name: 'split',
|
||
pure: true,
|
||
})
|
||
export class StringSplitPipe implements PipeTransform {
|
||
value = new Array<unknown>();
|
||
|
||
transform(aString: string | unknown, splitter: string | unknown): unknown[] {
|
||
if (typeof splitter !== 'string' || splitter.length <= 0) {
|
||
return this.value as never;
|
||
}
|
||
|
||
if (typeof aString !== 'string') {
|
||
throw new SyntaxError(
|
||
`Wrong parameter in StringSplitPipe. Expected a valid String, received: ${aString}`,
|
||
);
|
||
}
|
||
|
||
this.value = aString.split(splitter);
|
||
|
||
return this.value as never;
|
||
}
|
||
}
|
||
|
||
@Injectable()
|
||
@Pipe({
|
||
name: 'openingHours',
|
||
pure: true,
|
||
})
|
||
export class OpeningHoursPipe implements PipeTransform, OnDestroy {
|
||
locale: string;
|
||
|
||
onLangChange?: Subscription;
|
||
|
||
value = '';
|
||
|
||
constructor(private readonly translate: TranslateService) {
|
||
this.locale = translate.currentLang;
|
||
}
|
||
|
||
private _dispose(): void {
|
||
if (this.onLangChange?.closed === false) {
|
||
this.onLangChange?.unsubscribe();
|
||
}
|
||
}
|
||
|
||
ngOnDestroy(): void {
|
||
this._dispose();
|
||
}
|
||
|
||
transform(aString: string | unknown): string {
|
||
this.updateValue(aString);
|
||
this._dispose();
|
||
if (this.onLangChange?.closed === true) {
|
||
this.onLangChange = this.translate.onLangChange.subscribe(
|
||
(event: LangChangeEvent) => {
|
||
this.locale = event.lang;
|
||
this.updateValue(aString);
|
||
},
|
||
);
|
||
}
|
||
|
||
return this.value;
|
||
}
|
||
|
||
updateValue(aString: string | unknown) {
|
||
if (typeof aString !== 'string') {
|
||
logger.warn(`openingHours pipe unable to parse input: ${aString}`);
|
||
|
||
return;
|
||
}
|
||
let openingHours;
|
||
|
||
try {
|
||
openingHours = new ohFunction(aString, {
|
||
address: {
|
||
country_code: 'de',
|
||
state: 'Hessen',
|
||
},
|
||
lon: 8.667_97,
|
||
lat: 50.129_16,
|
||
});
|
||
} catch (error) {
|
||
logger.warn(error);
|
||
this.value = '';
|
||
|
||
return;
|
||
}
|
||
|
||
const isOpen: boolean = openingHours.getState();
|
||
const nextChange: Date = openingHours.getNextChange();
|
||
|
||
let prefixKey = isOpen
|
||
? 'common.openingHours.open_until'
|
||
: 'common.openingHours.closed_until';
|
||
|
||
let formattedCalender = moment(nextChange).calendar();
|
||
|
||
if (moment(nextChange).isBefore(moment().add(1, 'hours'))) {
|
||
prefixKey = isOpen
|
||
? 'common.openingHours.closing_soon'
|
||
: 'common.openingHours.opening_soon';
|
||
formattedCalender =
|
||
formattedCalender.slice(0, 1).toUpperCase() +
|
||
formattedCalender.slice(1);
|
||
}
|
||
this.value = `${this.translate.instant(prefixKey)} ${formattedCalender}`;
|
||
}
|
||
}
|
||
|
||
@Injectable()
|
||
@Pipe({
|
||
name: 'durationLocalized',
|
||
pure: true,
|
||
})
|
||
export class DurationLocalizedPipe implements PipeTransform, OnDestroy {
|
||
locale: string;
|
||
|
||
onLangChange?: Subscription;
|
||
|
||
value: string;
|
||
|
||
frequencyPrefixes: {[iso6391Code: string]: string} = {
|
||
de: 'alle',
|
||
en: 'every',
|
||
es: 'cada',
|
||
pt: 'a cada',
|
||
fr: 'tous les',
|
||
cn: '每',
|
||
ru: 'kаждые',
|
||
};
|
||
|
||
constructor(private readonly translate: TranslateService) {
|
||
this.locale = translate.currentLang;
|
||
}
|
||
|
||
private _dispose(): void {
|
||
if (this.onLangChange?.closed === false) {
|
||
this.onLangChange?.unsubscribe();
|
||
}
|
||
}
|
||
|
||
ngOnDestroy(): void {
|
||
this._dispose();
|
||
}
|
||
|
||
/**
|
||
* @param value An ISO 8601 duration string
|
||
* @param isFrequency Boolean indicating if this duration is to be interpreted as repeat frequency
|
||
*/
|
||
transform(value: string | unknown, isFrequency = false): string {
|
||
this.updateValue(value, isFrequency);
|
||
this._dispose();
|
||
if (this.onLangChange?.closed === true) {
|
||
this.onLangChange = this.translate.onLangChange.subscribe(
|
||
(event: LangChangeEvent) => {
|
||
this.locale = event.lang;
|
||
this.updateValue(value, isFrequency);
|
||
},
|
||
);
|
||
}
|
||
|
||
return this.value;
|
||
}
|
||
|
||
updateValue(value: string | unknown, isFrequency = false): void {
|
||
if (typeof value !== 'string') {
|
||
logger.warn(`durationLocalized pipe unable to parse input: ${value}`);
|
||
|
||
return;
|
||
}
|
||
|
||
if (isFrequency) {
|
||
const fequencyPrefix = Object.keys(this.frequencyPrefixes).filter(
|
||
element => this.locale.includes(element),
|
||
);
|
||
this.value = [
|
||
fequencyPrefix.length > 0
|
||
? this.frequencyPrefixes[fequencyPrefix[0]]
|
||
: this.frequencyPrefixes.en,
|
||
moment.duration(value).humanize(),
|
||
].join(' ');
|
||
} else {
|
||
this.value = moment.duration(value).humanize();
|
||
}
|
||
}
|
||
}
|
||
|
||
@Injectable()
|
||
@Pipe({
|
||
name: 'metersLocalized',
|
||
pure: false,
|
||
})
|
||
export class MetersLocalizedPipe implements PipeTransform, OnDestroy {
|
||
locale: string;
|
||
|
||
onLangChange?: Subscription;
|
||
|
||
value = '';
|
||
|
||
constructor(private readonly translate: TranslateService) {
|
||
this.locale = translate.currentLang;
|
||
}
|
||
|
||
private _dispose(): void {
|
||
if (this.onLangChange?.closed === false) {
|
||
this.onLangChange?.unsubscribe();
|
||
}
|
||
}
|
||
|
||
ngOnDestroy(): void {
|
||
this._dispose();
|
||
}
|
||
|
||
transform(value: string | number | unknown): string {
|
||
this.updateValue(value);
|
||
this._dispose();
|
||
if (this.onLangChange?.closed === true) {
|
||
this.onLangChange = this.translate.onLangChange.subscribe(
|
||
(event: LangChangeEvent) => {
|
||
this.locale = event.lang;
|
||
this.updateValue(value);
|
||
},
|
||
);
|
||
}
|
||
|
||
return this.value;
|
||
}
|
||
|
||
updateValue(value: string | number | unknown) {
|
||
if (typeof value !== 'string' && typeof value !== 'number') {
|
||
logger.warn(`metersLocalized pipe unable to parse input: ${value}`);
|
||
|
||
return;
|
||
}
|
||
const imperialLocale = ['US', 'UK', 'LR', 'MM'].some(term =>
|
||
this.locale.includes(term),
|
||
);
|
||
const meters =
|
||
typeof value === 'string' ? Number.parseFloat(value) : (value as number);
|
||
|
||
if (imperialLocale) {
|
||
const yards = meters * 1.0936;
|
||
const options = {
|
||
style: 'unit',
|
||
unit: yards >= 1760 ? 'mile' : 'yard',
|
||
maximumFractionDigits: yards >= 1760 ? 1 : 0,
|
||
} as unknown as Intl.NumberFormatOptions;
|
||
this.value = new Intl.NumberFormat(this.locale, options).format(
|
||
yards >= 1760 ? yards / 1760 : yards,
|
||
);
|
||
} else {
|
||
const options = {
|
||
style: 'unit',
|
||
unit: meters >= 1000 ? 'kilometer' : 'meter',
|
||
maximumFractionDigits: meters >= 1000 ? 1 : 0,
|
||
} as unknown as Intl.NumberFormatOptions;
|
||
this.value = new Intl.NumberFormat(this.locale, options).format(
|
||
meters >= 1000 ? meters / 1000 : meters,
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
@Injectable()
|
||
@Pipe({
|
||
name: 'numberLocalized',
|
||
pure: true,
|
||
})
|
||
export class NumberLocalizedPipe implements PipeTransform, OnDestroy {
|
||
locale: string;
|
||
|
||
onLangChange?: Subscription;
|
||
|
||
value: string;
|
||
|
||
constructor(private readonly translate: TranslateService) {
|
||
this.locale = translate.currentLang;
|
||
}
|
||
|
||
private _dispose(): void {
|
||
if (this.onLangChange?.closed === false) {
|
||
this.onLangChange?.unsubscribe();
|
||
}
|
||
}
|
||
|
||
ngOnDestroy(): void {
|
||
this._dispose();
|
||
}
|
||
|
||
/**
|
||
* @param value The number to be formatted
|
||
* @param formatOptions Formatting options to include.
|
||
* As specified by Intl.NumberFormatOptions as comma seperated key:value pairs.
|
||
*/
|
||
transform(value: string | number | unknown, formatOptions?: string): string {
|
||
this.updateValue(value, formatOptions);
|
||
this._dispose();
|
||
if (this.onLangChange?.closed === true) {
|
||
this.onLangChange = this.translate.onLangChange.subscribe(
|
||
(event: LangChangeEvent) => {
|
||
this.locale = event.lang;
|
||
this.updateValue(value, formatOptions);
|
||
},
|
||
);
|
||
}
|
||
|
||
return this.value;
|
||
}
|
||
|
||
updateValue(value: string | number | unknown, formatOptions?: string): void {
|
||
if (typeof value !== 'string' && typeof value !== 'number') {
|
||
logger.warn(`numberLocalized pipe unable to parse input: ${value}`);
|
||
|
||
return;
|
||
}
|
||
const options = formatOptions
|
||
?.split(',')
|
||
.map(element => element.split(':'))
|
||
// eslint-disable-next-line unicorn/no-array-reduce
|
||
.reduce(
|
||
(accumulator, [key, value_]) => ({
|
||
...accumulator,
|
||
[key.trim()]: value_.trim(),
|
||
}),
|
||
{},
|
||
) as Intl.NumberFormatOptions;
|
||
const float =
|
||
typeof value === 'string' ? Number.parseFloat(value) : (value as number);
|
||
this.value = new Intl.NumberFormat(this.locale, options).format(float);
|
||
}
|
||
}
|
||
|
||
@Injectable()
|
||
@Pipe({
|
||
name: 'dateFormat',
|
||
pure: true,
|
||
})
|
||
export class DateLocalizedFormatPipe implements PipeTransform, OnDestroy {
|
||
locale: string;
|
||
|
||
onLangChange?: Subscription;
|
||
|
||
value: string;
|
||
|
||
constructor(private readonly translate: TranslateService) {
|
||
this.locale = translate.currentLang;
|
||
}
|
||
|
||
private _dispose(): void {
|
||
if (this.onLangChange?.closed === false) {
|
||
this.onLangChange?.unsubscribe();
|
||
}
|
||
}
|
||
|
||
ngOnDestroy(): void {
|
||
this._dispose();
|
||
}
|
||
|
||
/**
|
||
* @param value The date to be formatted
|
||
* @param formatOptions Dateformat options to include.
|
||
* As specified by Intl.DateTimeFormatOptions as comma seperated key:value pairs
|
||
* Default is year,month,day,hour and minute in numeric representation e.g. (en-US) "8/6/2021, 10:35"
|
||
*/
|
||
transform(value: string | unknown, formatOptions?: string): string {
|
||
this.updateValue(value, formatOptions);
|
||
this._dispose();
|
||
if (this.onLangChange?.closed === true) {
|
||
this.onLangChange = this.translate.onLangChange.subscribe(
|
||
(event: LangChangeEvent) => {
|
||
this.locale = event.lang;
|
||
this.updateValue(value, formatOptions);
|
||
},
|
||
);
|
||
}
|
||
|
||
return this.value;
|
||
}
|
||
|
||
updateValue(value: string | Date | unknown, formatOptions?: string): void {
|
||
if (
|
||
typeof value !== 'string' &&
|
||
Object.prototype.toString.call(value) !== '[object Date]'
|
||
) {
|
||
logger.warn(`dateFormat pipe unable to parse input: ${value}`);
|
||
|
||
return;
|
||
}
|
||
const options = formatOptions
|
||
?.split(',')
|
||
.map(element => element.split(':'))
|
||
// eslint-disable-next-line unicorn/no-array-reduce
|
||
.reduce(
|
||
(accumulator, [key, value_]) => ({
|
||
...accumulator,
|
||
[key.trim()]: value_.trim(),
|
||
}),
|
||
{},
|
||
) as Intl.DateTimeFormatOptions;
|
||
const date =
|
||
typeof value === 'string' ? Date.parse(value) : (value as Date);
|
||
this.value = new Intl.DateTimeFormat(
|
||
this.locale,
|
||
options ?? {
|
||
day: 'numeric',
|
||
month: 'numeric',
|
||
year: 'numeric',
|
||
hour: 'numeric',
|
||
minute: 'numeric',
|
||
},
|
||
).format(date);
|
||
}
|
||
}
|