/** * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ import type { IInterceptable, IInterceptor, IListenable, ISetWillChange, ObservableMap } from "mobx"; import { observable, ObservableSet, runInAction } from "mobx"; export function makeIterableIterator(iterator: Iterator): IterableIterator { (iterator as IterableIterator)[Symbol.iterator] = () => iterator as IterableIterator; return iterator as IterableIterator; } export class HashSet implements Set { #hashmap: Map; constructor(initialValues: Iterable, protected hasher: (item: T) => string) { this.#hashmap = new Map(Array.from(initialValues, value => [this.hasher(value), value])); } replace(other: ObservableHashSet | ObservableSet | Set | readonly T[]): this { if (other === null || other === undefined) { return this; } if (!(Array.isArray(other) || other instanceof Set || other instanceof ObservableHashSet || other instanceof ObservableSet)) { throw new Error(`ObservableHashSet: Cannot initialize set from ${other}`); } this.clear(); for (const value of other) { this.add(value); } return this; } clear(): void { this.#hashmap.clear(); } add(value: T): this { this.#hashmap.set(this.hasher(value), value); return this; } toggle(value: T): void { const hash = this.hasher(value); if (this.#hashmap.has(hash)) { this.#hashmap.delete(hash); } else { this.#hashmap.set(hash, value); } } delete(value: T): boolean { return this.#hashmap.delete(this.hasher(value)); } forEach(callbackfn: (value: T, key: T, set: Set) => void, thisArg?: any): void { this.#hashmap.forEach(value => callbackfn(value, value, thisArg ?? this)); } has(value: T): boolean { return this.#hashmap.has(this.hasher(value)); } get size(): number { return this.#hashmap.size; } entries(): IterableIterator<[T, T]> { let nextIndex = 0; const keys = Array.from(this.keys()); const values = Array.from(this.values()); return makeIterableIterator<[T, T]>({ next() { const index = nextIndex++; return index < values.length ? { value: [keys[index], values[index]], done: false } : { done: true, value: undefined }; }, }); } keys(): IterableIterator { return this.values(); } values(): IterableIterator { let nextIndex = 0; const observableValues = Array.from(this.#hashmap.values()); return makeIterableIterator({ next: () => { return nextIndex < observableValues.length ? { value: observableValues[nextIndex++], done: false } : { done: true, value: undefined }; }, }); } [Symbol.iterator](): IterableIterator { return this.#hashmap.values(); } get [Symbol.toStringTag](): string { return "Set"; } toJSON(): T[] { return Array.from(this); } toString(): string { return "[object Set]"; } } export class ObservableHashSet implements Set, IInterceptable, IListenable { #hashmap: ObservableMap; get interceptors_(): IInterceptor>[] { return []; } get changeListeners_(): Function[] { return []; } constructor(initialValues: Iterable, protected hasher: (item: T) => string) { this.#hashmap = observable.map(Array.from(initialValues, value => [this.hasher(value), value]), undefined); } replace(other: ObservableHashSet | ObservableSet | Set | readonly T[]): this { return runInAction(() => { if (other === null || other === undefined) { return this; } if (!(Array.isArray(other) || other instanceof Set || other instanceof ObservableHashSet || other instanceof ObservableSet)) { throw new Error(`ObservableHashSet: Cannot initialize set from ${other}`); } this.clear(); for (const value of other) { this.add(value); } return this; }); } clear(): void { this.#hashmap.clear(); } add(value: T): this { this.#hashmap.set(this.hasher(value), value); return this; } toggle(value: T): void { runInAction(() => { const hash = this.hasher(value); if (this.#hashmap.has(hash)) { this.#hashmap.delete(hash); } else { this.#hashmap.set(hash, value); } }); } delete(value: T): boolean { return this.#hashmap.delete(this.hasher(value)); } forEach(callbackfn: (value: T, key: T, set: Set) => void, thisArg?: any): void { this.#hashmap.forEach(value => callbackfn(value, value, thisArg ?? this)); } has(value: T): boolean { return this.#hashmap.has(this.hasher(value)); } get size(): number { return this.#hashmap.size; } entries(): IterableIterator<[T, T]> { let nextIndex = 0; const keys = Array.from(this.keys()); const values = Array.from(this.values()); return makeIterableIterator<[T, T]>({ next() { const index = nextIndex++; return index < values.length ? { value: [keys[index], values[index]], done: false } : { done: true, value: undefined }; }, }); } keys(): IterableIterator { return this.values(); } values(): IterableIterator { let nextIndex = 0; const observableValues = Array.from(this.#hashmap.values()); return makeIterableIterator({ next: () => { return nextIndex < observableValues.length ? { value: observableValues[nextIndex++], done: false } : { done: true, value: undefined }; }, }); } [Symbol.iterator](): IterableIterator { return this.#hashmap.values(); } get [Symbol.toStringTag](): string { return "Set"; } toJSON(): T[] { return Array.from(this); } toString(): string { return "[object ObservableSet]"; } }