declare global {
|
interface Window {
|
APP_SETTINGS: any;
|
}
|
}
|
|
export const formDataToJPO = (formData: FormData) => {
|
if (formData instanceof FormData) {
|
const entries = formData.entries();
|
|
return Array.from(entries).reduce((res, [key, value]) => {
|
return { ...res, [key]: value };
|
}, {});
|
}
|
|
return formData;
|
};
|
|
type Uniqueness<T> = (a: T, b: T) => boolean;
|
|
export const unique = <T>(list: T[] | undefined, expression: Uniqueness<T>): T[] => {
|
const comparator = expression ?? ((a, b) => a === b);
|
|
return (list ?? []).reduce<T[]>((res, item) => {
|
const index = res.findIndex((elem) => comparator(elem, item));
|
|
if (index < 0) res.push(item);
|
|
return res;
|
}, []);
|
};
|
|
export const isDefined = <T>(value: T | undefined | null): value is T => {
|
return value !== null && value !== undefined;
|
};
|
|
export const isEmptyString = (value: any) => {
|
return typeof value === "string" && value.trim() === "";
|
};
|
|
export const objectClean = <T extends AnyObject>(source: T) => {
|
const cleanObject: [keyof T, unknown][] = Object.entries(source).reduce<[keyof T, unknown][]>((res, [key, value]) => {
|
const valueIsDefined = isDefined(value) && !isEmptyString(value);
|
|
if (!valueIsDefined) {
|
return res;
|
}
|
|
if (Object.prototype.toString.call(value) === "[object Object]") {
|
return [...res, [key, objectClean(value as AnyObject)]];
|
}
|
return [...res, [key, value]];
|
}, []);
|
|
return Object.fromEntries(cleanObject) as T;
|
};
|
|
export const numberWithPrecision = (n: number, precision = 1, removeTrailinZero = false) => {
|
if (typeof n !== "number" || isNaN(n)) return "";
|
|
let finalNum = n.toFixed(precision);
|
|
if (removeTrailinZero) {
|
finalNum = finalNum.replace(/.(0+)$/, "");
|
}
|
|
return finalNum;
|
};
|
|
export const humanReadableNumber = (n: number) => {
|
const abs = Math.abs(n);
|
|
if (isNaN(abs) || n === null) return "—";
|
const normalizeNumber = (n: number) => numberWithPrecision(n, 1, true);
|
|
let result;
|
|
if (abs < 1e3) {
|
result = normalizeNumber(n);
|
} else if (abs >= 1e3 && abs < 1e6) {
|
result = `${normalizeNumber(n / 1e3)}K`;
|
} else if (abs >= 1e6 && abs < 1e9) {
|
result = `${normalizeNumber(n / 1e6)}M`;
|
} else {
|
result = `${normalizeNumber(n / 1e9)}B`;
|
}
|
|
return result || null;
|
};
|
|
export const absoluteURL = (path = "") => {
|
if (path.match(/^https?/) || path.match(/^\/\//)) {
|
return path;
|
}
|
return [APP_SETTINGS.hostname.replace(/([/]+)$/, ""), path.replace(/^([/]+)/, "")].join("/");
|
};
|
|
export const removePrefix = (path: string) => {
|
if (APP_SETTINGS.hostname) {
|
const hostname = APP_SETTINGS.hostname;
|
const prefix = new URL(hostname).pathname.replace(/([/]+)$/, "");
|
|
return path.replace(new RegExp(`^${prefix}`), "");
|
}
|
|
return path || "/";
|
};
|
|
export const copyText = (text: string) => {
|
const input = document.createElement("textarea");
|
|
input.style.position = "fixed"; // don't mess up with scroll
|
document.body.appendChild(input);
|
|
input.value = text;
|
input.focus();
|
input.select();
|
|
document.execCommand("copy");
|
input.remove();
|
};
|
|
export const delay = (time = 0) => {
|
return new Promise((resolve) => setTimeout(resolve, time));
|
};
|
|
export const clamp = (value: number, min: number, max: number) => {
|
return Math.max(min, Math.min(value, max));
|
};
|
|
export const getLastTraceback = (traceback: string): string => {
|
const lines = traceback.split("\n");
|
let lastTraceIndex = -1;
|
|
for (let i = lines.length - 1; i >= 0; i--) {
|
if (lines[i].startsWith(" File")) {
|
lastTraceIndex = i;
|
break;
|
}
|
}
|
|
return lastTraceIndex >= 0 ? lines.slice(lastTraceIndex).join("\n") : traceback;
|
};
|