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 { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url'; import { CoreUrlUtils } from '@services/utils/url';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { CoreArray } from '@singletons/array';
import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreTime } from '@singletons/time'; import { CoreTime } from '@singletons/time';
@ -218,7 +219,7 @@ export class AddonBlogIndexPage implements OnInit, OnDestroy {
if (refresh) { if (refresh) {
this.entries = result.entries; this.entries = result.entries;
} else { } else {
this.entries = CoreUtils.uniqueArray(this.entries this.entries = CoreArray.unique(this.entries
.concat(result.entries), 'id') .concat(result.entries), 'id')
.sort((a, b) => b.created - a.created); .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 { CoreXAPIIRI } from '@features/xapi/classes/iri';
import { CoreXAPIItemAgent } from '@features/xapi/classes/item-agent'; import { CoreXAPIItemAgent } from '@features/xapi/classes/item-agent';
import { CoreWSError } from '@classes/errors/wserror'; import { CoreWSError } from '@classes/errors/wserror';
import { CoreArray } from '@singletons/array';
/** /**
* Service to sync H5P activities. * Service to sync H5P activities.
@ -76,7 +77,7 @@ export class AddonModH5PActivitySyncProvider extends CoreCourseActivitySyncBaseP
]); ]);
const entries = (<(CoreXAPIStatementDBRecord|CoreXAPIStateDBRecord)[]> statements).concat(states); 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. // Sync all activities.
const promises = contextIds.map(async (contextId) => { const promises = contextIds.map(async (contextId) => {

View File

@ -24,6 +24,7 @@ import { CoreEvents } from '@singletons/events';
import { AddonNotesDBRecord, AddonNotesDeletedDBRecord } from './database/notes'; import { AddonNotesDBRecord, AddonNotesDeletedDBRecord } from './database/notes';
import { AddonNotes, AddonNotesCreateNoteData } from './notes'; import { AddonNotes, AddonNotesCreateNoteData } from './notes';
import { AddonNotesOffline } from './notes-offline'; import { AddonNotesOffline } from './notes-offline';
import { CoreArray } from '@singletons/array';
/** /**
* Service to sync notes. * Service to sync notes.
@ -67,7 +68,7 @@ export class AddonNotesSyncProvider extends CoreSyncBaseProvider<AddonNotesSyncR
courseIds = courseIds.concat(notes.map((note) => note.courseid)); courseIds = courseIds.concat(notes.map((note) => note.courseid));
}); });
CoreUtils.uniqueArray(courseIds); CoreArray.unique(courseIds);
// Sync all courses. // Sync all courses.
const promises = courseIds.map(async (courseId) => { 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 { CorePromisedValue } from '@classes/promised-value';
import { CorePlatform } from '@services/platform'; import { CorePlatform } from '@services/platform';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { CoreArray } from '@singletons/array';
/** /**
* Directive to handle external content. * Directive to handle external content.
@ -279,7 +280,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
return; return;
} }
const urls = CoreUtils.uniqueArray(Array.from(inlineStyles.match(/https?:\/\/[^"') ;]*/g) ?? [])); const urls = CoreArray.unique(Array.from(inlineStyles.match(/https?:\/\/[^"') ;]*/g) ?? []));
if (!urls.length) { if (!urls.length) {
return; return;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,4 +66,46 @@ export class CoreArray {
return newArray; 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']); 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([]);
});
}); });