Files
nocodb/packages/nc-gui/utils/commonUtils.ts

256 lines
7.5 KiB
TypeScript

import type { DefaultOptionType } from 'ant-design-vue/lib/select'
export const modalSizes = {
xs: {
width: 'min(calc(100vw - 32px), 448px)',
height: 'min(90vh, 448px)',
},
sm: {
width: 'min(calc(100vw - 32px), 640px)',
height: 'min(90vh, 424px)',
},
md: {
width: 'min(80vw, 900px)',
height: 'min(90vh, 540px)',
},
lg: {
width: 'min(80vw, 1280px)',
height: 'min(90vh, 864px)',
},
fullscreen: {
width: '100vw',
height: '100vh',
},
}
/**
* Creates a promise that resolves after a specified delay.
*
* @param ms - The delay in milliseconds.
* @returns A promise that resolves after the specified delay.
*
* @example
* ```ts
* // Wait for 2 seconds
* await delay(2000);
* console.log('2 seconds have passed');
* ```
*/
export const ncDelay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
/**
* Generates an array of a given length with content generated by the provided callback function.
*
* @param length - The length of the array to be created.
* @param contentCallback - Optional function to generate content for each index. Defaults to using the index as content.
* @returns The generated array with content.
*
* @example
* // Generate an array of length 5 with default content
* const array = ncArrayFrom(5);
* console.log(array); // ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5']
*
* @example
* // Generate an array of length 3 with custom content
* const customArray = ncArrayFrom(3, (i) => `Custom Content ${i}`);
* console.log(customArray); // ['Custom Content 0', 'Custom Content 1', 'Custom Content 2']
*/
export const ncArrayFrom = <T>(
length: number,
contentCallback: (i: number) => T = (i) => `Item ${i + 1}` as unknown as T,
): T[] => {
return Array.from({ length }, (_, i) => contentCallback(i))
}
/**
* Checks if a string contains Unicode emojis.
*
* @param emoji - The string to check.
* @returns A boolean indicating if the string contains Unicode emojis.
*
* @example
* ```ts
* const hasEmoji = isUnicodeEmoji('Hello World 😊');
* console.log(hasEmoji); // Output: true
* ```
*/
export const isUnicodeEmoji = (emoji: string) => {
return !!emoji?.match(/(\p{Emoji}|\p{Extended_Pictographic})/gu)
}
/**
* Performs a case-insensitive search to check if the `query` exists within the `source`.
*
* - Handles strings, numbers, and arrays (including nested arrays) of strings/numbers.
* - Treats `undefined` as an empty string.
*
* @param source - The value to search within. Can be:
* - A string or number.
* - A single-level or nested array of strings/numbers.
* @param query - The value to search for. Treated as an empty string if `undefined`.
* @returns `true` if the `query` is found within the `source` (case-insensitively), otherwise `false`.
*
* @example
* ```typescript
* // Single string or number search
* searchCompare("Hello World", "world"); // true
* searchCompare(12345, "234"); // true
*
* // Array search
* searchCompare(["apple", "banana", "cherry"], "Banana"); // true
* searchCompare([123, 456, 789], "456"); // true
*
* // Nested array search
* searchCompare(["apple", ["banana", ["cherry"]]], "cherry"); // true
* searchCompare([123, [456, [789]]], "456"); // true
*
* // Handling undefined
* searchCompare(undefined, "test"); // false
* searchCompare("test", undefined); // true
* ```
*/
export const searchCompare = (
source?: NestedArray<string | number | undefined>,
query?: string,
onMatch?: (source: string | number | undefined) => void,
): boolean => {
if (ncIsArray(source)) {
return source.some((item) => searchCompare(item, query, onMatch))
}
const isMatch = (source || '')
.toString()
.toLowerCase()
.includes((query || '').toLowerCase())
if (isMatch && onMatch) {
onMatch(source)
}
return isMatch
}
/**
* Filters options for an Ant Design Select component based on an input value.
*
* @param inputValue - The input value to filter against.
* @param option - The option to evaluate for filtering.
* @param searchKey - The key(s) in the option object to compare against the input value. Defaults to 'key'.
* @returns `true` if the option matches the input value, otherwise `false`.
*/
export const antSelectFilterOption = (
inputValue: string,
option?: DefaultOptionType | NcListItemType,
searchKey: keyof DefaultOptionType | keyof NcListItemType | (keyof NcListItemType)[] | (keyof DefaultOptionType)[] = 'key',
) => {
if (!option) return false
const optionValue = ncIsArray(searchKey) ? searchKey.map((key) => option[key]) : [option[searchKey]]
return searchCompare(optionValue, inputValue)
}
/**
* Extracts the name from an email address.
*
* @param email - The email address to extract the name from.
* @returns The name extracted from the email address.
*
* @example
* ```typescript
* const name = extractNameFromEmail('john.doe@example.com');
* console.log(name); // Output: 'john.doe'
* ```
*/
export const extractNameFromEmail = (email?: string) => {
if (!email) return ''
return email?.slice(0, email.indexOf('@'))
}
/**
* Wait for a condition to be truthy
* @param conditionFn - Function that returns the condition to check
* @param interval - Polling interval in milliseconds (default: 100)
* @returns Promise that resolves when condition becomes truthy
*/
export function waitForCondition(conditionFn: () => unknown, interval: number = 100): Promise<void> {
return new Promise((resolve) => {
const check = (): void => {
if (conditionFn()) {
resolve()
} else {
setTimeout(check, interval)
}
}
check()
})
}
export const pollUntil = <T>(conditionFn: () => T | null | undefined | false, interval: number = 100): Promise<T> => {
return new Promise((resolve, reject) => {
const check = () => {
try {
const result = conditionFn()
if (result) {
resolve(result)
} else {
setTimeout(check, interval)
}
} catch (error) {
reject(error)
}
}
check()
})
}
/**
* Removes all `undefined` values and empty objects (`{}`) from an object.
* Can optionally run recursively with the `deep` flag.
*
* - Works only on plain objects (`ncIsObject`).
* - Arrays are preserved as-is (including `undefined` entries).
* - Empty objects are always removed if they occur as object values.
*
* @typeParam T - Type of the input value.
* @param obj - The object or value to clean.
* @param deep - If `true`, cleans recursively. If `false`, cleans only top-level. Defaults to `true`.
* @returns A cleaned copy of `obj`.
*
* @example
* ```ts
* const data = {
* a: 1,
* b: undefined,
* c: { d: 2, e: undefined, f: { g: undefined } },
* arr: [1, undefined, { x: undefined, y: 5 }]
* }
*
* removeUndefinedFromObj(data)
* // → { a: 1, c: { d: 2 }, arr: [1, undefined, { y: 5 }] }
*
* removeUndefinedFromObj(data, false)
* // → { a: 1, c: { d: 2, f: { g: undefined } }, arr: [1, undefined, { x: undefined, y: 5 }] }
* ```
*/
export const removeUndefinedFromObj = <T>(obj: T, deep = true): T => {
if (ncIsObject(obj)) {
const cleanedEntries = Object.entries(obj)
.map(([k, v]) => {
const cleanedValue = deep && (ncIsObject(v) || Array.isArray(v)) ? removeUndefinedFromObj(v, deep) : v
return [k, cleanedValue] as const
})
.filter(([_, v]) => {
if (v === undefined) return false
if (ncIsObject(v) && !Array.isArray(v) && ncIsEmptyObject(v)) return false
return true
})
return Object.fromEntries(cleanedEntries) as T
}
return obj
}