2016-03-12 22:19:24 +01:00
|
|
|
export class Utils {
|
2021-04-18 15:48:35 +02:00
|
|
|
static GUID(): string {
|
2022-04-04 19:37:31 +02:00
|
|
|
const s4 = (): string =>
|
2024-01-03 11:06:19 +01:00
|
|
|
Math.floor((1 + Math.random()) * 0x10000)
|
|
|
|
.toString(16)
|
|
|
|
.substring(1);
|
2019-07-27 22:56:12 +02:00
|
|
|
|
|
|
|
return s4() + s4() + '-' + s4() + s4();
|
|
|
|
}
|
|
|
|
|
2018-12-02 12:22:05 +01:00
|
|
|
static chunkArrays<T>(arr: T[], chunkSize: number): T[][] {
|
|
|
|
const R = [];
|
|
|
|
for (let i = 0; i < arr.length; i += chunkSize) {
|
|
|
|
R.push(arr.slice(i, i + chunkSize));
|
|
|
|
}
|
|
|
|
return R;
|
|
|
|
}
|
2016-03-12 22:19:24 +01:00
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
static wait(time: number): Promise<unknown> {
|
|
|
|
return new Promise((resolve): void => {
|
2018-12-07 22:06:13 +01:00
|
|
|
setTimeout(resolve, time);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-04-10 10:51:33 +02:00
|
|
|
static removeNullOrEmptyObj<T extends { [key: string]: any }>(obj: T): T {
|
2018-12-05 17:29:33 +01:00
|
|
|
if (typeof obj !== 'object' || obj == null) {
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
const keys = Object.keys(obj);
|
2021-04-18 15:48:35 +02:00
|
|
|
for (const key of keys) {
|
2018-12-05 17:29:33 +01:00
|
|
|
if (obj[key] !== null && typeof obj[key] === 'object') {
|
|
|
|
if (Utils.removeNullOrEmptyObj(obj[key])) {
|
|
|
|
if (Object.keys(obj[key]).length === 0) {
|
|
|
|
delete obj[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (obj[key] === null) {
|
|
|
|
delete obj[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
2017-06-10 22:32:56 +02:00
|
|
|
static clone<T>(object: T): T {
|
|
|
|
return JSON.parse(JSON.stringify(object));
|
|
|
|
}
|
|
|
|
|
2021-05-30 15:09:47 +02:00
|
|
|
static shallowClone<T>(object: T): T {
|
|
|
|
const c: any = {};
|
|
|
|
for (const e of Object.entries(object)) {
|
2021-05-30 17:26:07 +02:00
|
|
|
c[e[0]] = e[1];
|
2021-05-30 15:09:47 +02:00
|
|
|
}
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
static zeroPrefix(value: string | number, length: number): string {
|
2018-11-17 20:15:48 +01:00
|
|
|
const ret = '00000' + value;
|
|
|
|
return ret.substr(ret.length - length);
|
|
|
|
}
|
|
|
|
|
2021-02-14 12:57:05 +01:00
|
|
|
/**
|
|
|
|
* Checks if the two input (let them be objects or arrays or just primitives) are equal
|
|
|
|
*/
|
2022-12-28 19:12:18 +01:00
|
|
|
static equalsFilter(object: any, filter: any, skipProp: string[] = []): boolean {
|
2018-05-12 12:19:51 -04:00
|
|
|
if (typeof filter !== 'object' || filter == null) {
|
|
|
|
return object === filter;
|
2017-07-15 12:47:11 +02:00
|
|
|
}
|
2018-05-28 14:03:12 -04:00
|
|
|
if (!object) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-02-04 19:37:47 +01:00
|
|
|
|
|
|
|
if (Array.isArray(object) && object.length !== filter.length) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-07-08 12:43:42 +02:00
|
|
|
const keys = Object.keys(filter);
|
2021-04-18 15:48:35 +02:00
|
|
|
for (const key of keys) {
|
2022-12-28 19:12:18 +01:00
|
|
|
if (skipProp.includes(key)) {
|
|
|
|
continue;
|
|
|
|
}
|
2018-05-12 12:19:51 -04:00
|
|
|
if (typeof filter[key] === 'object') {
|
2022-12-28 19:12:18 +01:00
|
|
|
if (Utils.equalsFilter(object[key], filter[key], skipProp) === false) {
|
2017-07-08 12:43:42 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if (object[key] !== filter[key]) {
|
2017-06-10 22:32:56 +02:00
|
|
|
return false;
|
|
|
|
}
|
2016-03-12 22:19:24 +01:00
|
|
|
}
|
|
|
|
|
2017-06-10 22:32:56 +02:00
|
|
|
return true;
|
|
|
|
}
|
2016-07-07 12:19:08 +02:00
|
|
|
|
2023-09-03 18:35:57 +02:00
|
|
|
static toIsoString(d: number | Date) {
|
|
|
|
if (!(d instanceof Date)) {
|
|
|
|
d = new Date(d);
|
|
|
|
}
|
|
|
|
return d.getUTCFullYear() + '-' + d.getUTCMonth() + '-' + d.getUTCDate();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static makeUTCMidnight(d: number | Date) {
|
|
|
|
if (!(d instanceof Date)) {
|
|
|
|
d = new Date(d);
|
|
|
|
}
|
|
|
|
d.setUTCHours(0);
|
|
|
|
d.setUTCMinutes(0);
|
|
|
|
d.setUTCSeconds(0);
|
|
|
|
d.setUTCMilliseconds(0);
|
|
|
|
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
2024-02-16 19:17:31 +01:00
|
|
|
static getUTCFullYear(d: number | Date, offset: string) {
|
|
|
|
if (!(d instanceof Date)) {
|
|
|
|
d = new Date(d);
|
|
|
|
}
|
|
|
|
return new Date(new Date(d).toISOString().substring(0,19) + (offset ? offset : '')).getUTCFullYear();
|
|
|
|
}
|
|
|
|
|
|
|
|
static getFullYear(d: number | Date, offset: string) {
|
|
|
|
if (!(d instanceof Date)) {
|
|
|
|
d = new Date(d);
|
|
|
|
}
|
|
|
|
return new Date(new Date(d).toISOString().substring(0,19) + (offset ? offset : '')).getFullYear();
|
|
|
|
}
|
|
|
|
|
2024-03-13 19:56:28 +01:00
|
|
|
//function to convert timestamp into milliseconds taking offset into account
|
|
|
|
static timestampToMS(timestamp: string, offset: string) {
|
|
|
|
if (!timestamp) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
//replace : with - in the yyyy-mm-dd part of the timestamp.
|
|
|
|
let formattedTimestamp = timestamp.substring(0,9).replaceAll(':', '-') + timestamp.substring(9,timestamp.length);
|
|
|
|
if (formattedTimestamp.indexOf("Z") > 0) { //replace Z (and what comes after the Z) with offset
|
2024-03-31 14:57:20 +02:00
|
|
|
formattedTimestamp = formattedTimestamp.substring(0, formattedTimestamp.indexOf("Z")) + (offset ? offset : '+00:00');
|
2024-03-13 19:56:28 +01:00
|
|
|
} else if (formattedTimestamp.indexOf("+") > 0) { //don't do anything
|
|
|
|
} else { //add offset
|
|
|
|
formattedTimestamp = formattedTimestamp + (offset ? offset : '+00:00');
|
|
|
|
}
|
|
|
|
//parse into MS and return
|
|
|
|
return Date.parse(formattedTimestamp);
|
|
|
|
}
|
|
|
|
|
2024-03-30 13:58:06 +01:00
|
|
|
//function to extract offset string from timestamp string, returns undefined if timestamp does not contain offset
|
|
|
|
static timestampToOffsetString(timestamp: string) {
|
|
|
|
try {
|
|
|
|
const idx = timestamp.indexOf("+");
|
|
|
|
if (idx > 0) {
|
|
|
|
return timestamp.substring(idx, timestamp.length);
|
|
|
|
}
|
|
|
|
if (timestamp.indexOf("Z") > 0) {
|
|
|
|
return '+00:00';
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
} catch (err) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-13 19:56:28 +01:00
|
|
|
//function to calculate offset from exif.exif.gpsTimeStamp or exif.gps.GPSDateStamp + exif.gps.GPSTimestamp
|
|
|
|
static getTimeOffsetByGPSStamp(timestamp: string, gpsTimeStamp: string, gps: any) {
|
|
|
|
let UTCTimestamp = gpsTimeStamp;
|
|
|
|
if (!UTCTimestamp &&
|
|
|
|
gps &&
|
|
|
|
gps.GPSDateStamp &&
|
|
|
|
gps.GPSTimeStamp) { //else use exif.gps.GPS*Stamp if available
|
|
|
|
//GPS timestamp is always UTC (+00:00)
|
|
|
|
UTCTimestamp = gps.GPSDateStamp.replaceAll(':', '-') + gps.GPSTimeStamp.join(':');
|
|
|
|
}
|
|
|
|
if (UTCTimestamp && timestamp) {
|
|
|
|
//offset in minutes is the difference between gps timestamp and given timestamp
|
|
|
|
//to calculate this correctly, we have to work with the same offset
|
2024-03-14 10:41:41 +01:00
|
|
|
const offsetMinutes = (Utils.timestampToMS(timestamp, '+00:00')- Utils.timestampToMS(UTCTimestamp, '+00:00')) / 1000 / 60;
|
2024-03-13 19:56:28 +01:00
|
|
|
return Utils.getOffsetString(offsetMinutes);
|
|
|
|
} else {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-21 23:06:24 +01:00
|
|
|
static getOffsetString(offsetMinutes: number) {
|
|
|
|
if (-720 <= offsetMinutes && offsetMinutes <= 840) {
|
|
|
|
//valid offset is within -12 and +14 hrs (https://en.wikipedia.org/wiki/List_of_UTC_offsets)
|
|
|
|
return (offsetMinutes < 0 ? "-" : "+") + //leading +/-
|
|
|
|
("0" + Math.trunc(Math.abs(offsetMinutes) / 60)).slice(-2) + ":" + //zeropadded hours and ':'
|
|
|
|
("0" + Math.abs(offsetMinutes) % 60).slice(-2); //zeropadded minutes
|
|
|
|
} else {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static getOffsetMinutes(offsetString: string) { //Convert offset string (+HH:MM or -HH:MM) into a minute value
|
2024-03-29 13:20:40 +11:00
|
|
|
const regex = /^([+-](0[0-9]|1[0-4]):[0-5][0-9])$/; //checks if offset is between -14:00 and +14:00.
|
2024-02-21 23:06:24 +01:00
|
|
|
//-12:00 is the lowest valid UTC-offset, but we allow down to -14 for efficiency
|
|
|
|
if (regex.test(offsetString)) {
|
2024-02-21 23:38:07 +01:00
|
|
|
const hhmm = offsetString.split(":");
|
|
|
|
const hours = parseInt(hhmm[0]);
|
2024-02-21 23:06:24 +01:00
|
|
|
return hours < 0 ? ((hours*60) - parseInt(hhmm[1])) : ((hours*60) + parseInt(hhmm[1]));
|
|
|
|
} else {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-06 00:40:18 +01:00
|
|
|
static isLeapYear(year: number) {
|
|
|
|
return (0 == year % 4) && (0 != year % 100) || (0 == year % 400)
|
|
|
|
}
|
|
|
|
|
|
|
|
static isDateFromLeapYear(date: Date) {
|
|
|
|
return Utils.isLeapYear(date.getFullYear());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get Day of Year
|
|
|
|
static getDayOfYear(date: Date) {
|
|
|
|
//Day-number at the start of Jan to Dec. A month baseline
|
|
|
|
const dayCount = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
|
|
|
const mn = date.getMonth();
|
|
|
|
let dayOfYear = dayCount[mn] + date.getDate(); //add the date to the month baseline
|
|
|
|
if (mn > 1 && Utils.isLeapYear((date.getFullYear()))) {
|
|
|
|
dayOfYear++; //Add an extra day for march to december (mn>1) on leap years
|
|
|
|
}
|
|
|
|
return dayOfYear;
|
2024-03-06 06:11:39 +01:00
|
|
|
}
|
2024-03-06 00:40:18 +01:00
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
static renderDataSize(size: number): string {
|
2020-12-31 12:35:28 +01:00
|
|
|
const postFixes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
|
|
let index = 0;
|
|
|
|
while (size > 1000 && index < postFixes.length - 1) {
|
|
|
|
size /= 1000;
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
return size.toFixed(2) + postFixes[index];
|
|
|
|
}
|
|
|
|
|
2023-03-20 22:20:43 +01:00
|
|
|
static getUnique(arr: any[]) {
|
|
|
|
return arr.filter((value, index, arr) => arr.indexOf(value) === index);
|
|
|
|
}
|
|
|
|
|
2018-11-02 16:24:37 +01:00
|
|
|
static createRange(from: number, to: number): Array<number> {
|
|
|
|
const arr = new Array(to - from + 1);
|
|
|
|
let c = to - from + 1;
|
|
|
|
while (c--) {
|
|
|
|
arr[c] = to--;
|
|
|
|
}
|
|
|
|
return arr;
|
|
|
|
}
|
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
public static canonizePath(path: string): string {
|
2020-01-07 22:17:54 +01:00
|
|
|
return path
|
2024-01-03 11:06:19 +01:00
|
|
|
.replace(new RegExp('\\\\', 'g'), '/')
|
|
|
|
.replace(new RegExp('/+', 'g'), '/');
|
2020-01-07 22:17:54 +01:00
|
|
|
}
|
|
|
|
|
2021-05-04 22:43:19 +02:00
|
|
|
static concatUrls(...args: string[]): string {
|
2018-05-12 12:19:51 -04:00
|
|
|
let url = '';
|
2021-04-18 15:48:35 +02:00
|
|
|
for (const item of args) {
|
|
|
|
if (item === '' || typeof item === 'undefined') {
|
2018-05-12 12:19:51 -04:00
|
|
|
continue;
|
|
|
|
}
|
2016-03-26 16:25:48 +01:00
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
const part = item.replace(new RegExp('\\\\', 'g'), '/');
|
2018-05-12 12:19:51 -04:00
|
|
|
if (part === '/' || part === './') {
|
|
|
|
continue;
|
|
|
|
}
|
2016-04-22 13:23:44 +02:00
|
|
|
|
2018-05-12 12:19:51 -04:00
|
|
|
url += part + '/';
|
2016-05-12 18:24:26 +02:00
|
|
|
}
|
2023-03-20 22:20:43 +01:00
|
|
|
url = url.replace(/(https?:\/\/)|(\/){2,}/g, '$1$2');
|
2017-06-10 22:32:56 +02:00
|
|
|
|
2018-05-12 12:19:51 -04:00
|
|
|
if (url.trim() === '') {
|
|
|
|
url = './';
|
2017-07-19 20:47:09 +02:00
|
|
|
}
|
|
|
|
|
2017-06-10 22:32:56 +02:00
|
|
|
return url.substring(0, url.length - 1);
|
|
|
|
}
|
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
public static updateKeys(targetObject: any, sourceObject: any): void {
|
|
|
|
Object.keys(sourceObject).forEach((key): void => {
|
2018-05-12 12:19:51 -04:00
|
|
|
if (typeof targetObject[key] === 'undefined') {
|
2017-06-10 22:32:56 +02:00
|
|
|
return;
|
|
|
|
}
|
2018-05-12 12:19:51 -04:00
|
|
|
if (typeof targetObject[key] === 'object') {
|
2017-06-10 22:32:56 +02:00
|
|
|
Utils.updateKeys(targetObject[key], sourceObject[key]);
|
|
|
|
} else {
|
|
|
|
targetObject[key] = sourceObject[key];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
public static setKeys(targetObject: any, sourceObject: any): void {
|
|
|
|
Object.keys(sourceObject).forEach((key): void => {
|
2018-05-12 12:19:51 -04:00
|
|
|
if (typeof targetObject[key] === 'object') {
|
2017-06-10 22:32:56 +02:00
|
|
|
Utils.setKeys(targetObject[key], sourceObject[key]);
|
|
|
|
} else {
|
|
|
|
targetObject[key] = sourceObject[key];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
public static setKeysForced(targetObject: any, sourceObject: any): void {
|
|
|
|
Object.keys(sourceObject).forEach((key): void => {
|
2018-05-12 12:19:51 -04:00
|
|
|
if (typeof sourceObject[key] === 'object') {
|
|
|
|
if (typeof targetObject[key] === 'undefined') {
|
2017-06-10 22:32:56 +02:00
|
|
|
targetObject[key] = {};
|
2016-05-01 21:30:43 +02:00
|
|
|
}
|
2017-06-10 22:32:56 +02:00
|
|
|
Utils.setKeysForced(targetObject[key], sourceObject[key]);
|
|
|
|
} else {
|
|
|
|
targetObject[key] = sourceObject[key];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-08-30 23:07:13 +02:00
|
|
|
public static isValidEnumInt(EnumType: any, value: number) {
|
|
|
|
return typeof EnumType[value] === 'string';
|
|
|
|
}
|
|
|
|
|
2020-02-04 19:37:47 +01:00
|
|
|
public static enumToArray(EnumType: any): { key: number; value: string }[] {
|
2022-04-04 19:37:31 +02:00
|
|
|
const arr: Array<{ key: number; value: string }> = [];
|
2018-05-12 12:19:51 -04:00
|
|
|
for (const enumMember in EnumType) {
|
2022-04-25 18:36:18 +02:00
|
|
|
// eslint-disable-next-line no-prototype-builtins
|
|
|
|
if (!EnumType.hasOwnProperty(enumMember)) {
|
2017-06-10 22:32:56 +02:00
|
|
|
continue;
|
|
|
|
}
|
2018-05-12 12:19:51 -04:00
|
|
|
const key = parseInt(enumMember, 10);
|
2017-06-10 22:32:56 +02:00
|
|
|
if (key >= 0) {
|
2022-12-28 19:12:18 +01:00
|
|
|
arr.push({key, value: EnumType[enumMember]});
|
2017-06-10 22:32:56 +02:00
|
|
|
}
|
2016-05-01 21:30:43 +02:00
|
|
|
}
|
2017-06-10 22:32:56 +02:00
|
|
|
return arr;
|
|
|
|
}
|
2016-04-22 13:23:44 +02:00
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
public static findClosest(num: number, arr: number[]): number {
|
2017-06-10 22:32:56 +02:00
|
|
|
let curr = arr[0];
|
2021-04-18 15:48:35 +02:00
|
|
|
let diff = Math.abs(num - curr);
|
2016-05-12 11:00:46 +02:00
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
arr.forEach((value): void => {
|
|
|
|
const newDiff = Math.abs(num - value);
|
2016-05-12 11:00:46 +02:00
|
|
|
|
2017-06-10 22:32:56 +02:00
|
|
|
if (newDiff < diff) {
|
|
|
|
diff = newDiff;
|
|
|
|
curr = value;
|
|
|
|
}
|
|
|
|
});
|
2016-05-12 11:00:46 +02:00
|
|
|
|
2017-06-10 22:32:56 +02:00
|
|
|
return curr;
|
|
|
|
}
|
2016-05-12 11:00:46 +02:00
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
public static findClosestinSorted(num: number, arr: number[]): number {
|
2019-12-14 17:27:01 +01:00
|
|
|
let curr = arr[0];
|
2021-04-18 15:48:35 +02:00
|
|
|
let diff = Math.abs(num - curr);
|
|
|
|
for (const item of arr) {
|
|
|
|
const newDiff = Math.abs(num - item);
|
2019-12-14 17:27:01 +01:00
|
|
|
if (newDiff > diff) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
diff = newDiff;
|
2021-04-18 15:48:35 +02:00
|
|
|
curr = item;
|
2019-12-14 17:27:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return curr;
|
|
|
|
}
|
|
|
|
|
2022-04-25 18:09:06 +02:00
|
|
|
public static isUInt32(value: number, max = 4294967295): boolean {
|
2021-04-01 22:17:40 +02:00
|
|
|
value = parseInt('' + value, 10);
|
2019-02-02 22:22:51 -05:00
|
|
|
return !isNaN(value) && value >= 0 && value <= max;
|
|
|
|
}
|
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
public static isInt32(value: number): boolean {
|
2021-04-01 22:17:40 +02:00
|
|
|
value = parseFloat('' + value);
|
2019-02-02 22:22:51 -05:00
|
|
|
return !isNaN(value) && value >= -2147483648 && value <= 2147483647;
|
|
|
|
}
|
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
public static isFloat32(value: number): boolean {
|
2019-02-02 22:22:51 -05:00
|
|
|
const E = Math.pow(10, 38);
|
|
|
|
const nE = Math.pow(10, -38);
|
2022-04-04 19:37:31 +02:00
|
|
|
return (
|
2024-01-03 11:06:19 +01:00
|
|
|
!isNaN(value) &&
|
|
|
|
((value >= -3.402823466 * E && value <= -1.175494351 * nE) ||
|
|
|
|
(value <= 3.402823466 * E && value >= 1.175494351 * nE))
|
2022-04-04 19:37:31 +02:00
|
|
|
);
|
2019-02-02 22:22:51 -05:00
|
|
|
}
|
|
|
|
|
2021-01-16 23:37:14 +01:00
|
|
|
public static getAnyX(num: number, arr: any[], start = 0): any[][] {
|
|
|
|
if (num <= 0 || num > arr.length || start >= arr.length) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
if (num <= 1) {
|
2021-04-18 15:48:35 +02:00
|
|
|
return arr.slice(start).map((e): any[] => [e]);
|
2021-01-16 23:37:14 +01:00
|
|
|
}
|
|
|
|
if (num === arr.length - start) {
|
|
|
|
return [arr.slice(start)];
|
|
|
|
}
|
|
|
|
const ret: any[][] = [];
|
|
|
|
for (let i = start; i < arr.length; ++i) {
|
2021-04-18 15:48:35 +02:00
|
|
|
Utils.getAnyX(num - 1, arr, i + 1).forEach((a): void => {
|
2021-01-16 23:37:14 +01:00
|
|
|
a.push(arr[i]);
|
|
|
|
ret.push(a);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
2024-03-01 19:55:01 +01:00
|
|
|
|
|
|
|
public static xmpExifGpsCoordinateToDecimalDegrees(text: string): number {
|
2024-03-30 13:58:06 +01:00
|
|
|
if (!text) {
|
|
|
|
return undefined;
|
|
|
|
}
|
2024-03-01 19:55:01 +01:00
|
|
|
const parts = text.match(/^([0-9]+),([0-9.]+)([EWNS])$/);
|
|
|
|
const degrees: number = parseInt(parts[1], 10);
|
|
|
|
const minutes: number = parseFloat(parts[2]);
|
|
|
|
const sign = (parts[3] === "N" || parts[3] === "E") ? 1 : -1;
|
2024-03-30 13:58:06 +01:00
|
|
|
return (sign * (degrees + (minutes / 60.0)))
|
2024-03-01 19:55:01 +01:00
|
|
|
}
|
2024-03-29 13:20:40 +11:00
|
|
|
|
|
|
|
|
|
|
|
public static sortableFilename(filename: string): string {
|
|
|
|
const lastDot = filename.lastIndexOf(".");
|
|
|
|
|
|
|
|
// Avoid 0 as well as -1 to prevent empty names for extensionless dot-files
|
|
|
|
if (lastDot > 0) {
|
|
|
|
return filename.substring(0, lastDot);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fallback to the full name
|
|
|
|
return filename;
|
|
|
|
}
|
2016-03-12 22:19:24 +01:00
|
|
|
}
|
2021-04-06 11:32:31 +02:00
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
export class LRU<V> {
|
2022-04-04 19:37:31 +02:00
|
|
|
data: { [key: string]: { value: V; usage: number } } = {};
|
2021-04-06 11:32:31 +02:00
|
|
|
|
2022-12-28 19:12:18 +01:00
|
|
|
constructor(public readonly size: number) {
|
|
|
|
}
|
2021-04-06 11:32:31 +02:00
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
set(key: string, value: V): void {
|
2022-12-28 19:12:18 +01:00
|
|
|
this.data[key] = {usage: Date.now(), value};
|
2021-04-18 15:48:35 +02:00
|
|
|
if (Object.keys(this.data).length > this.size) {
|
|
|
|
let oldestK = key;
|
|
|
|
let oldest = this.data[oldestK].usage;
|
|
|
|
for (const k in this.data) {
|
|
|
|
if (this.data[k].usage < oldest) {
|
|
|
|
oldestK = k;
|
|
|
|
oldest = this.data[oldestK].usage;
|
2021-04-06 11:32:31 +02:00
|
|
|
}
|
|
|
|
}
|
2021-04-18 15:48:35 +02:00
|
|
|
delete this.data[oldestK];
|
2021-04-06 11:32:31 +02:00
|
|
|
}
|
2021-04-18 15:48:35 +02:00
|
|
|
}
|
2021-04-06 11:32:31 +02:00
|
|
|
|
2021-04-18 15:48:35 +02:00
|
|
|
get(key: string): V {
|
|
|
|
if (!this.data[key]) {
|
|
|
|
return;
|
2021-04-06 11:32:31 +02:00
|
|
|
}
|
2021-04-18 15:48:35 +02:00
|
|
|
return this.data[key].value;
|
2021-04-06 11:32:31 +02:00
|
|
|
}
|
|
|
|
}
|