@oardi/ts-utils 0.0.20 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extensions/array-extension.ts +54 -0
- package/extensions/date-extension.ts +69 -0
- package/extensions/{index.d.ts → index.ts} +0 -1
- package/extensions/string-extensions.ts +21 -0
- package/helpers/browser/browser-type.enum.ts +13 -0
- package/helpers/browser/browser.helper.ts +95 -0
- package/helpers/browser/{index.d.ts → index.ts} +0 -1
- package/helpers/clipboard.helper.ts +3 -0
- package/helpers/csv.helper.ts +23 -0
- package/helpers/download.helper.ts +127 -0
- package/helpers/enum.helper.ts +20 -0
- package/helpers/file.helper.ts +105 -0
- package/helpers/{index.d.ts → index.ts} +0 -1
- package/helpers/local-storage.helper.ts +30 -0
- package/helpers/logger/index.ts +1 -0
- package/helpers/logger/logger-type.enum.ts +7 -0
- package/helpers/logger/logger.helper.ts +63 -0
- package/helpers/logger/{logger.types.d.ts → logger.types.ts} +5 -4
- package/helpers/sort.helper.ts +33 -0
- package/helpers/token/{index.d.ts → index.ts} +0 -1
- package/helpers/token/{token-type.enum.d.ts → token-type.enum.ts} +0 -1
- package/helpers/token/token.helper.ts +16 -0
- package/{index.d.ts → index.ts} +0 -1
- package/interfaces/{index.d.ts → index.ts} +0 -1
- package/interfaces/keyvalue.interface.ts +7 -0
- package/package.json +1 -1
- package/services/browser/browser.consts.ts +11 -0
- package/services/browser/browser.interface.ts +6 -0
- package/services/browser/browser.service.ts +119 -0
- package/services/browser/{browser.types.d.ts → browser.types.ts} +1 -1
- package/services/browser/{index.d.ts → index.ts} +0 -1
- package/services/index.ts +1 -0
- package/tsconfig.json +33 -0
- package/types/index.ts +11 -0
- package/vite.config.ts +21 -0
- package/extensions/array-extension.d.ts +0 -13
- package/extensions/array-extension.d.ts.map +0 -1
- package/extensions/date-extension.d.ts +0 -16
- package/extensions/date-extension.d.ts.map +0 -1
- package/extensions/index.d.ts.map +0 -1
- package/extensions/string-extensions.d.ts +0 -9
- package/extensions/string-extensions.d.ts.map +0 -1
- package/helpers/browser/browser-type.enum.d.ts +0 -14
- package/helpers/browser/browser-type.enum.d.ts.map +0 -1
- package/helpers/browser/browser.helper.d.ts +0 -16
- package/helpers/browser/browser.helper.d.ts.map +0 -1
- package/helpers/browser/index.d.ts.map +0 -1
- package/helpers/clipboard.helper.d.ts +0 -2
- package/helpers/clipboard.helper.d.ts.map +0 -1
- package/helpers/csv.helper.d.ts +0 -6
- package/helpers/csv.helper.d.ts.map +0 -1
- package/helpers/download.helper.d.ts +0 -14
- package/helpers/download.helper.d.ts.map +0 -1
- package/helpers/enum.helper.d.ts +0 -6
- package/helpers/enum.helper.d.ts.map +0 -1
- package/helpers/file.helper.d.ts +0 -16
- package/helpers/file.helper.d.ts.map +0 -1
- package/helpers/index.d.ts.map +0 -1
- package/helpers/local-storage.helper.d.ts +0 -8
- package/helpers/local-storage.helper.d.ts.map +0 -1
- package/helpers/logger/index.d.ts +0 -2
- package/helpers/logger/index.d.ts.map +0 -1
- package/helpers/logger/logger-type.enum.d.ts +0 -8
- package/helpers/logger/logger-type.enum.d.ts.map +0 -1
- package/helpers/logger/logger.helper.d.ts +0 -12
- package/helpers/logger/logger.helper.d.ts.map +0 -1
- package/helpers/logger/logger.types.d.ts.map +0 -1
- package/helpers/sort.helper.d.ts +0 -2
- package/helpers/sort.helper.d.ts.map +0 -1
- package/helpers/token/index.d.ts.map +0 -1
- package/helpers/token/token-type.enum.d.ts.map +0 -1
- package/helpers/token/token.helper.d.ts +0 -7
- package/helpers/token/token.helper.d.ts.map +0 -1
- package/index.cjs.js +0 -15
- package/index.d.ts.map +0 -1
- package/index.es.js +0 -1111
- package/index.umd.js +0 -15
- package/interfaces/index.d.ts.map +0 -1
- package/interfaces/keyvalue.interface.d.ts +0 -5
- package/interfaces/keyvalue.interface.d.ts.map +0 -1
- package/readme.md +0 -15
- package/services/browser/browser.consts.d.ts +0 -3
- package/services/browser/browser.consts.d.ts.map +0 -1
- package/services/browser/browser.interface.d.ts +0 -6
- package/services/browser/browser.interface.d.ts.map +0 -1
- package/services/browser/browser.service.d.ts +0 -30
- package/services/browser/browser.service.d.ts.map +0 -1
- package/services/browser/browser.types.d.ts.map +0 -1
- package/services/browser/index.d.ts.map +0 -1
- package/services/index.d.ts +0 -2
- package/services/index.d.ts.map +0 -1
- package/types/index.d.ts +0 -9
- package/types/index.d.ts.map +0 -1
- package/vite.config.d.ts +0 -3
- package/vite.config.d.ts.map +0 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { sortHelper } from '../helpers';
|
|
2
|
+
import { Dictionary } from '../types';
|
|
3
|
+
|
|
4
|
+
export {};
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface Array<T> {
|
|
8
|
+
distinct<T>(comparator?: (obj1: unknown, obj2: unknown) => boolean): T[];
|
|
9
|
+
filterBy<T>(valueGetter: (a: T) => boolean): T[];
|
|
10
|
+
first(): T;
|
|
11
|
+
groupBy(valueGetter: (a: T) => string): Dictionary<T[]>;
|
|
12
|
+
orderBy<T, G>(valueGetter: (a: T) => G, ascending?: boolean): T[];
|
|
13
|
+
removeBy<T, G>(valueGetter: (a: T) => G, val: G): T[];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
Array.prototype.distinct = function <T>(comparator?: (obj1: unknown, obj2: unknown) => boolean): T[] {
|
|
18
|
+
const thisAsArray: T[] = this as T[];
|
|
19
|
+
const result: T[] = comparator
|
|
20
|
+
? thisAsArray.filter((value, index, array) => array.findIndex((o) => comparator(o, value)) === index)
|
|
21
|
+
: thisAsArray.filter((value, index, array) => array.indexOf(value) === index);
|
|
22
|
+
return result;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
Array.prototype.filterBy = function <T>(valueGetter: (a: T) => boolean): T[] {
|
|
26
|
+
return this.filter((obj) => {
|
|
27
|
+
return valueGetter(obj);
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
Array.prototype.first = function <T>(): Nullable<T> {
|
|
32
|
+
return this?.length > 0 ? this[0] : null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
Array.prototype.groupBy = function <T>(valueGetter: (a: T) => string): Dictionary<T> {
|
|
36
|
+
return this.reduce((curr, obj) => {
|
|
37
|
+
const key: string = valueGetter(obj);
|
|
38
|
+
(curr[key] = curr[key] || []).push(obj);
|
|
39
|
+
return curr;
|
|
40
|
+
}, {});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
Array.prototype.orderBy = function <T, G>(valueGetter: (a: T) => G, ascending: boolean = true): T[] {
|
|
44
|
+
this?.sort((a, b) => sortHelper(a, b, valueGetter, ascending));
|
|
45
|
+
return this;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
Array.prototype.removeBy = function <T, G>(valueGetter: (a: T) => G, val: G): T[] {
|
|
49
|
+
return this.filter((curr) => {
|
|
50
|
+
const key: G = valueGetter(curr);
|
|
51
|
+
// change to split to remove only first item found
|
|
52
|
+
return key !== val;
|
|
53
|
+
});
|
|
54
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ConfigType, ManipulateType, OpUnitType, UnitType } from 'dayjs';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
|
4
|
+
dayjs.extend(localizedFormat);
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface Date {
|
|
8
|
+
isAfter(date: ConfigType, unit?: OpUnitType): boolean;
|
|
9
|
+
isBefore(date: ConfigType, unit?: OpUnitType): boolean;
|
|
10
|
+
format(template?: string): string;
|
|
11
|
+
isValid(): boolean;
|
|
12
|
+
add(value: number, unit?: ManipulateType): Date;
|
|
13
|
+
subtract(value: number, unit?: ManipulateType): Date;
|
|
14
|
+
firstDayOfMonth(): Date;
|
|
15
|
+
lastDayOfMonth(): Date;
|
|
16
|
+
set(unit: UnitType, value: number): Date;
|
|
17
|
+
diff(date: Date, unit?: UnitType): number;
|
|
18
|
+
startOf(unit: UnitType): Date;
|
|
19
|
+
endOf(unit: UnitType): Date;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Date.prototype.isAfter = function (date: dayjs.ConfigType, unit?: dayjs.OpUnitType): boolean {
|
|
24
|
+
return dayjs(this).isAfter(date, unit);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
Date.prototype.isBefore = function (date: dayjs.ConfigType, unit?: dayjs.OpUnitType): boolean {
|
|
28
|
+
return dayjs(this).isBefore(date, unit);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
Date.prototype.format = function (template?: string): string {
|
|
32
|
+
return dayjs(this).format(template);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
Date.prototype.isValid = function (): boolean {
|
|
36
|
+
return dayjs(this).isValid();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
Date.prototype.add = function (value: number, unit?: ManipulateType): Date {
|
|
40
|
+
return dayjs(this).add(value, unit).toDate();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
Date.prototype.subtract = function (value: number, unit?: ManipulateType): Date {
|
|
44
|
+
return dayjs(this).subtract(value, unit).toDate();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
Date.prototype.firstDayOfMonth = function (): Date {
|
|
48
|
+
return dayjs(this).startOf('month').toDate();
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
Date.prototype.lastDayOfMonth = function (): Date {
|
|
52
|
+
return dayjs(this).endOf('month').toDate();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
Date.prototype.set = function (unit: UnitType, value: number): Date {
|
|
56
|
+
return dayjs(this).set(unit, value).toDate();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
Date.prototype.diff = function (date: Date, unit?: UnitType): number {
|
|
60
|
+
return dayjs(this).diff(date, unit);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
Date.prototype.startOf = function (unit: UnitType): Date {
|
|
64
|
+
return dayjs(this).startOf(unit).toDate();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
Date.prototype.endOf = function (unit: UnitType): Date {
|
|
68
|
+
return dayjs(this).endOf(unit).toDate();
|
|
69
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export {};
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface String {
|
|
5
|
+
capitalize(): string;
|
|
6
|
+
isNumber(): boolean;
|
|
7
|
+
isEmpty(): boolean;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
String.prototype.capitalize = function (): string {
|
|
12
|
+
return this.charAt(0).toUpperCase() + this.slice(1);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
String.prototype.isNumber = function (): boolean {
|
|
16
|
+
return !isNaN(Number(this)) && this.trim() !== '';
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
String.prototype.isEmpty = function (): boolean {
|
|
20
|
+
return this === undefined || !this || this.length === 0;
|
|
21
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export enum BrowserType {
|
|
2
|
+
opera = 'opera',
|
|
3
|
+
firefox = 'firefox',
|
|
4
|
+
safari = 'safari',
|
|
5
|
+
ie = 'ie',
|
|
6
|
+
edge = 'edge',
|
|
7
|
+
edg = 'edg',
|
|
8
|
+
electron = 'electron',
|
|
9
|
+
chrome = 'chrome',
|
|
10
|
+
iOsSafari = 'iOsSafari',
|
|
11
|
+
edgiOs = 'edgiOs',
|
|
12
|
+
headlessChrome = 'headlessChrome',
|
|
13
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { BrowserType } from './browser-type.enum';
|
|
2
|
+
|
|
3
|
+
export class BrowserHelper {
|
|
4
|
+
static isOpera(): boolean {
|
|
5
|
+
return (
|
|
6
|
+
typeof window !== 'undefined' &&
|
|
7
|
+
((!!(window as any).opr && !!(window as any).opr.addons) ||
|
|
8
|
+
!!(window as any).opera ||
|
|
9
|
+
navigator.userAgent.includes('OPR/'))
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static isFirefox(): boolean {
|
|
14
|
+
return typeof (globalThis as any).InstallTrigger !== 'undefined';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static isSafari(): boolean {
|
|
18
|
+
if (typeof window === 'undefined') return false;
|
|
19
|
+
return (
|
|
20
|
+
/constructor/i.test((window as any).HTMLElement) ||
|
|
21
|
+
((p: any) => p.toString() === '[object SafariRemoteNotification]')(
|
|
22
|
+
!(window as any)['safari'] || ((window as any)['safari'] && (window as any)['safari'].pushNotification)
|
|
23
|
+
)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static isEdge(): boolean {
|
|
28
|
+
if (typeof window === 'undefined') return false;
|
|
29
|
+
return !BrowserHelper.isIE() && !!(window as any).StyleMedia;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static isIE(): boolean {
|
|
33
|
+
if (typeof document === 'undefined') return false;
|
|
34
|
+
return !!(document as any).documentMode;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static isEdg(): boolean {
|
|
38
|
+
if (typeof navigator === 'undefined') return false;
|
|
39
|
+
return navigator.userAgent.toLowerCase().includes('edg');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static isElectron(userAgentLower: string): boolean {
|
|
43
|
+
return userAgentLower.includes('electron');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static isChrome(): boolean {
|
|
47
|
+
if (typeof window === 'undefined') return false;
|
|
48
|
+
return (
|
|
49
|
+
!!(window as any).chrome &&
|
|
50
|
+
!BrowserHelper.isOpera() &&
|
|
51
|
+
!BrowserHelper.isEdge() &&
|
|
52
|
+
!BrowserHelper.isEdg() &&
|
|
53
|
+
!BrowserHelper.isFirefox() &&
|
|
54
|
+
!BrowserHelper.isSafari() &&
|
|
55
|
+
!BrowserHelper.isIE()
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static isIOsSafari(userAgentLower: string): boolean {
|
|
60
|
+
return (
|
|
61
|
+
userAgentLower.includes('iphone') ||
|
|
62
|
+
userAgentLower.includes('ipad') ||
|
|
63
|
+
(userAgentLower.includes('macintosh') &&
|
|
64
|
+
typeof document !== 'undefined' &&
|
|
65
|
+
'ontouchend' in document &&
|
|
66
|
+
userAgentLower.includes('safari'))
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static isEdgiOs(userAgentLower: string): boolean {
|
|
71
|
+
return userAgentLower.includes('edgios');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static isHeadlessChrome(userAgentLower: string): boolean {
|
|
75
|
+
return userAgentLower.includes('headlesschrome');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public getCurrentBrowser(): BrowserType {
|
|
79
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
80
|
+
|
|
81
|
+
if (BrowserHelper.isOpera()) return BrowserType.opera;
|
|
82
|
+
if (BrowserHelper.isFirefox()) return BrowserType.firefox;
|
|
83
|
+
if (BrowserHelper.isSafari()) return BrowserType.safari;
|
|
84
|
+
if (BrowserHelper.isIE()) return BrowserType.ie;
|
|
85
|
+
if (BrowserHelper.isEdge()) return BrowserType.edge;
|
|
86
|
+
if (BrowserHelper.isEdg()) return BrowserType.edg;
|
|
87
|
+
if (BrowserHelper.isElectron(userAgent)) return BrowserType.electron;
|
|
88
|
+
if (BrowserHelper.isChrome()) return BrowserType.chrome;
|
|
89
|
+
if (BrowserHelper.isIOsSafari(userAgent)) return BrowserType.iOsSafari;
|
|
90
|
+
if (BrowserHelper.isEdgiOs(userAgent)) return BrowserType.edgiOs;
|
|
91
|
+
if (BrowserHelper.isHeadlessChrome(userAgent)) return BrowserType.headlessChrome;
|
|
92
|
+
|
|
93
|
+
throw new Error('Browser could not be detected: ' + userAgent);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ParseResult, UnparseConfig, UnparseObject } from 'papaparse';
|
|
2
|
+
import pkg from 'papaparse';
|
|
3
|
+
const { parse, unparse } = pkg;
|
|
4
|
+
|
|
5
|
+
export class CsvHelper {
|
|
6
|
+
static unparse<T>(data: T[] | UnparseObject<T>, config?: UnparseConfig): string {
|
|
7
|
+
return unparse(data, config);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static parse<T>(content: string | File): Promise<ParseResult<T>> {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
parse(content, {
|
|
13
|
+
header: true,
|
|
14
|
+
complete: (results: ParseResult<T>) => resolve(results),
|
|
15
|
+
error: (err: Error) => reject(err),
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// usage
|
|
22
|
+
// const csvData: string = CsvHelper.unparse(data);
|
|
23
|
+
// const result: ParseResult<TranslationCsvDataModel> = CsvHelper.parse(file);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export type SaveSource = Blob | ArrayBuffer | Uint8Array | string;
|
|
2
|
+
|
|
3
|
+
export interface SaveOptions {
|
|
4
|
+
mimeType?: string; /** MIME-Type, falls `source` kein Blob ist (z. B. "application/pdf") */
|
|
5
|
+
showSaveFilePicker?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* save file on client
|
|
10
|
+
*
|
|
11
|
+
* 1) File System Access API (showSaveFilePicker) – if supported
|
|
12
|
+
* 2) Fallback: <a download> + blob: URL
|
|
13
|
+
* 3) iOS-/Safari-Fallback: open new Tab if download-attribute is being ignored
|
|
14
|
+
*/
|
|
15
|
+
export async function saveAs(source: SaveSource, fileName: string, options?: SaveOptions): Promise<void> {
|
|
16
|
+
const mime: string = options?.mimeType ?? guessMimeFromFilename(fileName);
|
|
17
|
+
const blob: Blob = ensureBlob(source, mime);
|
|
18
|
+
|
|
19
|
+
// 1) experimental - feature check
|
|
20
|
+
if ('showSaveFilePicker' in window && options?.showSaveFilePicker) {
|
|
21
|
+
try {
|
|
22
|
+
const handle: FileSystemFileHandle = await (
|
|
23
|
+
window as unknown as {
|
|
24
|
+
showSaveFilePicker: (opts: {
|
|
25
|
+
suggestedName?: string;
|
|
26
|
+
types?: { description?: string; accept: Record<string, string[]> }[];
|
|
27
|
+
}) => Promise<FileSystemFileHandle>;
|
|
28
|
+
}
|
|
29
|
+
).showSaveFilePicker({
|
|
30
|
+
suggestedName: fileName,
|
|
31
|
+
types: [{ description: 'Datei', accept: { [mime]: [`.${extOf(fileName)}`] } }],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const writable: FileSystemWritableFileStream = await handle.createWritable();
|
|
35
|
+
await writable.write(blob);
|
|
36
|
+
await writable.close();
|
|
37
|
+
return;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
// Abbruch durch Nutzer oder fehlende Berechtigung -> auf Fallback gehen
|
|
40
|
+
// (keine Early-Return mitten in der Funktion – wir laufen weiter)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 2) classic: <a download> + blob: URL
|
|
45
|
+
const url: string = URL.createObjectURL(blob);
|
|
46
|
+
|
|
47
|
+
// In iOS Safari kann `download` für blob:-URLs ignoriert werden.
|
|
48
|
+
// Wir versuchen es zuerst regulär, und fallbacken dann auf "neuen Tab öffnen".
|
|
49
|
+
const anchor: HTMLAnchorElement = document.createElement('a');
|
|
50
|
+
anchor.href = url;
|
|
51
|
+
anchor.download = fileName;
|
|
52
|
+
anchor.rel = 'noopener'; // gute Praxis
|
|
53
|
+
anchor.style.display = 'none';
|
|
54
|
+
document.body.appendChild(anchor);
|
|
55
|
+
|
|
56
|
+
triggerClick(anchor);
|
|
57
|
+
|
|
58
|
+
// Kurzer Delay, dann Cleanup + optionaler iOS-Fallback
|
|
59
|
+
window.setTimeout(() => {
|
|
60
|
+
document.body.removeChild(anchor);
|
|
61
|
+
URL.revokeObjectURL(url);
|
|
62
|
+
}, 0);
|
|
63
|
+
|
|
64
|
+
// Heuristik: Wenn Safari das download-Attribut ignoriert, bleibt häufig dieselbe Seite,
|
|
65
|
+
// ohne dass ein Download-Dialog erscheint. Wir bieten dann eine Sicht-Alternative an.
|
|
66
|
+
// (Bewusst defensiv: wir öffnen nur, wenn `download` potenziell ignoriert wird.)
|
|
67
|
+
if (isProbablyIOSorOldSafari()) {
|
|
68
|
+
// Öffnet die Ressource in einem neuen Tab, damit der Nutzer manuell speichern kann.
|
|
69
|
+
// (Für PDFs & Bilder oft ausreichend; für Binärdateien hilft korrekter MIME-Type.)
|
|
70
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function ensureBlob(source: SaveSource, mime: string): Blob {
|
|
75
|
+
if (source instanceof Blob) {
|
|
76
|
+
return source.type ? source : new Blob([source], { type: mime });
|
|
77
|
+
}
|
|
78
|
+
if (source instanceof ArrayBuffer || source instanceof Uint8Array) {
|
|
79
|
+
const buf: Uint8Array<ArrayBufferLike> = source instanceof Uint8Array ? source : new Uint8Array(source);
|
|
80
|
+
return new Blob([buf as BlobPart], { type: mime });
|
|
81
|
+
}
|
|
82
|
+
// string -> als Text-BLOB speichern
|
|
83
|
+
return new Blob([source], { type: mime || 'text/plain;charset=utf-8' });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function guessMimeFromFilename(filename: string): string {
|
|
87
|
+
const ext: string = extOf(filename).toLowerCase();
|
|
88
|
+
switch (ext) {
|
|
89
|
+
case 'txt':
|
|
90
|
+
return 'text/plain;charset=utf-8';
|
|
91
|
+
case 'csv':
|
|
92
|
+
return 'text/csv;charset=utf-8';
|
|
93
|
+
case 'json':
|
|
94
|
+
return 'application/json;charset=utf-8';
|
|
95
|
+
case 'pdf':
|
|
96
|
+
return 'application/pdf';
|
|
97
|
+
case 'png':
|
|
98
|
+
return 'image/png';
|
|
99
|
+
case 'jpg':
|
|
100
|
+
case 'jpeg':
|
|
101
|
+
return 'image/jpeg';
|
|
102
|
+
case 'svg':
|
|
103
|
+
return 'image/svg+xml';
|
|
104
|
+
case 'zip':
|
|
105
|
+
return 'application/zip';
|
|
106
|
+
default:
|
|
107
|
+
return 'application/octet-stream';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function extOf(filename: string): string {
|
|
112
|
+
const idx: number = filename.lastIndexOf('.');
|
|
113
|
+
return idx >= 0 && idx < filename.length - 1 ? filename.slice(idx + 1) : '';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function triggerClick(el: HTMLElement): void {
|
|
117
|
+
const evt: MouseEvent = document.createEvent('MouseEvents');
|
|
118
|
+
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
|
119
|
+
el.dispatchEvent(evt);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isProbablyIOSorOldSafari(): boolean {
|
|
123
|
+
const userAgent: string = navigator.userAgent;
|
|
124
|
+
const isIOS: boolean = /iPad|iPhone|iPod/.test(userAgent);
|
|
125
|
+
const isSafari: boolean = /^((?!chrome|android).)*safari/i.test(userAgent);
|
|
126
|
+
return isIOS || isSafari;
|
|
127
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IKeyValue } from '../interfaces/keyvalue.interface';
|
|
2
|
+
|
|
3
|
+
export class EnumHelper {
|
|
4
|
+
static getEnumValue<T extends object>(enumObj: T, key: string): T[keyof T] {
|
|
5
|
+
if (!(key in enumObj)) {
|
|
6
|
+
throw Error(`${key} not found in ${enumObj}`);
|
|
7
|
+
} else {
|
|
8
|
+
return enumObj[key as keyof T];
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static getKeyValues<T extends object>(enumObj: T): IKeyValue<string>[] {
|
|
13
|
+
return Object.keys(enumObj)
|
|
14
|
+
.filter(key => !isNaN(Number(key)))
|
|
15
|
+
.map(key => ({
|
|
16
|
+
key: Number(key),
|
|
17
|
+
value: enumObj[key as unknown as keyof T] as string,
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { saveAs } from './download.helper';
|
|
2
|
+
|
|
3
|
+
export class FileHelper {
|
|
4
|
+
static getFileExtension(filename: string): string {
|
|
5
|
+
let result: string = '';
|
|
6
|
+
const index: number = filename.lastIndexOf('.');
|
|
7
|
+
const hasExtension: boolean = index > 0 && index < filename.length - 1; // index > 0 verhindert z.B.: ".bashrc" als Extension
|
|
8
|
+
if (hasExtension) {
|
|
9
|
+
const ext: string = filename.substring(index + 1);
|
|
10
|
+
result = `.${ext.toLowerCase()}`;
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* read content from a file or blob
|
|
17
|
+
*/
|
|
18
|
+
static readAsText(file: File): Promise<string> {
|
|
19
|
+
const fileReader: FileReader = new FileReader();
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
fileReader.onload = (): void => {
|
|
22
|
+
fileReader.onload = null;
|
|
23
|
+
fileReader.onerror = null;
|
|
24
|
+
if (typeof fileReader.result === 'string') {
|
|
25
|
+
resolve(fileReader.result);
|
|
26
|
+
} else {
|
|
27
|
+
reject(new Error('Unexpected result type'));
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
fileReader.onerror = (): void => {
|
|
31
|
+
const error = fileReader.error ?? new Error('FileReader error');
|
|
32
|
+
fileReader.onload = null;
|
|
33
|
+
fileReader.onerror = null;
|
|
34
|
+
reject(error);
|
|
35
|
+
};
|
|
36
|
+
fileReader.readAsText(file);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static readAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
|
41
|
+
const fileReader: FileReader = new FileReader();
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
fileReader.onload = (): void => {
|
|
44
|
+
fileReader.onload = null;
|
|
45
|
+
fileReader.onerror = null;
|
|
46
|
+
if (fileReader.result instanceof ArrayBuffer) {
|
|
47
|
+
resolve(fileReader.result);
|
|
48
|
+
} else {
|
|
49
|
+
reject(new Error('Unexpected result type'));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
fileReader.onerror = (): void => {
|
|
53
|
+
const error = fileReader.error ?? new Error('FileReader error');
|
|
54
|
+
fileReader.onload = null;
|
|
55
|
+
fileReader.onerror = null;
|
|
56
|
+
reject(error);
|
|
57
|
+
};
|
|
58
|
+
fileReader.readAsArrayBuffer(file);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static readAsDataUrl(file: File): Promise<ProgressEvent<FileReader>> {
|
|
63
|
+
const reader: FileReader = new FileReader();
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
reader.onload = (event: ProgressEvent<FileReader>): void => {
|
|
66
|
+
resolve(event);
|
|
67
|
+
};
|
|
68
|
+
reader.onerror = (error): void => reject(error);
|
|
69
|
+
reader.readAsDataURL(file);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** usage:
|
|
74
|
+
* const file = new File(['Hello'], 'test.txt');
|
|
75
|
+
* FileHelper.formatFileSize(file.size);
|
|
76
|
+
* */
|
|
77
|
+
static formatFileSize(bytes: number, decimals: number = 2, unit?: 'Bytes' | 'KB' | 'MB' | 'GB' | 'TB'): string {
|
|
78
|
+
if (bytes === 0) {
|
|
79
|
+
return '0 Bytes';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const k = 1024;
|
|
83
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
84
|
+
|
|
85
|
+
let i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
86
|
+
|
|
87
|
+
if (unit && sizes.includes(unit)) {
|
|
88
|
+
i = sizes.indexOf(unit);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const value = bytes / Math.pow(k, i);
|
|
92
|
+
const fixed = parseFloat(value.toFixed(decimals));
|
|
93
|
+
|
|
94
|
+
return `${fixed} ${sizes[i]}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static save(content: BlobPart, fileName: string, fileType: string): void {
|
|
98
|
+
const blob: Blob = new Blob([content], { type: fileType });
|
|
99
|
+
saveAs(blob, fileName);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// usage
|
|
104
|
+
// const extension: string = FileHelper.getFileExtension('asdf.txt');
|
|
105
|
+
// -> .txt
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class LocalStorageHelper {
|
|
2
|
+
static set<T>(key: string, value: T): void {
|
|
3
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
static get<T>(key: string): Nullable<T> {
|
|
7
|
+
let result: Nullable<T> = null;
|
|
8
|
+
const local: Nullable<string> = localStorage.getItem(key);
|
|
9
|
+
if (local) {
|
|
10
|
+
result = JSON.parse(local);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static remove(key: string): boolean {
|
|
16
|
+
let result: boolean = !!LocalStorageHelper.get(key);
|
|
17
|
+
if (result) {
|
|
18
|
+
localStorage.removeItem(key);
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static removeAll(): void {
|
|
24
|
+
localStorage.clear();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static getKeysBy(key: string): string[] {
|
|
28
|
+
return Object.keys(localStorage).filter(k => k.startsWith(key));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './logger.helper';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { LoggerType } from './logger-type.enum';
|
|
2
|
+
import { LoggerListener, LoggerPayload } from './logger.types';
|
|
3
|
+
|
|
4
|
+
export class LoggerHelper {
|
|
5
|
+
private static listeners: Set<LoggerListener> = new Set();
|
|
6
|
+
|
|
7
|
+
public static log(...args: unknown[]): void {
|
|
8
|
+
LoggerHelper.doLog(LoggerType.log, args);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public static info(...args: unknown[]): void {
|
|
12
|
+
LoggerHelper.doLog(LoggerType.info, args);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public static warn(...args: unknown[]): void {
|
|
16
|
+
LoggerHelper.doLog(LoggerType.warn, args);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public static debug(...args: unknown[]): void {
|
|
20
|
+
LoggerHelper.doLog(LoggerType.debug, args);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public static error(...args: unknown[]): void {
|
|
24
|
+
LoggerHelper.doLog(LoggerType.error, args);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private static doLog(level: LoggerType, args: unknown[]): void {
|
|
28
|
+
const payload: LoggerPayload = {
|
|
29
|
+
type: level,
|
|
30
|
+
args,
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
};
|
|
33
|
+
for (const listener of LoggerHelper.listeners) {
|
|
34
|
+
try {
|
|
35
|
+
const res = listener(payload);
|
|
36
|
+
if (res === false) {
|
|
37
|
+
return; // cancelled by listener
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// swallow listener errors so logging never breaks the app
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
(console as Console)[level].apply(console, args);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public static onLog(listener: LoggerListener, once = false): () => void {
|
|
48
|
+
if (once) {
|
|
49
|
+
const wrapper: LoggerListener = (payload) => {
|
|
50
|
+
try {
|
|
51
|
+
return listener(payload);
|
|
52
|
+
} finally {
|
|
53
|
+
LoggerHelper.listeners.delete(wrapper);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
LoggerHelper.listeners.add(wrapper);
|
|
57
|
+
return () => LoggerHelper.listeners.delete(wrapper);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
LoggerHelper.listeners.add(listener);
|
|
61
|
+
return () => LoggerHelper.listeners.delete(listener);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { LoggerType } from './logger-type.enum';
|
|
2
|
+
|
|
2
3
|
export type LoggerPayload = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
type: LoggerType;
|
|
5
|
+
args: unknown[];
|
|
6
|
+
timestamp: string;
|
|
6
7
|
};
|
|
8
|
+
|
|
7
9
|
export type LoggerListener = (payload: LoggerPayload) => void | boolean;
|
|
8
|
-
//# sourceMappingURL=logger.types.d.ts.map
|