@kwiz/common 1.0.91 → 1.0.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.github/workflows/npm-publish.yml +24 -24
  2. package/.madgerc +2 -2
  3. package/LICENSE +21 -21
  4. package/fix-folder-imports.js +26 -26
  5. package/lib/cjs/helpers/browser.js +32 -8
  6. package/lib/cjs/helpers/browser.js.map +1 -1
  7. package/lib/cjs/types/libs/msal.types.js +26 -26
  8. package/lib/cjs/utils/sharepoint.rest/user.js +11 -11
  9. package/lib/esm/helpers/browser.js +32 -8
  10. package/lib/esm/helpers/browser.js.map +1 -1
  11. package/lib/esm/types/libs/msal.types.js +26 -26
  12. package/lib/esm/utils/sharepoint.rest/user.js +11 -11
  13. package/package.json +81 -80
  14. package/readme.md +17 -17
  15. package/src/_dependencies.ts +12 -12
  16. package/src/config.ts +17 -17
  17. package/src/helpers/Guid.ts +181 -181
  18. package/src/helpers/base64.ts +173 -173
  19. package/src/helpers/browser.test.js +13 -13
  20. package/src/helpers/browser.ts +1386 -1357
  21. package/src/helpers/browserinfo.ts +292 -292
  22. package/src/helpers/collections.base.test.js +25 -25
  23. package/src/helpers/collections.base.ts +437 -437
  24. package/src/helpers/collections.ts +107 -107
  25. package/src/helpers/color.ts +54 -54
  26. package/src/helpers/cookies.ts +59 -59
  27. package/src/helpers/date.test.js +119 -119
  28. package/src/helpers/date.ts +188 -188
  29. package/src/helpers/debug.ts +186 -186
  30. package/src/helpers/emails.ts +6 -6
  31. package/src/helpers/eval.ts +5 -5
  32. package/src/helpers/file.test.js +50 -50
  33. package/src/helpers/file.ts +60 -60
  34. package/src/helpers/flatted.ts +149 -149
  35. package/src/helpers/functions.ts +16 -16
  36. package/src/helpers/graph/calendar.types.ts +10 -10
  37. package/src/helpers/http.ts +69 -69
  38. package/src/helpers/images.ts +22 -22
  39. package/src/helpers/json.ts +38 -38
  40. package/src/helpers/md5.ts +189 -189
  41. package/src/helpers/objects.test.js +33 -33
  42. package/src/helpers/objects.ts +274 -274
  43. package/src/helpers/promises.test.js +37 -37
  44. package/src/helpers/promises.ts +165 -165
  45. package/src/helpers/random.ts +27 -27
  46. package/src/helpers/scheduler/scheduler.test.js +103 -103
  47. package/src/helpers/scheduler/scheduler.ts +131 -131
  48. package/src/helpers/sharepoint.ts +776 -776
  49. package/src/helpers/strings.test.js +101 -101
  50. package/src/helpers/strings.ts +317 -317
  51. package/src/helpers/typecheckers.test.js +34 -34
  52. package/src/helpers/typecheckers.ts +266 -266
  53. package/src/helpers/url.test.js +43 -43
  54. package/src/helpers/url.ts +207 -207
  55. package/src/helpers/urlhelper.ts +111 -111
  56. package/src/index.ts +6 -6
  57. package/src/types/auth.ts +54 -54
  58. package/src/types/common.types.ts +15 -15
  59. package/src/types/flatted.types.ts +59 -59
  60. package/src/types/globals.types.ts +6 -6
  61. package/src/types/graph/calendar.types.ts +80 -80
  62. package/src/types/knownscript.types.ts +18 -18
  63. package/src/types/libs/datajs.types.ts +28 -28
  64. package/src/types/libs/ics.types.ts +30 -30
  65. package/src/types/libs/msal.types.ts +49 -49
  66. package/src/types/locales.ts +124 -124
  67. package/src/types/localstoragecache.types.ts +8 -8
  68. package/src/types/location.types.ts +27 -27
  69. package/src/types/moment.ts +11 -11
  70. package/src/types/regex.types.ts +16 -16
  71. package/src/types/rest.types.ts +95 -95
  72. package/src/types/sharepoint.types.ts +1465 -1465
  73. package/src/types/sharepoint.utils.types.ts +287 -287
  74. package/src/utils/auth/common.ts +74 -74
  75. package/src/utils/auth/discovery.test.js +12 -12
  76. package/src/utils/auth/discovery.ts +132 -132
  77. package/src/utils/base64.ts +27 -27
  78. package/src/utils/consolelogger.ts +320 -320
  79. package/src/utils/date.ts +35 -35
  80. package/src/utils/emails.ts +24 -24
  81. package/src/utils/knownscript.ts +286 -286
  82. package/src/utils/localstoragecache.ts +441 -441
  83. package/src/utils/rest.ts +501 -501
  84. package/src/utils/script.ts +170 -170
  85. package/src/utils/sharepoint.rest/common.ts +154 -154
  86. package/src/utils/sharepoint.rest/date.ts +62 -62
  87. package/src/utils/sharepoint.rest/file.folder.ts +598 -598
  88. package/src/utils/sharepoint.rest/item.ts +547 -547
  89. package/src/utils/sharepoint.rest/list.ts +1480 -1480
  90. package/src/utils/sharepoint.rest/listutils/GetListItemsByCaml.ts +774 -774
  91. package/src/utils/sharepoint.rest/listutils/GetListItemsById.ts +275 -275
  92. package/src/utils/sharepoint.rest/listutils/common.ts +206 -206
  93. package/src/utils/sharepoint.rest/location.ts +141 -141
  94. package/src/utils/sharepoint.rest/navigation-links.ts +86 -86
  95. package/src/utils/sharepoint.rest/user-search.ts +252 -252
  96. package/src/utils/sharepoint.rest/user.ts +491 -491
  97. package/src/utils/sharepoint.rest/web.ts +1384 -1384
  98. package/src/utils/sod.ts +194 -194
