9
common/resources/client/utils/array/async-iterable-to-array.ts
Executable file
9
common/resources/client/utils/array/async-iterable-to-array.ts
Executable file
@@ -0,0 +1,9 @@
|
||||
export async function asyncIterableToArray<T>(
|
||||
iterator: AsyncIterable<T>
|
||||
): Promise<T[]> {
|
||||
const items: T[] = [];
|
||||
for await (const item of iterator) {
|
||||
items.push(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
13
common/resources/client/utils/array/chunk-array.ts
Executable file
13
common/resources/client/utils/array/chunk-array.ts
Executable file
@@ -0,0 +1,13 @@
|
||||
export function chunkArray<T>(array: T[], chunkSize: number): T[][] {
|
||||
return array.reduce<any>((resultArray, item, index) => {
|
||||
const chunkIndex = Math.floor(index / chunkSize);
|
||||
|
||||
if (!resultArray[chunkIndex]) {
|
||||
resultArray[chunkIndex] = [];
|
||||
}
|
||||
|
||||
resultArray[chunkIndex].push(item);
|
||||
|
||||
return resultArray;
|
||||
}, []);
|
||||
}
|
||||
26
common/resources/client/utils/array/group-array-by.ts
Executable file
26
common/resources/client/utils/array/group-array-by.ts
Executable file
@@ -0,0 +1,26 @@
|
||||
interface Options<T> {
|
||||
map?: (item: T) => T;
|
||||
}
|
||||
|
||||
export function groupArrayBy<T>(
|
||||
arr: T[],
|
||||
cb: (item: any) => string,
|
||||
options?: Options<T>,
|
||||
): {[key: string]: T[]} {
|
||||
const result: {[key: string]: T[]} = {};
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
let item = arr[i];
|
||||
const bucketCategory = cb(item);
|
||||
const bucket = result[bucketCategory];
|
||||
|
||||
item = options?.map ? options.map(arr[i]) : arr[i];
|
||||
|
||||
if (!Array.isArray(bucket)) {
|
||||
result[bucketCategory] = [item];
|
||||
} else {
|
||||
result[bucketCategory].push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
25
common/resources/client/utils/array/move-item-in-array.ts
Executable file
25
common/resources/client/utils/array/move-item-in-array.ts
Executable file
@@ -0,0 +1,25 @@
|
||||
import {clamp} from '../number/clamp';
|
||||
|
||||
export function moveItemInArray<T = any>(
|
||||
array: T[],
|
||||
fromIndex: number,
|
||||
toIndex: number
|
||||
): T[] {
|
||||
const from = clamp(fromIndex, 0, array.length - 1);
|
||||
const to = clamp(toIndex, 0, array.length - 1);
|
||||
|
||||
if (from === to) {
|
||||
return array;
|
||||
}
|
||||
|
||||
const target = array[from];
|
||||
const delta = to < from ? -1 : 1;
|
||||
|
||||
for (let i = from; i !== to; i += delta) {
|
||||
array[i] = array[i + delta];
|
||||
}
|
||||
|
||||
array[to] = target;
|
||||
|
||||
return array;
|
||||
}
|
||||
14
common/resources/client/utils/array/move-item-in-new-array.ts
Executable file
14
common/resources/client/utils/array/move-item-in-new-array.ts
Executable file
@@ -0,0 +1,14 @@
|
||||
export function moveItemInNewArray<T>(
|
||||
array: T[],
|
||||
from: number,
|
||||
to: number
|
||||
): T[] {
|
||||
const newArray = array.slice();
|
||||
newArray.splice(
|
||||
to < 0 ? newArray.length + to : to,
|
||||
0,
|
||||
newArray.splice(from, 1)[0]
|
||||
);
|
||||
|
||||
return newArray;
|
||||
}
|
||||
35
common/resources/client/utils/array/move-multiple-items-in-array.ts
Executable file
35
common/resources/client/utils/array/move-multiple-items-in-array.ts
Executable file
@@ -0,0 +1,35 @@
|
||||
export function moveMultipleItemsInArray<T>(
|
||||
array: T[],
|
||||
indexOrIndexes: number | number[],
|
||||
newIndex: number
|
||||
) {
|
||||
const indexes = Array.isArray(indexOrIndexes)
|
||||
? indexOrIndexes
|
||||
: [indexOrIndexes];
|
||||
const insertBefore = array[newIndex + (newIndex < indexes[0] ? 0 : 1)];
|
||||
const itemsToBeMoved = indexes.map(i => array[i]);
|
||||
|
||||
// in original sequence order, check for presence in the removal
|
||||
// list, *and* remove them from the original array
|
||||
const moved = [];
|
||||
for (let i = 0; i < array.length; ) {
|
||||
const value = array[i];
|
||||
if (itemsToBeMoved.indexOf(value) >= 0) {
|
||||
moved.push(value);
|
||||
array.splice(i, 1);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// find the new index of the insertion point
|
||||
let insertionIndex = array.indexOf(insertBefore);
|
||||
if (insertionIndex < 0) {
|
||||
insertionIndex = array.length;
|
||||
}
|
||||
|
||||
// and add the elements back in
|
||||
array.splice(insertionIndex, 0, ...moved);
|
||||
|
||||
return array;
|
||||
}
|
||||
5
common/resources/client/utils/array/prepend-to-array-at-index.ts
Executable file
5
common/resources/client/utils/array/prepend-to-array-at-index.ts
Executable file
@@ -0,0 +1,5 @@
|
||||
export function prependToArrayAtIndex<T>(array: T[], toAdd: T[], index = 0): T[] {
|
||||
const copyOfArray = [...array];
|
||||
const tail = copyOfArray.splice(index + 1);
|
||||
return [...copyOfArray, ...toAdd, ...tail];
|
||||
}
|
||||
22
common/resources/client/utils/array/shuffle-array.ts
Executable file
22
common/resources/client/utils/array/shuffle-array.ts
Executable file
@@ -0,0 +1,22 @@
|
||||
export function shuffleArray(items: any[], keepFirst = false) {
|
||||
let first = keepFirst ? items.shift() : null;
|
||||
|
||||
let currentIndex = items.length,
|
||||
temporaryValue,
|
||||
randomIndex;
|
||||
|
||||
while (0 !== currentIndex) {
|
||||
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex -= 1;
|
||||
|
||||
temporaryValue = items[currentIndex];
|
||||
items[currentIndex] = items[randomIndex];
|
||||
items[randomIndex] = temporaryValue;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
items.unshift(first);
|
||||
}
|
||||
|
||||
return [...items];
|
||||
}
|
||||
77
common/resources/client/utils/array/sort-array-of-objects.ts
Executable file
77
common/resources/client/utils/array/sort-array-of-objects.ts
Executable file
@@ -0,0 +1,77 @@
|
||||
import dot from 'dot-object';
|
||||
|
||||
const MAX_SAFE_INTEGER = 9007199254740991;
|
||||
|
||||
export function sortArrayOfObjects<T extends object>(
|
||||
data: T[],
|
||||
orderBy: string,
|
||||
orderDir: 'asc' | 'desc' = 'desc'
|
||||
): T[] {
|
||||
return data.sort((a, b) => {
|
||||
let valueA = sortingDataAccessor(a, orderBy);
|
||||
let valueB = sortingDataAccessor(b, orderBy);
|
||||
|
||||
// If there are data in the column that can be converted to a number,
|
||||
// it must be ensured that the rest of the data
|
||||
// is of the same type so as not to order incorrectly.
|
||||
const valueAType = typeof valueA;
|
||||
const valueBType = typeof valueB;
|
||||
|
||||
if (valueAType !== valueBType) {
|
||||
if (valueAType === 'number') {
|
||||
valueA += '';
|
||||
}
|
||||
if (valueBType === 'number') {
|
||||
valueB += '';
|
||||
}
|
||||
}
|
||||
|
||||
// If both valueA and valueB exist (truthy), then compare the two. Otherwise, check if
|
||||
// one value exists while the other doesn't. In this case, existing value should come last.
|
||||
// This avoids inconsistent results when comparing values to undefined/null.
|
||||
// If neither value exists, return 0 (equal).
|
||||
let comparatorResult = 0;
|
||||
if (valueA != null && valueB != null) {
|
||||
// Check if one value is greater than the other; if equal, comparatorResult should remain 0.
|
||||
if (valueA > valueB) {
|
||||
comparatorResult = 1;
|
||||
} else if (valueA < valueB) {
|
||||
comparatorResult = -1;
|
||||
}
|
||||
} else if (valueA != null) {
|
||||
comparatorResult = 1;
|
||||
} else if (valueB != null) {
|
||||
comparatorResult = -1;
|
||||
}
|
||||
|
||||
return comparatorResult * (orderDir === 'asc' ? 1 : -1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Data accessor function that is used for accessing data properties for sorting through
|
||||
* the default sortData function.
|
||||
* This default function assumes that the sort header IDs (which defaults to the column name)
|
||||
* matches the data's properties (e.g. column Xyz represents data['Xyz']).
|
||||
* May be set to a custom function for different behavior.
|
||||
*/
|
||||
function sortingDataAccessor(data: object, key: string): string {
|
||||
const value = dot.pick(key, data);
|
||||
|
||||
if (isNumberValue(value)) {
|
||||
const numberValue = Number(value);
|
||||
|
||||
// Numbers beyond `MAX_SAFE_INTEGER` can't be compared reliably, so we
|
||||
// leave them as strings. For more info: https://goo.gl/y5vbSg
|
||||
return numberValue < MAX_SAFE_INTEGER ? numberValue : value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function isNumberValue(value: any): boolean {
|
||||
// parseFloat(value) handles most of the cases we're interested in (it treats null, empty string,
|
||||
// and other non-number values as NaN, where Number just uses 0) but it considers the string
|
||||
// '123hello' to be a valid number. Therefore, we also check if Number(value) is NaN.
|
||||
return !isNaN(parseFloat(value as any)) && !isNaN(Number(value));
|
||||
}
|
||||
Reference in New Issue
Block a user