/* * Squidex Headless CMS * * @license * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ export interface IdField { id: string; } function freeze(items: T[]): T[] { for (let item of items) { Object.freeze(item); } return items; } export class ImmutableArray implements Iterable { private static readonly EMPTY = new ImmutableArray([]); private readonly items: T[]; public [Symbol.iterator](): Iterator { return this.items.values(); } public get length(): number { return this.items.length; } public get values(): T[] { return [...this.items]; } public get mutableValues(): T[] { return this.items; } private constructor(items: T[]) { this.items = items; } public static empty(): ImmutableArray { return ImmutableArray.EMPTY; } public static of(items?: V[]): ImmutableArray { if (!items || items.length === 0) { return ImmutableArray.EMPTY; } else { return new ImmutableArray(freeze([...items])); } } public at(index: number) { return this.values[index]; } public map(projection: (item: T) => R): ImmutableArray { return new ImmutableArray(freeze(this.items.map(v => projection(v!)))); } public filter(predicate: (item: T) => boolean): ImmutableArray { return new ImmutableArray(this.items.filter(v => predicate(v!))); } public find(predicate: (item: T, index: number) => boolean): T | undefined { return this.items.find(predicate); } public sort(compareFn?: (a: T, b: T) => number): ImmutableArray { const clone = [...this.items]; clone.sort(compareFn); return new ImmutableArray(clone); } public sortByStringAsc(filter: (a: T) => string): ImmutableArray { return this.sort((a, b) => { const av = filter(a); const bv = filter(b); if (av < bv) { return -1; } if (av > bv) { return 1; } return 0; }); } public sortByStringDesc(filter: (a: T) => string): ImmutableArray { return this.sort((a, b) => { const av = filter(a); const bv = filter(b); if (av < bv) { return 1; } if (av > bv) { return -1; } return 0; }); } public sortByNumberAsc(filter: (a: T) => number): ImmutableArray { return this.sort((a, b) => { const av = filter(a); const bv = filter(b); if (av < bv) { return -1; } if (av > bv) { return 1; } return 0; }); } public sortByNumberDesc(filter: (a: T) => number): ImmutableArray { return this.sort((a, b) => { const av = filter(a); const bv = filter(b); if (av < bv) { return 1; } if (av > bv) { return -1; } return 0; }); } public pushFront(...items: T[]): ImmutableArray { if (items.length === 0) { return this; } return new ImmutableArray([...freeze(items), ...this.items]); } public push(...items: T[]): ImmutableArray { if (items.length === 0) { return this; } return new ImmutableArray([...this.items, ...freeze(items)]); } public remove(...items: T[]): ImmutableArray { if (items.length === 0) { return this; } const copy = this.items.slice(); for (let item of items) { const index = copy.indexOf(item); if (index >= 0) { copy.splice(index, 1); } } return new ImmutableArray(copy); } public removeAll(predicate: (item: T, index: number) => boolean): ImmutableArray { const copy = this.items.slice(); let hasChange = false; for (let i = 0; i < copy.length; ) { if (predicate(copy[i], i)) { copy.splice(i, 1); hasChange = true; } else { ++i; } } return hasChange ? new ImmutableArray(copy) : this; } public replace(oldItem: T, newItem: T): ImmutableArray { const index = this.items.indexOf(oldItem); if (index >= 0) { if (newItem) { Object.freeze(newItem); } const copy = [...this.items.slice(0, index), newItem, ...this.items.slice(index + 1)]; return new ImmutableArray(copy); } else { return this; } } public replaceAll(predicate: (item: T, index: number) => boolean, replacer: (item: T) => T): ImmutableArray { const copy = this.items.slice(); let hasChange = false; for (let i = 0; i < copy.length; i++) { if (predicate(copy[i], i)) { const newItem = replacer(copy[i]); if (newItem) { Object.freeze(newItem); } if (copy[i] !== newItem) { copy[i] = newItem; hasChange = true; } } } return hasChange ? new ImmutableArray(copy) : this; } public replaceBy(field: string, newValue: T, replacer?: (o: T, n: T) => T) { return this.replaceAll(x => x[field] === newValue[field], o => replacer ? replacer(o, newValue) : newValue); } }