diff --git a/npm/ng-packs/packages/components/extensible/src/lib/models/actions.ts b/npm/ng-packs/packages/components/extensible/src/lib/models/actions.ts index b8a9d8fa3f..003979e452 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/models/actions.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/models/actions.ts @@ -1,5 +1,5 @@ -import { LinkedList } from '@abp/utils/dist'; import { InjectionToken, InjectOptions, Type } from '@angular/core'; +import { LinkedList } from '../utils/linked-list'; export abstract class ActionList> extends LinkedList {} diff --git a/npm/ng-packs/packages/components/extensible/src/lib/models/props.ts b/npm/ng-packs/packages/components/extensible/src/lib/models/props.ts index 7cc9571440..808d18db22 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/models/props.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/models/props.ts @@ -1,5 +1,5 @@ -import { LinkedList } from '@abp/utils/dist'; import { InjectionToken, InjectOptions, Type } from '@angular/core'; +import { LinkedList } from '../utils/linked-list'; import { ePropType } from '../enums/props.enum'; import { FormPropTooltip } from './form-props'; diff --git a/npm/ng-packs/packages/components/extensible/src/lib/utils/linked-list.ts b/npm/ng-packs/packages/components/extensible/src/lib/utils/linked-list.ts new file mode 100644 index 0000000000..9640b40674 --- /dev/null +++ b/npm/ng-packs/packages/components/extensible/src/lib/utils/linked-list.ts @@ -0,0 +1,410 @@ +/** @description This file is referenced from here npm/packs/utils/projects/utils/src/lib/linked-list.ts */ + +/* eslint-disable prefer-spread */ +/* tslint:disable:no-non-null-assertion */ + +import compare from 'just-compare'; + +export class ListNode { + next: ListNode | undefined; + previous: ListNode | undefined; + constructor(public readonly value: T) {} +} + +export class LinkedList { + private first: ListNode | undefined; + private last: ListNode | undefined; + private size = 0; + + get head(): ListNode | undefined { + return this.first; + } + get tail(): ListNode | undefined { + return this.last; + } + get length(): number { + return this.size; + } + + private attach( + value: T, + previousNode: ListNode | undefined, + nextNode: ListNode | undefined, + ): ListNode { + if (!previousNode) return this.addHead(value); + + if (!nextNode) return this.addTail(value); + + const node = new ListNode(value); + node.previous = previousNode; + previousNode.next = node; + node.next = nextNode; + nextNode.previous = node; + + this.size++; + + return node; + } + + private attachMany( + values: T[], + previousNode: ListNode | undefined, + nextNode: ListNode | undefined, + ): ListNode[] { + if (!values.length) return []; + + if (!previousNode) return this.addManyHead(values); + + if (!nextNode) return this.addManyTail(values); + + const list = new LinkedList(); + list.addManyTail(values); + list.first!.previous = previousNode; + previousNode.next = list.first; + list.last!.next = nextNode; + nextNode.previous = list.last; + + this.size += values.length; + + return list.toNodeArray(); + } + + private detach(node: ListNode) { + if (!node.previous) return this.dropHead(); + + if (!node.next) return this.dropTail(); + + node.previous.next = node.next; + node.next.previous = node.previous; + + this.size--; + + return node; + } + + add(value: T) { + return { + after: (...params: [T] | [any, ListComparisonFn]) => + this.addAfter.call(this, value, ...params), + before: (...params: [T] | [any, ListComparisonFn]) => + this.addBefore.call(this, value, ...params), + byIndex: (position: number) => this.addByIndex(value, position), + head: () => this.addHead(value), + tail: () => this.addTail(value), + }; + } + + addMany(values: T[]) { + return { + after: (...params: [T] | [any, ListComparisonFn]) => + this.addManyAfter.call(this, values, ...params), + before: (...params: [T] | [any, ListComparisonFn]) => + this.addManyBefore.call(this, values, ...params), + byIndex: (position: number) => this.addManyByIndex(values, position), + head: () => this.addManyHead(values), + tail: () => this.addManyTail(values), + }; + } + + addAfter(value: T, previousValue: T): ListNode; + addAfter(value: T, previousValue: any, compareFn: ListComparisonFn): ListNode; + addAfter(value: T, previousValue: any, compareFn: ListComparisonFn = compare): ListNode { + const previous = this.find(node => compareFn(node.value, previousValue)); + + return previous ? this.attach(value, previous, previous.next) : this.addTail(value); + } + + addBefore(value: T, nextValue: T): ListNode; + addBefore(value: T, nextValue: any, compareFn: ListComparisonFn): ListNode; + addBefore(value: T, nextValue: any, compareFn: ListComparisonFn = compare): ListNode { + const next = this.find(node => compareFn(node.value, nextValue)); + + return next ? this.attach(value, next.previous, next) : this.addHead(value); + } + + addByIndex(value: T, position: number): ListNode { + if (position < 0) position += this.size; + else if (position >= this.size) return this.addTail(value); + + if (position <= 0) return this.addHead(value); + + const next = this.get(position)!; + + return this.attach(value, next.previous, next); + } + + addHead(value: T): ListNode { + const node = new ListNode(value); + + node.next = this.first; + + if (this.first) this.first.previous = node; + else this.last = node; + + this.first = node; + this.size++; + + return node; + } + + addTail(value: T): ListNode { + const node = new ListNode(value); + + if (this.first) { + node.previous = this.last; + this.last!.next = node; + this.last = node; + } else { + this.first = node; + this.last = node; + } + + this.size++; + + return node; + } + + addManyAfter(values: T[], previousValue: T): ListNode[]; + addManyAfter(values: T[], previousValue: any, compareFn: ListComparisonFn): ListNode[]; + addManyAfter( + values: T[], + previousValue: any, + compareFn: ListComparisonFn = compare, + ): ListNode[] { + const previous = this.find(node => compareFn(node.value, previousValue)); + + return previous ? this.attachMany(values, previous, previous.next) : this.addManyTail(values); + } + + addManyBefore(values: T[], nextValue: T): ListNode[]; + addManyBefore(values: T[], nextValue: any, compareFn: ListComparisonFn): ListNode[]; + addManyBefore( + values: T[], + nextValue: any, + compareFn: ListComparisonFn = compare, + ): ListNode[] { + const next = this.find(node => compareFn(node.value, nextValue)); + + return next ? this.attachMany(values, next.previous, next) : this.addManyHead(values); + } + + addManyByIndex(values: T[], position: number): ListNode[] { + if (position < 0) position += this.size; + + if (position <= 0) return this.addManyHead(values); + + if (position >= this.size) return this.addManyTail(values); + + const next = this.get(position)!; + + return this.attachMany(values, next.previous, next); + } + + addManyHead(values: T[]): ListNode[] { + return values.reduceRight[]>((nodes, value) => { + nodes.unshift(this.addHead(value)); + return nodes; + }, []); + } + + addManyTail(values: T[]): ListNode[] { + return values.map(value => this.addTail(value)); + } + + drop() { + return { + byIndex: (position: number) => this.dropByIndex(position), + byValue: (...params: [T] | [any, ListComparisonFn]) => + this.dropByValue.apply(this, params), + byValueAll: (...params: [T] | [any, ListComparisonFn]) => + this.dropByValueAll.apply(this, params), + head: () => this.dropHead(), + tail: () => this.dropTail(), + }; + } + + dropMany(count: number) { + return { + byIndex: (position: number) => this.dropManyByIndex(count, position), + head: () => this.dropManyHead(count), + tail: () => this.dropManyTail(count), + }; + } + + dropByIndex(position: number): ListNode | undefined { + if (position < 0) position += this.size; + + const current = this.get(position); + + return current ? this.detach(current) : undefined; + } + + dropByValue(value: T): ListNode | undefined; + dropByValue(value: any, compareFn: ListComparisonFn): ListNode | undefined; + dropByValue(value: any, compareFn: ListComparisonFn = compare): ListNode | undefined { + const position = this.findIndex(node => compareFn(node.value, value)); + + return position < 0 ? undefined : this.dropByIndex(position); + } + + dropByValueAll(value: T): ListNode[]; + dropByValueAll(value: any, compareFn: ListComparisonFn): ListNode[]; + dropByValueAll(value: any, compareFn: ListComparisonFn = compare): ListNode[] { + const dropped: ListNode[] = []; + + for (let current = this.first, position = 0; current; position++, current = current.next) { + if (compareFn(current.value, value)) { + dropped.push(this.dropByIndex(position - dropped.length)!); + } + } + + return dropped; + } + + dropHead(): ListNode | undefined { + const head = this.first; + + if (head) { + this.first = head.next; + + if (this.first) this.first.previous = undefined; + else this.last = undefined; + + this.size--; + + return head; + } + + return undefined; + } + + dropTail(): ListNode | undefined { + const tail = this.last; + + if (tail) { + this.last = tail.previous; + + if (this.last) this.last.next = undefined; + else this.first = undefined; + + this.size--; + + return tail; + } + + return undefined; + } + + dropManyByIndex(count: number, position: number): ListNode[] { + if (count <= 0) return []; + + if (position < 0) position = Math.max(position + this.size, 0); + else if (position >= this.size) return []; + + count = Math.min(count, this.size - position); + + const dropped: ListNode[] = []; + + while (count--) { + const current = this.get(position); + dropped.push(this.detach(current!)!); + } + + return dropped; + } + + dropManyHead(count: Exclude): ListNode[] { + if (count <= 0) return []; + + count = Math.min(count, this.size); + + const dropped: ListNode[] = []; + + while (count--) dropped.unshift(this.dropHead()!); + + return dropped; + } + + dropManyTail(count: Exclude): ListNode[] { + if (count <= 0) return []; + + count = Math.min(count, this.size); + + const dropped: ListNode[] = []; + + while (count--) dropped.push(this.dropTail()!); + + return dropped; + } + + find(predicate: ListIteratorFn): ListNode | undefined { + for (let current = this.first, position = 0; current; position++, current = current.next) { + if (predicate(current, position, this)) return current; + } + + return undefined; + } + + findIndex(predicate: ListIteratorFn): number { + for (let current = this.first, position = 0; current; position++, current = current.next) { + if (predicate(current, position, this)) return position; + } + + return -1; + } + + forEach(iteratorFn: ListIteratorFn) { + for (let node = this.first, position = 0; node; position++, node = node.next) { + iteratorFn(node, position, this); + } + } + + get(position: number): ListNode | undefined { + return this.find((_, index) => position === index); + } + + indexOf(value: T): number; + indexOf(value: any, compareFn: ListComparisonFn): number; + indexOf(value: any, compareFn: ListComparisonFn = compare): number { + return this.findIndex(node => compareFn(node.value, value)); + } + + toArray(): T[] { + const array = new Array(this.size); + + this.forEach((node, index) => (array[index!] = node.value)); + + return array; + } + + toNodeArray(): ListNode[] { + const array = new Array(this.size); + + this.forEach((node, index) => (array[index!] = node)); + + return array; + } + + toString(mapperFn: ListMapperFn = JSON.stringify): string { + return this.toArray() + .map(value => mapperFn(value)) + .join(' <-> '); + } + + // Cannot use Generator type because of ng-packagr + *[Symbol.iterator](): any { + for (let node = this.first, position = 0; node; position++, node = node.next) { + yield node.value; + } + } +} + +export type ListMapperFn = (value: T) => any; + +export type ListComparisonFn = (value1: T, value2: any) => boolean; + +export type ListIteratorFn = ( + node: ListNode, + index?: number, + list?: LinkedList, +) => R;