From 93b420311dd63a83b30c58d06dda879fc47ca1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 23 Apr 2024 09:35:38 +0200 Subject: [PATCH] MOBILE-4565 utils: Move some array functions to CoreArray --- src/addons/blog/pages/index/index.ts | 3 +- .../h5pactivity/services/h5pactivity-sync.ts | 3 +- src/addons/notes/services/notes-sync.ts | 3 +- src/core/directives/external-content.ts | 3 +- .../features/enrol/services/enrol-helper.ts | 3 +- .../fileuploader/services/handlers/album.ts | 4 +- .../fileuploader/services/handlers/audio.ts | 7 ++-- .../fileuploader/services/handlers/camera.ts | 4 +- .../fileuploader/services/handlers/video.ts | 7 ++-- .../features/h5p/classes/content-validator.ts | 7 ++-- src/core/services/utils/utils.ts | 33 +++++---------- src/core/singletons/array.ts | 42 +++++++++++++++++++ src/core/singletons/tests/array.test.ts | 14 +++++++ 13 files changed, 92 insertions(+), 41 deletions(-) diff --git a/src/addons/blog/pages/index/index.ts b/src/addons/blog/pages/index/index.ts index 46ba2bba6..8f2cb9329 100644 --- a/src/addons/blog/pages/index/index.ts +++ b/src/addons/blog/pages/index/index.ts @@ -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); } diff --git a/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts b/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts index 1189c3d3e..d9132942c 100644 --- a/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts +++ b/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts @@ -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) => { diff --git a/src/addons/notes/services/notes-sync.ts b/src/addons/notes/services/notes-sync.ts index 494ad931c..8d20bcb8d 100644 --- a/src/addons/notes/services/notes-sync.ts +++ b/src/addons/notes/services/notes-sync.ts @@ -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 note.courseid)); }); - CoreUtils.uniqueArray(courseIds); + CoreArray.unique(courseIds); // Sync all courses. const promises = courseIds.map(async (courseId) => { diff --git a/src/core/directives/external-content.ts b/src/core/directives/external-content.ts index 0ede98c26..95c2ecfd1 100644 --- a/src/core/directives/external-content.ts +++ b/src/core/directives/external-content.ts @@ -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; } diff --git a/src/core/features/enrol/services/enrol-helper.ts b/src/core/features/enrol/services/enrol-helper.ts index 54d3dd03b..72ef71821 100644 --- a/src/core/features/enrol/services/enrol-helper.ts +++ b/src/core/features/enrol/services/enrol-helper.ts @@ -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 { - methodTypes = CoreUtils.uniqueArray(methodTypes); + methodTypes = CoreArray.unique(methodTypes); let enrolmentIcons: CoreEnrolInfoIcon[] = []; let addBrowserOption = false; diff --git a/src/core/features/fileuploader/services/handlers/album.ts b/src/core/features/fileuploader/services/handlers/album.ts index e61ddf6de..6f457a7a1 100644 --- a/src/core/features/fileuploader/services/handlers/album.ts +++ b/src/core/features/fileuploader/services/handlers/album.ts @@ -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)\//); } /** diff --git a/src/core/features/fileuploader/services/handlers/audio.ts b/src/core/features/fileuploader/services/handlers/audio.ts index 2a2e93725..a5e5dc573 100644 --- a/src/core/features/fileuploader/services/handlers/audio.ts +++ b/src/core/features/fileuploader/services/handlers/audio.ts @@ -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) { diff --git a/src/core/features/fileuploader/services/handlers/camera.ts b/src/core/features/fileuploader/services/handlers/camera.ts index 80cae2cdd..373b338fa 100644 --- a/src/core/features/fileuploader/services/handlers/camera.ts +++ b/src/core/features/fileuploader/services/handlers/camera.ts @@ -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)$/); } /** diff --git a/src/core/features/fileuploader/services/handlers/video.ts b/src/core/features/fileuploader/services/handlers/video.ts index 7d5b72476..ac655b580 100644 --- a/src/core/features/fileuploader/services/handlers/video.ts +++ b/src/core/features/fileuploader/services/handlers/video.ts @@ -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) { diff --git a/src/core/features/h5p/classes/content-validator.ts b/src/core/features/h5p/classes/content-validator.ts index 3dec4b47b..df57b88d1 100644 --- a/src/core/features/h5p/classes/content-validator.ts +++ b/src/core/features/h5p/classes/content-validator.ts @@ -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); diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index 00c1e2729..9b68444a5 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -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 & { children: TreeNode[] }; @@ -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, to: Record, 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): 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(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, prefix: string): Record { 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(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); } /** diff --git a/src/core/singletons/array.ts b/src/core/singletons/array.ts index 711313ef6..49f81195f 100644 --- a/src/core/singletons/array.ts +++ b/src/core/singletons/array.ts @@ -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(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; + }); + } + } diff --git a/src/core/singletons/tests/array.test.ts b/src/core/singletons/tests/array.test.ts index c09225cf1..db34315a3 100644 --- a/src/core/singletons/tests/array.test.ts +++ b/src/core/singletons/tests/array.test.ts @@ -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([]); + }); + });