yunohost-admin/app/src/helpers/commons.ts
2024-08-13 11:52:06 +02:00

205 lines
5.1 KiB
TypeScript

import type { UnwrapRef } from 'vue'
import type { Obj } from '@/types/commons'
/**
* Allow to set a timeout on a `Promise` expected response.
* The returned Promise will be rejected if the original Promise is not resolved or
* rejected before the delay.
*
* @param promise - A promise (like a fetch call).
* @param delay - delay after which the promise is rejected
*/
export function timeout<T extends unknown>(
promise: Promise<T>,
delay: number,
): Promise<T> {
return new Promise((resolve, reject) => {
// FIXME reject(new Error('api_not_responding')) for post-install
setTimeout(() => reject, delay)
promise.then(resolve, reject)
})
}
/**
* Check if passed value is an object literal.
*
* @param value - Anything.
*/
export function isObjectLiteral(value: any): value is object {
return (
value !== null &&
value !== undefined &&
Object.is(value.constructor, Object)
)
}
export function objectGet<
T extends Obj,
K extends keyof T | string,
F extends any = undefined,
>(obj: T, key: K, fallback?: F) {
return (key in obj ? obj[key] : fallback) as K extends keyof T ? T[K] : F
}
/**
* Check if value is "empty" (`null`, `undefined`, `''`, `[]`, '{}').
* Note: `0` is not considered "empty" in that helper.
*
* @param value - Anything.
*/
export function isEmptyValue(
value: any,
): value is null | undefined | '' | [] | {} {
if (typeof value === 'number' || typeof value === 'boolean') return false
return (
!value ||
(Array.isArray(value) && value.length === 0) ||
Object.keys(value).length === 0
)
}
type Flatten<T extends object> = object extends T
? object
: {
[K in keyof T]-?: (
x: NonNullable<T[K]> extends infer V
? V extends object
? V extends readonly any[]
? Pick<T, K>
: Flatten<V> extends infer FV
? {
[P in keyof FV as `${Extract<P, string | number>}`]: FV[P]
}
: never
: Pick<T, K>
: never,
) => void
} extends Record<keyof T, (y: infer O) => void>
? O extends infer U
? { [K in keyof O]: O[K] }
: never
: never
/**
* Returns an flattened object literal, with all keys at first level and removing nested ones.
*
* @param obj - An object literal to flatten.
* @param flattened - An object literal to add passed obj keys/values.
*/
export function flattenObjectLiteral<T extends object>(
obj: T,
flattened: Partial<Flatten<T>> = {},
) {
function flatten(objLit: Partial<Flatten<T>>) {
for (const key in objLit) {
const value = objLit[key]
if (isObjectLiteral(value)) {
flatten(value)
} else {
flattened[key] = value
}
}
}
flatten(obj)
return flattened as Flatten<T>
}
/**
* Returns an new Object filtered with passed filter function.
* Each entry `[key, value]` will be forwarded to the `filter` function.
*
* @param obj - object to filter.
* @param filter - the filter function to call for each entry.
*/
export function filterObject<T extends Obj>(
obj: T,
filter: (
entries: [string, any],
index: number,
array: [string, any][],
) => boolean,
) {
return Object.fromEntries(
Object.entries(obj).filter((...args) => filter(...args)),
)
}
/**
* Returns an new array containing items that are in first array but not in the other.
*/
export function arrayDiff<T extends string>(
arr1: T[] = [],
arr2: T[] = [],
): T[] {
return arr1.filter((item) => !arr2.includes(item))
}
/**
* Returns a new string with escaped HTML (`&<>"'` replaced by entities).
*
* @param unsafe - string to escape
*/
export function escapeHtml(unsafe: string) {
return unsafe
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}
/**
* Returns a random integer between `min` and `max`.
*
* @param min - min possible value
* @param max - max possible value
*/
export function randint(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
/**
* Returns a File content.
*
* @param file -
* @param base64 - returns a base64 representation of the file.
*/
export function getFileContent(
file: File,
{ base64 = false } = {},
): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onerror = reject
reader.onload = () => resolve(reader.result as string)
if (base64) {
reader.readAsDataURL(file)
} else {
reader.readAsText(file)
}
})
}
export function toEntries<T extends Record<PropertyKey, unknown>>(
obj: T,
): { [K in keyof T]: [K, T[K]] }[keyof T][] {
return Object.entries(obj) as { [K in keyof T]: [K, T[K]] }[keyof T][]
}
}
export function omit<T extends Obj, K extends (keyof T)[]>(
obj: T,
keys: K,
): Omit<T, K[number]> {
return Object.fromEntries(
Object.keys(obj)
.filter((key) => !keys.includes(key))
.map((key) => [key, obj[key]]),
) as Omit<T, K[number]>
}
export function asUnreffed<T>(value: T): UnwrapRef<T> {
return value as UnwrapRef<T>
}