MOBILE-4565 utils: Move some array functions to CoreArray

main
Pau Ferrer Ocaña 2024-04-23 09:35:38 +02:00
parent f9ddfb48c9
commit 93b420311d
13 changed files with 92 additions and 41 deletions

View File

@ -28,6 +28,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreArray } from '@singletons/array';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreTime } from '@singletons/time';
@ -218,7 +219,7 @@ export class AddonBlogIndexPage implements OnInit, OnDestroy {
if (refresh) {
this.entries = result.entries;
} else {
this.entries = CoreUtils.uniqueArray(this.entries
this.entries = CoreArray.unique(this.entries
.concat(result.entries), 'id')
.sort((a, b) => b.created - a.created);
}

View File

@ -36,6 +36,7 @@ import { CoreTextUtils } from '@services/utils/text';
import { CoreXAPIIRI } from '@features/xapi/classes/iri';
import { CoreXAPIItemAgent } from '@features/xapi/classes/item-agent';
import { CoreWSError } from '@classes/errors/wserror';
import { CoreArray } from '@singletons/array';
/**
* Service to sync H5P activities.
@ -76,7 +77,7 @@ export class AddonModH5PActivitySyncProvider extends CoreCourseActivitySyncBaseP
]);
const entries = (<(CoreXAPIStatementDBRecord|CoreXAPIStateDBRecord)[]> statements).concat(states);
const contextIds = CoreUtils.uniqueArray(entries.map(entry => 'contextid' in entry ? entry.contextid : entry.itemid));
const contextIds = CoreArray.unique(entries.map(entry => 'contextid' in entry ? entry.contextid : entry.itemid));
// Sync all activities.
const promises = contextIds.map(async (contextId) => {

View File

@ -24,6 +24,7 @@ import { CoreEvents } from '@singletons/events';
import { AddonNotesDBRecord, AddonNotesDeletedDBRecord } from './database/notes';
import { AddonNotes, AddonNotesCreateNoteData } from './notes';
import { AddonNotesOffline } from './notes-offline';
import { CoreArray } from '@singletons/array';
/**
* Service to sync notes.
@ -67,7 +68,7 @@ export class AddonNotesSyncProvider extends CoreSyncBaseProvider<AddonNotesSyncR
courseIds = courseIds.concat(notes.map((note) => note.courseid));
});
CoreUtils.uniqueArray(courseIds);
CoreArray.unique(courseIds);
// Sync all courses.
const promises = courseIds.map(async (courseId) => {

View File

@ -40,6 +40,7 @@ import { CoreDirectivesRegistry } from '@singletons/directives-registry';
import { CorePromisedValue } from '@classes/promised-value';
import { CorePlatform } from '@services/platform';
import { CoreTextUtils } from '@services/utils/text';
import { CoreArray } from '@singletons/array';
/**
* Directive to handle external content.
@ -279,7 +280,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
return;
}
const urls = CoreUtils.uniqueArray(Array.from(inlineStyles.match(/https?:\/\/[^"') ;]*/g) ?? []));
const urls = CoreArray.unique(Array.from(inlineStyles.match(/https?:\/\/[^"') ;]*/g) ?? []));
if (!urls.length) {
return;
}

View File

@ -17,6 +17,7 @@ import { makeSingleton } from '@singletons';
import { CoreEnrolAction, CoreEnrolDelegate, CoreEnrolInfoIcon } from './enrol-delegate';
import { CoreUtils } from '@services/utils/utils';
import { CoreEnrol, CoreEnrolEnrolmentMethod } from './enrol';
import { CoreArray } from '@singletons/array';
/**
* Service that provides helper functions for enrolment plugins.
@ -32,7 +33,7 @@ export class CoreEnrolHelperService {
* @returns Enrolment icons to show.
*/
async getEnrolmentIcons(methodTypes: string[], courseId: number): Promise<CoreEnrolInfoIcon[]> {
methodTypes = CoreUtils.uniqueArray(methodTypes);
methodTypes = CoreArray.unique(methodTypes);
let enrolmentIcons: CoreEnrolInfoIcon[] = [];
let addBrowserOption = false;

View File

@ -15,7 +15,7 @@
import { Injectable } from '@angular/core';
import { CorePlatform } from '@services/platform';
import { CoreUtils } from '@services/utils/utils';
import { CoreArray } from '@singletons/array';
import { makeSingleton } from '@singletons';
import { CoreFileUploaderHandler, CoreFileUploaderHandlerData, CoreFileUploaderHandlerResult } from '../fileuploader-delegate';
import { CoreFileUploaderHelper } from '../fileuploader-helper';
@ -41,7 +41,7 @@ export class CoreFileUploaderAlbumHandlerService implements CoreFileUploaderHand
*/
getSupportedMimetypes(mimetypes: string[]): string[] {
// Album allows picking images and videos.
return CoreUtils.filterByRegexp(mimetypes, /^(image|video)\//);
return CoreArray.filterByRegexp(mimetypes, /^(image|video)\//);
}
/**

View File

@ -16,10 +16,11 @@ import { Injectable } from '@angular/core';
import { CoreApp } from '@services/app';
import { CorePlatform } from '@services/platform';
import { CoreUtils } from '@services/utils/utils';
import { CoreArray } from '@singletons/array';
import { makeSingleton } from '@singletons';
import { CoreFileUploaderHandler, CoreFileUploaderHandlerData, CoreFileUploaderHandlerResult } from '../fileuploader-delegate';
import { CoreFileUploaderHelper } from '../fileuploader-helper';
/**
* Handler to record an audio to upload it.
*/
@ -42,10 +43,10 @@ export class CoreFileUploaderAudioHandlerService implements CoreFileUploaderHand
getSupportedMimetypes(mimetypes: string[]): string[] {
if (CorePlatform.isIOS()) {
// In iOS it's recorded as WAV.
return CoreUtils.filterByRegexp(mimetypes, /^audio\/wav$/);
return CoreArray.filterByRegexp(mimetypes, /^audio\/wav$/);
} else if (CorePlatform.isAndroid()) {
// In Android we don't know the format the audio will be recorded, so accept any audio mimetype.
return CoreUtils.filterByRegexp(mimetypes, /^audio\//);
return CoreArray.filterByRegexp(mimetypes, /^audio\//);
} else {
// In browser, support audio formats that are supported by MediaRecorder.
if (MediaRecorder) {

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreApp } from '@services/app';
import { CorePlatform } from '@services/platform';
import { CoreUtils } from '@services/utils/utils';
import { CoreArray } from '@singletons/array';
import { makeSingleton } from '@singletons';
import { CoreFileUploaderHandler, CoreFileUploaderHandlerData, CoreFileUploaderHandlerResult } from '../fileuploader-delegate';
import { CoreFileUploaderHelper } from '../fileuploader-helper';
@ -42,7 +42,7 @@ export class CoreFileUploaderCameraHandlerService implements CoreFileUploaderHan
*/
getSupportedMimetypes(mimetypes: string[]): string[] {
// Camera only supports JPEG and PNG.
return CoreUtils.filterByRegexp(mimetypes, /^image\/(jpeg|png)$/);
return CoreArray.filterByRegexp(mimetypes, /^image\/(jpeg|png)$/);
}
/**

View File

@ -16,10 +16,11 @@ import { Injectable } from '@angular/core';
import { CoreApp } from '@services/app';
import { CorePlatform } from '@services/platform';
import { CoreUtils } from '@services/utils/utils';
import { CoreArray } from '@singletons/array';
import { makeSingleton } from '@singletons';
import { CoreFileUploaderHandler, CoreFileUploaderHandlerData, CoreFileUploaderHandlerResult } from '../fileuploader-delegate';
import { CoreFileUploaderHelper } from '../fileuploader-helper';
/**
* Handler to record a video to upload it.
*/
@ -42,10 +43,10 @@ export class CoreFileUploaderVideoHandlerService implements CoreFileUploaderHand
getSupportedMimetypes(mimetypes: string[]): string[] {
if (CorePlatform.isIOS()) {
// In iOS it's recorded as MOV.
return CoreUtils.filterByRegexp(mimetypes, /^video\/quicktime$/);
return CoreArray.filterByRegexp(mimetypes, /^video\/quicktime$/);
} else if (CorePlatform.isAndroid()) {
// In Android we don't know the format the video will be recorded, so accept any video mimetype.
return CoreUtils.filterByRegexp(mimetypes, /^video\//);
return CoreArray.filterByRegexp(mimetypes, /^video\//);
} else {
// In browser, support video formats that are supported by MediaRecorder.
if (MediaRecorder) {

View File

@ -17,6 +17,7 @@ import { CoreUtils } from '@services/utils/utils';
import { CoreH5P } from '@features/h5p/services/h5p';
import { Translate } from '@singletons';
import { CoreH5PCore, CoreH5PLibraryData, CoreH5PLibraryAddonData, CoreH5PContentDepsTreeDependency } from './core';
import { CoreArray } from '@singletons/array';
const ALLOWED_STYLEABLE_TAGS = ['span', 'p', 'div', 'h1', 'h2', 'h3', 'td'];
@ -131,7 +132,7 @@ export class CoreH5PContentValidator {
tags.push('s');
}
tags = CoreUtils.uniqueArray(tags);
tags = CoreArray.unique(tags);
// Determine allowed style tags
const stylePatterns: RegExp[] = [];
@ -372,7 +373,7 @@ export class CoreH5PContentValidator {
if (semantics.extraAttributes) {
validKeys = validKeys.concat(semantics.extraAttributes);
}
validKeys = CoreUtils.uniqueArray(validKeys);
validKeys = CoreArray.unique(validKeys);
this.filterParams(file, validKeys);
@ -556,7 +557,7 @@ export class CoreH5PContentValidator {
let validKeys = ['library', 'params', 'subContentId', 'metadata'];
if (semantics.extraAttributes) {
validKeys = CoreUtils.uniqueArray(validKeys.concat(semantics.extraAttributes));
validKeys = CoreArray.unique(validKeys.concat(semantics.extraAttributes));
}
this.filterParams(value, validKeys);

View File

@ -40,6 +40,7 @@ import { CoreCancellablePromise } from '@classes/cancellable-promise';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from './url';
import { QRScanner } from '@features/native/plugins';
import { CoreArray } from '@singletons/array';
export type TreeNode<T> = T & { children: TreeNode<T>[] };
@ -350,6 +351,7 @@ export class CoreUtilsProvider {
* @param from Object to copy the properties from.
* @param to Object where to store the properties.
* @param clone Whether the properties should be cloned (so they are different instances).
* @deprecated since 4.4. Not used anymore.
*/
copyProperties(from: Record<string, unknown>, to: Record<string, unknown>, clone: boolean = true): void {
for (const name in from) {
@ -387,6 +389,7 @@ export class CoreUtilsProvider {
* Empties an array without losing its reference.
*
* @param array Array to empty.
* @deprecated since 4.4. Not used anymore.
*/
emptyArray(array: unknown[]): void {
array.length = 0; // Empty array without losing its reference.
@ -396,6 +399,7 @@ export class CoreUtilsProvider {
* Removes all properties from an object without losing its reference.
*
* @param object Object to remove the properties.
* @deprecated since 4.4. Not used anymore.
*/
emptyObject(object: Record<string, unknown>): void {
for (const key in object) {
@ -482,17 +486,10 @@ export class CoreUtilsProvider {
* @param array Array to filter.
* @param regex RegExp to apply to each string.
* @returns Filtered array.
* @deprecated since 4.4. Use CoreArray.filterByRegexp instead.
*/
filterByRegexp(array: string[], regex: RegExp): string[] {
if (!array || !array.length) {
return [];
}
return array.filter((entry) => {
const matches = entry.match(regex);
return matches && matches.length;
});
return CoreArray.filterByRegexp(array, regex);
}
/**
@ -956,7 +953,7 @@ export class CoreUtilsProvider {
* @returns Merged array.
*/
mergeArraysWithoutDuplicates<T>(array1: T[], array2: T[], key?: string): T[] {
return this.uniqueArray(array1.concat(array2), key) as T[];
return CoreArray.unique(array1.concat(array2), key) as T[];
}
/**
@ -1390,6 +1387,7 @@ export class CoreUtilsProvider {
* @param data Object.
* @param prefix Prefix to add.
* @returns Prefixed object.
* @deprecated since 4.4. Not used anymore.
*/
prefixKeys(data: Record<string, unknown>, prefix: string): Record<string, unknown> {
const newObj = {};
@ -1611,21 +1609,10 @@ export class CoreUtilsProvider {
* @param array The array to treat.
* @param [key] Key of the property that must be unique. If not specified, the whole entry.
* @returns Array without duplicate values.
* @deprecated since 4.4. Use CoreArray.unique instead.
*/
uniqueArray<T>(array: T[], key?: string): T[] {
const unique = {}; // Use an object to make it faster to check if it's duplicate.
return array.filter(entry => {
const value = key ? entry[key] : entry;
if (value in unique) {
return false;
}
unique[value] = true;
return true;
});
return CoreArray.unique(array, key);
}
/**

View File

@ -66,4 +66,46 @@ export class CoreArray {
return newArray;
}
/**
* Return an array without duplicate values.
*
* @param array The array to treat.
* @param [key] Key of the property that must be unique. If not specified, the whole entry.
* @returns Array without duplicate values.
*/
static unique<T>(array: T[], key?: string): T[] {
const unique = {}; // Use an object to make it faster to check if it's duplicate.
return array.filter(entry => {
const value = key ? entry[key] : entry;
if (value in unique) {
return false;
}
unique[value] = true;
return true;
});
}
/**
* Given an array of strings, return only the ones that match a regular expression.
*
* @param array Array to filter.
* @param regex RegExp to apply to each string.
* @returns Filtered array.
*/
static filterByRegexp(array: string[], regex: RegExp): string[] {
if (!array || !array.length) {
return [];
}
return array.filter((entry) => {
const matches = entry.match(regex);
return matches && matches.length;
});
}
}

View File

@ -23,4 +23,18 @@ describe('CoreArray singleton', () => {
expect(CoreArray.withoutItem(originalArray, 'not found')).toEqual(['foo', 'bar', 'baz']);
});
it('gets unique array', () => {
const originalArray = ['foo', 'bar', 'foo', 'baz'];
expect(CoreArray.unique(originalArray)).toEqual(['foo', 'bar', 'baz']);
});
it('filters array by regexp', () => {
const originalArray = ['foo', 'bar', 'baz', 'qux'];
expect(CoreArray.filterByRegexp(originalArray, /ba/)).toEqual(['bar', 'baz']);
expect(CoreArray.filterByRegexp(originalArray, /foo/)).toEqual(['foo']);
expect(CoreArray.filterByRegexp([], /foo/)).toEqual([]);
});
});