@@ -1,438 +1,438 @@
1
-
2
- /** this file will only use basic type checker and types, do not add functions that require
3
- * a reference to tother helpers
4
- */
5
- import { IDictionary } from "../types/common.types";
6
- import { getFromFullName, isBoolean, isDate, isFunction, isNotEmptyArray, isNullOrEmptyArray, isNullOrEmptyString, isNullOrUndefined, isNumber, isString, isTypeofFullNameFunction } from "./typecheckers";
7
-
8
- /** this will support HtmlCollectionOf, Arrays, and any other types that have a length and indexer Issue 568 */
9
- export interface IndexedCollection<ElementType> {
10
- length: number;
11
- [index: number]: ElementType;
12
- }
13
-
14
- /** Finds an object in array based on a filter and moves it to the start of the array */
15
- export function moveToStart<T>(arr: T[], filter?: (item: T) => boolean) {
16
- let index = firstIndexOf<T>(arr, filter);
17
-
18
- if (index > 0) {
19
- let obj = arr[index];
20
- arr.splice(index, 1);
21
- arr.unshift(obj);
22
- }
23
- }
24
-
25
- /** Finds an object in array based on a filter and moves it to the end of the array */
26
- export function moveToEnd<T>(arr: T[], filter?: (item: T) => boolean) {
27
- let index = firstIndexOf<T>(arr, filter);
28
-
29
- if (index !== -1 && index !== arr.length - 1) {
30
- let obj = arr[index];
31
- arr.splice(index, 1);
32
- arr.push(obj);
33
- }
34
- }
35
-
36
- /** Get the first index of an object of an array, or -1 if the array is empty / null */
37
- // export function firstIndexOf<T extends Element>(arr: HTMLCollectionOf<T>, filter?: (item: T, index?: number) => boolean, startFrom?: number): number;
38
- // export function firstIndexOf<T>(arr: T[], filter?: (item: T, index?: number) => boolean, startFrom?: number): number;
39
- export function firstIndexOf<T>(arr: IndexedCollection<T>, filter?: (item: T, index?: number) => boolean, startFrom?: number): number {
40
- if (!isNullOrUndefined(arr) && arr.length > 0) {
41
- if (isFunction(filter)) {
42
- //use for loop so we can stop when it is found
43
- for (let i = startFrom > 0 ? startFrom : 0; i < arr.length; i++)
44
- if (filter(arr[i], i) === true)
45
- return i;
46
- }
47
- else return 0;
48
- }
49
-
50
- return -1;
51
- }
52
- /** Get the first object of an array, or null if the array is empty / null
53
- * If you pass a filter, it will find the first element that matches the filter and return it, stopping the loop when it is found
54
- * */
55
- export function firstOrNull<T>(arr: IndexedCollection<T>, filter?: (item: T, index?: number) => boolean): T {
56
- let index = firstIndexOf(arr, filter);
57
- return index < 0 ? null : arr[index];
58
- }
59
-
60
-
61
- /** Get the last index of an object of an array, or -1 if the array is empty / null */
62
- export function lastIndexOf<T>(arr: IndexedCollection<T>, filter?: (item: T) => boolean): number {
63
- if (!isNullOrUndefined(arr) && arr.length > 0) {
64
- if (isFunction(filter)) {
65
- //use for loop so we can stop when it is found
66
- for (let i = arr.length - 1; i >= 0; i--)
67
- if (filter(arr[i]) === true)
68
- return i;
69
- }
70
- else return arr.length - 1;
71
- }
72
-
73
- return -1;
74
- }
75
-
76
- /** get the last element or null */
77
- export function lastOrNull<T>(arr: IndexedCollection<T>, filter?: (item: T) => boolean): T {
78
- let index = lastIndexOf(arr as T[], filter);
79
- return index < 0 ? null : arr[index];
80
- }
81
-
82
- /** Get the first index of an object of an array, or -1 if the array is empty / null */
83
- export async function firstIndexOfAsync<T>(arr: IndexedCollection<T>, filter?: (item: T, index?: number) => Promise<boolean>, startFrom?: number): Promise<number> {
84
- if (!isNullOrUndefined(arr) && arr.length > 0) {
85
- if (isFunction(filter)) {
86
- //use for loop so we can stop when it is found
87
- for (let i = startFrom > 0 ? startFrom : 0; i < arr.length; i++)
88
- if ((await filter(arr[i], i)) === true)
89
- return i;
90
- }
91
- else return 0;
92
- }
93
-
94
- return -1;
95
- }
96
- /** Get the first object of an array, or null if the array is empty / null
97
- * If you pass a filter, it will find the first element that matches the filter and return it, stopping the loop when it is found
98
- * */
99
- export async function firstOrNullAsync<T>(arr: IndexedCollection<T>, filter?: (item: T, index?: number) => Promise<boolean>): Promise<T> {
100
- let index = await firstIndexOfAsync(arr, filter);
101
- return index < 0 ? null : arr[index];
102
- }
103
-
104
-
105
- /** Get the last index of an object of an array, or -1 if the array is empty / null */
106
- export async function lastIndexOfAsync<T>(arr: IndexedCollection<T>, filter?: (item: T) => Promise<boolean>): Promise<number> {
107
- if (!isNullOrUndefined(arr) && arr.length > 0) {
108
- if (isFunction(filter)) {
109
- //use for loop so we can stop when it is found
110
- for (let i = arr.length - 1; i >= 0; i--)
111
- if ((await filter(arr[i])) === true)
112
- return i;
113
- }
114
- else return arr.length - 1;
115
- }
116
-
117
- return -1;
118
- }
119
-
120
- /** get the last element or null */
121
- export async function lastOrNullAsync<T>(arr: IndexedCollection<T>, filter?: (item: T) => Promise<boolean>): Promise<T> {
122
- let index = await lastIndexOfAsync(arr, filter);
123
- return index < 0 ? null : arr[index];
124
- }
125
-
126
- /** Sorts an array of complex objects, use defaultPrimitiveGetValue for default functionality */
127
- export function sortArray<T>(arr: T[], getValue: (item: T) => number | string) {
128
- if (!isNullOrEmptyArray(arr)) {
129
- if (isTypeofFullNameFunction("Intl.Collator")) {
130
- //todo: should probably use the SharePoint locale isntead of 'undefined'
131
- let collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
132
- arr.sort((a, b) => {
133
- let va = getValue(a);
134
- let vb = getValue(b);
135
- return collator.compare(va as string, vb as string);
136
- });
137
- } else {
138
- arr.sort((a, b) => {
139
- let va = getValue(a);
140
- if (isString(va)) va = va.toLowerCase();
141
- let vb = getValue(b);
142
- if (isString(vb)) vb = vb.toLowerCase();
143
- return va === vb ? 0 : va > vb ? 1 : -1;
144
- });
145
- }
146
- }
147
- return arr;
148
- }
149
-
150
- /** removes null, undefined or "" elements from the array */
151
- export function filterEmptyEntries<T>(arr: T[]) {
152
- return arr.filter(val => !isNullOrEmptyString(val));
153
- }
154
-
155
-
156
- export function sortNumberArrayAsc(a: number, b: number): number {
157
- return a - b;
158
- }
159
- export function sortNumberArray(a: number, b: number): number {
160
- return b - a;
161
- }
162
- /** call a foreach on an object or an array, with an option to break when returning false */
163
- export function forEach<T>(obj: IDictionary<T> | Array<T> | { [key: number]: T; length: number; }, func: (propertyName: string, propertyValue: T, _args?: any) => void | boolean, args?: any) {
164
- if (obj && func && isFunction(func)) {
165
- if (Array.isArray(obj) || obj.constructor && getFromFullName("constructor.name", obj) === "Array") {
166
- for (let i = 0; i < (obj as Array<T>).length; i++) {
167
- let property = i;
168
- let value = obj[property];
169
- let result = func(property.toString(10), value, args);
170
- if (result === false) {
171
- break;
172
- }
173
- }
174
- }
175
- else {
176
- let keys = Object.keys(obj);
177
- for (let i = 0; i < keys.length; i++) {
178
- let property = keys[i];
179
- let value = obj[property];
180
- let result = func(property, value, args);
181
- if (result === false) {
182
- break;
183
- }
184
- }
185
- }
186
- }
187
- }
188
-
189
- export async function forEachAsync<ElmType, ResultType>(arr: Array<ElmType> | IDictionary<ElmType>, handler: (elm: ElmType, index: number) => Promise<ResultType>, options?: { parallel?: boolean; }) {
190
- if (!isNullOrUndefined(arr) && Object.keys(arr).length > 0) {
191
- let keys = Object.keys(arr);
192
- if (options && options.parallel) {
193
- let promises: Promise<ResultType>[] = [];
194
- keys.forEach((key, i) => {
195
- promises.push(handler(arr[key], i));
196
- });
197
- return Promise.all(promises);
198
- }
199
- else {
200
- let results: ResultType[] = [];
201
- for (let i = 0; i < keys.length; i++) {
202
- results.push(await handler(arr[keys[i]], i));
203
- }
204
- return results;
205
- }
206
- }
207
- else return []
208
- }
209
-
210
- export function sizeOf(obj: any) {
211
- if (Array.isArray(obj))
212
- return obj.length;
213
- return Object.keys(obj).length;
214
- }
215
-
216
- export function chunkArray<T>(array: T[], chunkSize: number) {
217
- let chunkedArray: T[][] = [];
218
- for (var i = 0; i < array.length; i += chunkSize) {
219
- chunkedArray.push(array.slice(i, i + chunkSize));
220
- }
221
- return chunkedArray;
222
- }
223
-
224
- /** Takes an array and transforms it into a hash. this will assign 1 item per key, assumig getKey will be unique per item. */
225
- export function toHash<T, Y = T>(arr: T[], getKey: (element: T) => string | number, filter?: (element: T) => boolean, transformValue?: (element: T) => Y): IDictionary<Y> {
226
- let hash: { [key: string | number]: Y; } = {};
227
- if (!isFunction(transformValue)) transformValue = v => v as any as Y;
228
- if (isNotEmptyArray(arr))
229
- arr.forEach(i => {
230
- if (!isFunction(filter) || filter(i))
231
- hash[getKey(i)] = transformValue(i);
232
- });
233
- return hash;
234
- }
235
-
236
- /** Returns an array from the values of the dictionary. */
237
- export function toArray<Source, Result = Source>(hash: { [key: string]: Source; }, filter?: (element: Source) => boolean, transform?: (key: string, element: Source) => Result): Result[] {
238
- let arr: Result[] = [];
239
- if (!isFunction(transform)) transform = (key, element) => element as any as Result;
240
-
241
- if (!isNullOrUndefined(hash))
242
- Object.keys(hash).forEach(key => {
243
- if (!isFunction(filter) || filter(hash[key]))
244
- arr.push(transform(key, hash[key]));
245
- });
246
- return arr;
247
- }
248
-
249
- /** returns a new dictionary, converting each entry in source using the transform function */
250
- export function convertDictionary<S, R>(source: IDictionary<S>, transform: (sourceItem: S) => R): IDictionary<R> {
251
- let result: IDictionary<R> = {};
252
- forEach(source, (key, value) => { result[key] = transform(value); });
253
- return result;
254
- }
255
-
256
- export function flattenArray<T>(array: (T | T[])[]) {
257
- return array.reduce((acc, val) => (acc as T[]).concat(val), []) as T[];
258
- }
259
-
260
- /** careful, does not work for date/complex objects. Use GetUniqueArrayInfo if you suspect you might have Date/complex objects. */
261
- export function makeUniqueArray<T>(arr: T[]) {
262
- return arr.filter((v, i, a) => a.indexOf(v) === i);
263
- }
264
-
265
- /** return an array of unique values, and the first index they were found, use defaultPrimitiveGetValue for default functionality */
266
- export function GetUniqueArrayInfo<T, V>(arr: T[], getValue: (item: T) => V) {
267
- var uniqueValues: { item: T; value: V; firstIndex: number; }[] = [];
268
- var uniqueArray: T[] = [];
269
- var foundValues: V[] = [];
270
- var hasDuplicates = false;
271
- var duplicateIndexes: number[] = [];
272
-
273
- if (isNotEmptyArray(arr)) {
274
- arr.forEach((item, index) => {
275
- let value = getValue(item);
276
- if (foundValues.includes(value)) {
277
- hasDuplicates = true;
278
- duplicateIndexes.push(index);
279
- }
280
- else {
281
- foundValues.push(value);
282
- uniqueValues.push({ item: item, value: value, firstIndex: index });
283
- uniqueArray.push(item);
284
- }
285
- });
286
- }
287
-
288
- return {
289
- /** true if duplicate values found */
290
- hasDuplicates: hasDuplicates,
291
- /** all duplicate item indexes */
292
- duplicateIndexes: duplicateIndexes,
293
- /** unique values and their info */
294
- uniqueValues: uniqueValues,
295
- /** the unique version of this array */
296
- uniqueArray: uniqueArray
297
- };
298
- }
299
-
300
- export interface IMultiLevelGroupItem<ItemType> {
301
- parentGroup: IMultiLevelGroup<ItemType>;
302
- }
303
- export interface IMultiLevelGroup<ItemType> {
304
- groupItems: (ItemType & IMultiLevelGroupItem<ItemType>)[];
305
- subGroups: IDictionary<IMultiLevelGroup<ItemType>>;
306
- depth: number;
307
- parentGroup: IMultiLevelGroup<ItemType>;
308
- /** would contain the path to the group, such as: 0_1_3 for first group, second sub group, 4th sub-sub group */
309
- key: string;
310
- index: number;
311
- title: string;
312
- /** Optional, add a prefix to the group. For example: "Priority > " to fullTitle will be "Priority > High" */
313
- groupPrefix?: string;
314
- /** title with decorations, such as prefix */
315
- fullTitle: string;
316
- }
317
- export type MultiLevelGroupItem<ItemType> = ItemType & IMultiLevelGroupItem<ItemType>;
318
- export type MultiLevelGroupOrItem<ItemType> = MultiLevelGroupItem<ItemType> | IMultiLevelGroup<ItemType>;
319
- /** returns true if the element is a group of items */
320
- export function IsMultiLevelGroup<ItemType>(groupOrItem: MultiLevelGroupOrItem<ItemType>): groupOrItem is IMultiLevelGroup<ItemType> {
321
- let asGroup = groupOrItem as IMultiLevelGroup<ItemType>;
322
- return !isNullOrUndefined(asGroup.subGroups) && Array.isArray(asGroup.groupItems) && isNumber(asGroup.index) && isNumber(asGroup.depth);
323
- }
324
-
325
- /** returns a flat array of groups>items ordered by groups */
326
- export function FlattenGroupItems<ItemType>(groups: IDictionary<IMultiLevelGroup<ItemType>>) {
327
- let flatItems: MultiLevelGroupOrItem<ItemType>[] = [];
328
- Object.keys(groups).forEach(groupName => {
329
- let group = groups[groupName];
330
- if (!isNullOrEmptyString(groupName))
331
- flatItems.push(group);
332
- let subGroups = Object.keys(group.subGroups);
333
- if (isNotEmptyArray(subGroups)) {
334
- flatItems.push(...FlattenGroupItems(group.subGroups));
335
- }
336
- else flatItems.push(...group.groupItems);
337
- });
338
-
339
- return flatItems;
340
- }
341
-
342
- /** split a collection by page size and return the info */
343
- export function GetPagedCollectionInfo<T>(collection: Array<T>, pageSize: number, currentPage?: number) {
344
- let pagedItems: (T[])[] = [];
345
-
346
- if (pageSize < 1) {
347
- pagedItems = [collection.slice()];
348
- }
349
- else {
350
- let copy = collection.slice();
351
- while (isNotEmptyArray(copy)) {
352
- pagedItems.push(copy.splice(0, pageSize));
353
- }
354
- }
355
-
356
- currentPage = isNumber(currentPage) && currentPage >= 0 && currentPage < pagedItems.length ? currentPage : 0;
357
- return {
358
- /** nubmer of pages */
359
- pages: pagedItems.length,
360
- /** page items, per page (Array of pages, each has an array of the page items) */
361
- pagedItems: pagedItems,
362
- /** the current page */
363
- currentPage: currentPage,
364
- /** the current page items */
365
- currentPageItems: pagedItems[currentPage] || [],
366
- /** has more than 1 page */
367
- hasPages: pagedItems.length > 1,
368
- allowPrev: currentPage > 0,
369
- allowNext: currentPage < pagedItems.length - 1
370
- };
371
- }
372
-
373
- /** use with sortArray or get unique array to handle premitive types or dates, with a JSON.stringify to all other values */
374
- export function defaultPrimitiveGetValue<T>(item: T) {
375
- return isNullOrUndefined(item)
376
- ? ""
377
- : isDate(item) ? item.getTime()
378
- : isBoolean(item)
379
- ? item === true ? 1 : 0
380
- : isNumber(item) || isString(item)
381
- ? item
382
- : JSON.stringify(item);
383
- }
384
-
385
- export function RemoveItemFromArr<T>(arr: T[], item: T) {
386
- let idx = arr.indexOf(item);
387
- if (idx >= 0)
388
- arr.splice(idx, 1);
389
- }
390
- export function PushNoDuplicate<T>(arr: T[], item: T) {
391
- if (!arr.includes(item)) arr.push(item);
392
- }
393
- /** fills an array with a value. Array.fill isn't available on SPFx. */
394
- export function ArrayFill<T>(arr: T[], value: T, onlyEmpty?: boolean) {
395
- for (let i = 0; i < arr.length; i++) {
396
- if (onlyEmpty !== true || isNullOrUndefined(arr[i]))
397
- arr[i] = value;
398
- }
399
- return arr;
400
- }
401
-
402
- /** give a name and a collection, and it will return a unique name availalbe, suffixing a _# to the name
403
- * example: file
404
- * return file, file_2, file_9 etc... whichever is availalbe first.
405
- */
406
- export function FindNextAvailableName(name: string, usedNames: string[], options?: {
407
- //check for specific letter case
408
- caseSensitive?: boolean;
409
- //append suffix when adding _# for example: file_1.docx, file_2.docx etc
410
- suffix?: string;
411
- }) {
412
- let nameForTest = name;
413
- if (options && options.caseSensitive !== true) {
414
- usedNames = usedNames.map(n => n.toLowerCase());
415
- nameForTest = name.toLowerCase();
416
- }
417
-
418
- let nameSuffix = options && options.suffix || "";
419
-
420
- let suffixIdx = 0;
421
- let suffixStr = "";
422
- while (usedNames.indexOf(`${nameForTest}${suffixStr}${nameSuffix}`) >= 0) {
423
- suffixIdx++;
424
- suffixStr = "_" + suffixIdx;
425
- }
426
-
427
- return `${name}${suffixStr}${nameSuffix}`;
428
- }
429
-
430
- //** returns an array of numbers from 0,1,2... */
431
- export function numbersArray<T extends number>(length: number, startFrom: number = 0) {
432
- //dvp build will fail without any type
433
- if (isNullOrUndefined(length) || length < 0) length = 0;
434
- let arr: number[] = Array.from((Array(length) as any).keys());
435
- return startFrom > 0
436
- ? arr.map(i => i + startFrom) as T[]
437
- : arr as T[];
1
+
2
+ /** this file will only use basic type checker and types, do not add functions that require
3
+ * a reference to tother helpers
4
+ */
5
+ import { IDictionary } from "../types/common.types";
6
+ import { getFromFullName, isBoolean, isDate, isFunction, isNotEmptyArray, isNullOrEmptyArray, isNullOrEmptyString, isNullOrUndefined, isNumber, isString, isTypeofFullNameFunction } from "./typecheckers";
7
+
8
+ /** this will support HtmlCollectionOf, Arrays, and any other types that have a length and indexer Issue 568 */
9
+ export interface IndexedCollection<ElementType> {
10
+ length: number;
11
+ [index: number]: ElementType;
12
+ }
13
+
14
+ /** Finds an object in array based on a filter and moves it to the start of the array */
15
+ export function moveToStart<T>(arr: T[], filter?: (item: T) => boolean) {
16
+ let index = firstIndexOf<T>(arr, filter);
17
+
18
+ if (index > 0) {
19
+ let obj = arr[index];
20
+ arr.splice(index, 1);
21
+ arr.unshift(obj);
22
+ }
23
+ }
24
+
25
+ /** Finds an object in array based on a filter and moves it to the end of the array */
26
+ export function moveToEnd<T>(arr: T[], filter?: (item: T) => boolean) {
27
+ let index = firstIndexOf<T>(arr, filter);
28
+
29
+ if (index !== -1 && index !== arr.length - 1) {
30
+ let obj = arr[index];
31
+ arr.splice(index, 1);
32
+ arr.push(obj);
33
+ }
34
+ }
35
+
36
+ /** Get the first index of an object of an array, or -1 if the array is empty / null */
37
+ // export function firstIndexOf<T extends Element>(arr: HTMLCollectionOf<T>, filter?: (item: T, index?: number) => boolean, startFrom?: number): number;
38
+ // export function firstIndexOf<T>(arr: T[], filter?: (item: T, index?: number) => boolean, startFrom?: number): number;
39
+ export function firstIndexOf<T>(arr: IndexedCollection<T>, filter?: (item: T, index?: number) => boolean, startFrom?: number): number {
40
+ if (!isNullOrUndefined(arr) && arr.length > 0) {
41
+ if (isFunction(filter)) {
42
+ //use for loop so we can stop when it is found
43
+ for (let i = startFrom > 0 ? startFrom : 0; i < arr.length; i++)
44
+ if (filter(arr[i], i) === true)
45
+ return i;
46
+ }
47
+ else return 0;
48
+ }
49
+
50
+ return -1;
51
+ }
52
+ /** Get the first object of an array, or null if the array is empty / null
53
+ * If you pass a filter, it will find the first element that matches the filter and return it, stopping the loop when it is found
54
+ * */
55
+ export function firstOrNull<T>(arr: IndexedCollection<T>, filter?: (item: T, index?: number) => boolean): T {
56
+ let index = firstIndexOf(arr, filter);
57
+ return index < 0 ? null : arr[index];
58
+ }
59
+
60
+
61
+ /** Get the last index of an object of an array, or -1 if the array is empty / null */
62
+ export function lastIndexOf<T>(arr: IndexedCollection<T>, filter?: (item: T) => boolean): number {
63
+ if (!isNullOrUndefined(arr) && arr.length > 0) {
64
+ if (isFunction(filter)) {
65
+ //use for loop so we can stop when it is found
66
+ for (let i = arr.length - 1; i >= 0; i--)
67
+ if (filter(arr[i]) === true)
68
+ return i;
69
+ }
70
+ else return arr.length - 1;
71
+ }
72
+
73
+ return -1;
74
+ }
75
+
76
+ /** get the last element or null */
77
+ export function lastOrNull<T>(arr: IndexedCollection<T>, filter?: (item: T) => boolean): T {
78
+ let index = lastIndexOf(arr as T[], filter);
79
+ return index < 0 ? null : arr[index];
80
+ }
81
+
82
+ /** Get the first index of an object of an array, or -1 if the array is empty / null */
83
+ export async function firstIndexOfAsync<T>(arr: IndexedCollection<T>, filter?: (item: T, index?: number) => Promise<boolean>, startFrom?: number): Promise<number> {
84
+ if (!isNullOrUndefined(arr) && arr.length > 0) {
85
+ if (isFunction(filter)) {
86
+ //use for loop so we can stop when it is found
87
+ for (let i = startFrom > 0 ? startFrom : 0; i < arr.length; i++)
88
+ if ((await filter(arr[i], i)) === true)
89
+ return i;
90
+ }
91
+ else return 0;
92
+ }
93
+
94
+ return -1;
95
+ }
96
+ /** Get the first object of an array, or null if the array is empty / null
97
+ * If you pass a filter, it will find the first element that matches the filter and return it, stopping the loop when it is found
98
+ * */
99
+ export async function firstOrNullAsync<T>(arr: IndexedCollection<T>, filter?: (item: T, index?: number) => Promise<boolean>): Promise<T> {
100
+ let index = await firstIndexOfAsync(arr, filter);
101
+ return index < 0 ? null : arr[index];
102
+ }
103
+
104
+
105
+ /** Get the last index of an object of an array, or -1 if the array is empty / null */
106
+ export async function lastIndexOfAsync<T>(arr: IndexedCollection<T>, filter?: (item: T) => Promise<boolean>): Promise<number> {
107
+ if (!isNullOrUndefined(arr) && arr.length > 0) {
108
+ if (isFunction(filter)) {
109
+ //use for loop so we can stop when it is found
110
+ for (let i = arr.length - 1; i >= 0; i--)
111
+ if ((await filter(arr[i])) === true)
112
+ return i;
113
+ }
114
+ else return arr.length - 1;
115
+ }
116
+
117
+ return -1;
118
+ }
119
+
120
+ /** get the last element or null */
121
+ export async function lastOrNullAsync<T>(arr: IndexedCollection<T>, filter?: (item: T) => Promise<boolean>): Promise<T> {
122
+ let index = await lastIndexOfAsync(arr, filter);
123
+ return index < 0 ? null : arr[index];
124
+ }
125
+
126
+ /** Sorts an array of complex objects, use defaultPrimitiveGetValue for default functionality */
127
+ export function sortArray<T>(arr: T[], getValue: (item: T) => number | string) {
128
+ if (!isNullOrEmptyArray(arr)) {
129
+ if (isTypeofFullNameFunction("Intl.Collator")) {
130
+ //todo: should probably use the SharePoint locale isntead of 'undefined'
131
+ let collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
132
+ arr.sort((a, b) => {
133
+ let va = getValue(a);
134
+ let vb = getValue(b);
135
+ return collator.compare(va as string, vb as string);
136
+ });
137
+ } else {
138
+ arr.sort((a, b) => {
139
+ let va = getValue(a);
140
+ if (isString(va)) va = va.toLowerCase();
141
+ let vb = getValue(b);
142
+ if (isString(vb)) vb = vb.toLowerCase();
143
+ return va === vb ? 0 : va > vb ? 1 : -1;
144
+ });
145
+ }
146
+ }
147
+ return arr;
148
+ }
149
+
150
+ /** removes null, undefined or "" elements from the array */
151
+ export function filterEmptyEntries<T>(arr: T[]) {
152
+ return arr.filter(val => !isNullOrEmptyString(val));
153
+ }
154
+
155
+
156
+ export function sortNumberArrayAsc(a: number, b: number): number {
157
+ return a - b;
158
+ }
159
+ export function sortNumberArray(a: number, b: number): number {
160
+ return b - a;
161
+ }
162
+ /** call a foreach on an object or an array, with an option to break when returning false */
163
+ export function forEach<T>(obj: IDictionary<T> | Array<T> | { [key: number]: T; length: number; }, func: (propertyName: string, propertyValue: T, _args?: any) => void | boolean, args?: any) {
164
+ if (obj && func && isFunction(func)) {
165
+ if (Array.isArray(obj) || obj.constructor && getFromFullName("constructor.name", obj) === "Array") {
166
+ for (let i = 0; i < (obj as Array<T>).length; i++) {
167
+ let property = i;
168
+ let value = obj[property];
169
+ let result = func(property.toString(10), value, args);
170
+ if (result === false) {
171
+ break;
172
+ }
173
+ }
174
+ }
175
+ else {
176
+ let keys = Object.keys(obj);
177
+ for (let i = 0; i < keys.length; i++) {
178
+ let property = keys[i];
179
+ let value = obj[property];
180
+ let result = func(property, value, args);
181
+ if (result === false) {
182
+ break;
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ export async function forEachAsync<ElmType, ResultType>(arr: Array<ElmType> | IDictionary<ElmType>, handler: (elm: ElmType, index: number) => Promise<ResultType>, options?: { parallel?: boolean; }) {
190
+ if (!isNullOrUndefined(arr) && Object.keys(arr).length > 0) {
191
+ let keys = Object.keys(arr);
192
+ if (options && options.parallel) {
193
+ let promises: Promise<ResultType>[] = [];
194
+ keys.forEach((key, i) => {
195
+ promises.push(handler(arr[key], i));
196
+ });
197
+ return Promise.all(promises);
198
+ }
199
+ else {
200
+ let results: ResultType[] = [];
201
+ for (let i = 0; i < keys.length; i++) {
202
+ results.push(await handler(arr[keys[i]], i));
203
+ }
204
+ return results;
205
+ }
206
+ }
207
+ else return []
208
+ }
209
+
210
+ export function sizeOf(obj: any) {
211
+ if (Array.isArray(obj))
212
+ return obj.length;
213
+ return Object.keys(obj).length;
214
+ }
215
+
216
+ export function chunkArray<T>(array: T[], chunkSize: number) {
217
+ let chunkedArray: T[][] = [];
218
+ for (var i = 0; i < array.length; i += chunkSize) {
219
+ chunkedArray.push(array.slice(i, i + chunkSize));
220
+ }
221
+ return chunkedArray;
222
+ }
223
+
224
+ /** Takes an array and transforms it into a hash. this will assign 1 item per key, assumig getKey will be unique per item. */
225
+ export function toHash<T, Y = T>(arr: T[], getKey: (element: T) => string | number, filter?: (element: T) => boolean, transformValue?: (element: T) => Y): IDictionary<Y> {
226
+ let hash: { [key: string | number]: Y; } = {};
227
+ if (!isFunction(transformValue)) transformValue = v => v as any as Y;
228
+ if (isNotEmptyArray(arr))
229
+ arr.forEach(i => {
230
+ if (!isFunction(filter) || filter(i))
231
+ hash[getKey(i)] = transformValue(i);
232
+ });
233
+ return hash;
234
+ }
235
+
236
+ /** Returns an array from the values of the dictionary. */
237
+ export function toArray<Source, Result = Source>(hash: { [key: string]: Source; }, filter?: (element: Source) => boolean, transform?: (key: string, element: Source) => Result): Result[] {
238
+ let arr: Result[] = [];
239
+ if (!isFunction(transform)) transform = (key, element) => element as any as Result;
240
+
241
+ if (!isNullOrUndefined(hash))
242
+ Object.keys(hash).forEach(key => {
243
+ if (!isFunction(filter) || filter(hash[key]))
244
+ arr.push(transform(key, hash[key]));
245
+ });
246
+ return arr;
247
+ }
248
+
249
+ /** returns a new dictionary, converting each entry in source using the transform function */
250
+ export function convertDictionary<S, R>(source: IDictionary<S>, transform: (sourceItem: S) => R): IDictionary<R> {
251
+ let result: IDictionary<R> = {};
252
+ forEach(source, (key, value) => { result[key] = transform(value); });
253
+ return result;
254
+ }
255
+
256
+ export function flattenArray<T>(array: (T | T[])[]) {
257
+ return array.reduce((acc, val) => (acc as T[]).concat(val), []) as T[];
258
+ }
259
+
260
+ /** careful, does not work for date/complex objects. Use GetUniqueArrayInfo if you suspect you might have Date/complex objects. */
261
+ export function makeUniqueArray<T>(arr: T[]) {
262
+ return arr.filter((v, i, a) => a.indexOf(v) === i);
263
+ }
264
+
265
+ /** return an array of unique values, and the first index they were found, use defaultPrimitiveGetValue for default functionality */
266
+ export function GetUniqueArrayInfo<T, V>(arr: T[], getValue: (item: T) => V) {
267
+ var uniqueValues: { item: T; value: V; firstIndex: number; }[] = [];
268
+ var uniqueArray: T[] = [];
269
+ var foundValues: V[] = [];
270
+ var hasDuplicates = false;
271
+ var duplicateIndexes: number[] = [];
272
+
273
+ if (isNotEmptyArray(arr)) {
274
+ arr.forEach((item, index) => {
275
+ let value = getValue(item);
276
+ if (foundValues.includes(value)) {
277
+ hasDuplicates = true;
278
+ duplicateIndexes.push(index);
279
+ }
280
+ else {
281
+ foundValues.push(value);
282
+ uniqueValues.push({ item: item, value: value, firstIndex: index });
283
+ uniqueArray.push(item);
284
+ }
285
+ });
286
+ }
287
+
288
+ return {
289
+ /** true if duplicate values found */
290
+ hasDuplicates: hasDuplicates,
291
+ /** all duplicate item indexes */
292
+ duplicateIndexes: duplicateIndexes,
293
+ /** unique values and their info */
294
+ uniqueValues: uniqueValues,
295
+ /** the unique version of this array */
296
+ uniqueArray: uniqueArray
297
+ };
298
+ }
299
+
300
+ export interface IMultiLevelGroupItem<ItemType> {
301
+ parentGroup: IMultiLevelGroup<ItemType>;
302
+ }
303
+ export interface IMultiLevelGroup<ItemType> {
304
+ groupItems: (ItemType & IMultiLevelGroupItem<ItemType>)[];
305
+ subGroups: IDictionary<IMultiLevelGroup<ItemType>>;
306
+ depth: number;
307
+ parentGroup: IMultiLevelGroup<ItemType>;
308
+ /** would contain the path to the group, such as: 0_1_3 for first group, second sub group, 4th sub-sub group */
309
+ key: string;
310
+ index: number;
311
+ title: string;
312
+ /** Optional, add a prefix to the group. For example: "Priority > " to fullTitle will be "Priority > High" */
313
+ groupPrefix?: string;
314
+ /** title with decorations, such as prefix */
315
+ fullTitle: string;
316
+ }
317
+ export type MultiLevelGroupItem<ItemType> = ItemType & IMultiLevelGroupItem<ItemType>;
318
+ export type MultiLevelGroupOrItem<ItemType> = MultiLevelGroupItem<ItemType> | IMultiLevelGroup<ItemType>;
319
+ /** returns true if the element is a group of items */
320
+ export function IsMultiLevelGroup<ItemType>(groupOrItem: MultiLevelGroupOrItem<ItemType>): groupOrItem is IMultiLevelGroup<ItemType> {
321
+ let asGroup = groupOrItem as IMultiLevelGroup<ItemType>;
322
+ return !isNullOrUndefined(asGroup.subGroups) && Array.isArray(asGroup.groupItems) && isNumber(asGroup.index) && isNumber(asGroup.depth);
323
+ }
324
+
325
+ /** returns a flat array of groups>items ordered by groups */
326
+ export function FlattenGroupItems<ItemType>(groups: IDictionary<IMultiLevelGroup<ItemType>>) {
327
+ let flatItems: MultiLevelGroupOrItem<ItemType>[] = [];
328
+ Object.keys(groups).forEach(groupName => {
329
+ let group = groups[groupName];
330
+ if (!isNullOrEmptyString(groupName))
331
+ flatItems.push(group);
332
+ let subGroups = Object.keys(group.subGroups);
333
+ if (isNotEmptyArray(subGroups)) {
334
+ flatItems.push(...FlattenGroupItems(group.subGroups));
335
+ }
336
+ else flatItems.push(...group.groupItems);
337
+ });
338
+
339
+ return flatItems;
340
+ }
341
+
342
+ /** split a collection by page size and return the info */
343
+ export function GetPagedCollectionInfo<T>(collection: Array<T>, pageSize: number, currentPage?: number) {
344
+ let pagedItems: (T[])[] = [];
345
+
346
+ if (pageSize < 1) {
347
+ pagedItems = [collection.slice()];
348
+ }
349
+ else {
350
+ let copy = collection.slice();
351
+ while (isNotEmptyArray(copy)) {
352
+ pagedItems.push(copy.splice(0, pageSize));
353
+ }
354
+ }
355
+
356
+ currentPage = isNumber(currentPage) && currentPage >= 0 && currentPage < pagedItems.length ? currentPage : 0;
357
+ return {
358
+ /** nubmer of pages */
359
+ pages: pagedItems.length,
360
+ /** page items, per page (Array of pages, each has an array of the page items) */
361
+ pagedItems: pagedItems,
362
+ /** the current page */
363
+ currentPage: currentPage,
364
+ /** the current page items */
365
+ currentPageItems: pagedItems[currentPage] || [],
366
+ /** has more than 1 page */
367
+ hasPages: pagedItems.length > 1,
368
+ allowPrev: currentPage > 0,
369
+ allowNext: currentPage < pagedItems.length - 1
370
+ };
371
+ }
372
+
373
+ /** use with sortArray or get unique array to handle premitive types or dates, with a JSON.stringify to all other values */
374
+ export function defaultPrimitiveGetValue<T>(item: T) {
375
+ return isNullOrUndefined(item)
376
+ ? ""
377
+ : isDate(item) ? item.getTime()
378
+ : isBoolean(item)
379
+ ? item === true ? 1 : 0
380
+ : isNumber(item) || isString(item)
381
+ ? item
382
+ : JSON.stringify(item);
383
+ }
384
+
385
+ export function RemoveItemFromArr<T>(arr: T[], item: T) {
386
+ let idx = arr.indexOf(item);
387
+ if (idx >= 0)
388
+ arr.splice(idx, 1);
389
+ }
390
+ export function PushNoDuplicate<T>(arr: T[], item: T) {
391
+ if (!arr.includes(item)) arr.push(item);
392
+ }
393
+ /** fills an array with a value. Array.fill isn't available on SPFx. */
394
+ export function ArrayFill<T>(arr: T[], value: T, onlyEmpty?: boolean) {
395
+ for (let i = 0; i < arr.length; i++) {
396
+ if (onlyEmpty !== true || isNullOrUndefined(arr[i]))
397
+ arr[i] = value;
398
+ }
399
+ return arr;
400
+ }
401
+
402
+ /** give a name and a collection, and it will return a unique name availalbe, suffixing a _# to the name
403
+ * example: file
404
+ * return file, file_2, file_9 etc... whichever is availalbe first.
405
+ */
406
+ export function FindNextAvailableName(name: string, usedNames: string[], options?: {
407
+ //check for specific letter case
408
+ caseSensitive?: boolean;
409
+ //append suffix when adding _# for example: file_1.docx, file_2.docx etc
410
+ suffix?: string;
411
+ }) {
412
+ let nameForTest = name;
413
+ if (options && options.caseSensitive !== true) {
414
+ usedNames = usedNames.map(n => n.toLowerCase());
415
+ nameForTest = name.toLowerCase();
416
+ }
417
+
418
+ let nameSuffix = options && options.suffix || "";
419
+
420
+ let suffixIdx = 0;
421
+ let suffixStr = "";
422
+ while (usedNames.indexOf(`${nameForTest}${suffixStr}${nameSuffix}`) >= 0) {
423
+ suffixIdx++;
424
+ suffixStr = "_" + suffixIdx;
425
+ }
426
+
427
+ return `${name}${suffixStr}${nameSuffix}`;
428
+ }
429
+
430
+ //** returns an array of numbers from 0,1,2... */
431
+ export function numbersArray<T extends number>(length: number, startFrom: number = 0) {
432
+ //dvp build will fail without any type
433
+ if (isNullOrUndefined(length) || length < 0) length = 0;
434
+ let arr: number[] = Array.from((Array(length) as any).keys());
435
+ return startFrom > 0
436
+ ? arr.map(i => i + startFrom) as T[]
437
+ : arr as T[];
438
438
  }