mobx-keystone-mindreframer
Version:
A MobX powered state management solution based on data trees with first class support for Typescript, snapshots, patches and much more
128 lines (108 loc) • 3.04 kB
text/typescript
import {
action,
intercept,
IObservableArray,
ISetWillChange,
isObservableArray,
observable,
ObservableSet,
observe,
} from "mobx"
import { assertIsObservableArray, assertIsSet, failure, inDevMode } from "../utils"
import { Lock } from "../utils/lock"
import { tag } from "../utils/tag"
const observableSetBackedByObservableArray = action(
<T>(array: IObservableArray<T>): ObservableSet<T> & { dataObject: typeof array } => {
if (inDevMode()) {
if (!isObservableArray(array)) {
throw failure("assertion failed: expected an observable array")
}
}
const set = observable.set(array)
;(set as any).dataObject = array
if (set.size !== array.length) {
throw failure("arrays backing a set cannot contain duplicate values")
}
const mutationLock = new Lock()
// for speed reasons we will just assume distinct values are only once in the array
// when the array changes the set changes
observe(
array,
action(
mutationLock.unlockedFn((change: any /*IArrayDidChange<T>*/) => {
switch (change.type) {
case "splice": {
{
const removed = change.removed
for (let i = 0; i < removed.length; i++) {
set.delete(removed[i])
}
}
{
const added = change.added
for (let i = 0; i < added.length; i++) {
set.add(added[i])
}
}
break
}
case "update": {
set.delete(change.oldValue)
set.add(change.newValue)
break
}
}
})
)
)
// when the set changes also change the array
intercept(
set,
action((change: ISetWillChange<T>) => {
if (!mutationLock.isLocked) {
return null // already changed
}
switch (change.type) {
case "add": {
array.push(change.newValue)
break
}
case "delete": {
const i = array.indexOf(change.oldValue)
if (i >= 0) {
array.splice(i, 1)
}
break
}
}
return change
})
)
return set as any
}
)
const asSetTag = tag((array: Array<any>) => {
assertIsObservableArray(array, "array")
return observableSetBackedByObservableArray(array)
})
/**
* Wraps an observable array to offer a set like interface.
*
* @param array
*/
export function asSet<T>(array: Array<T>): ObservableSet<T> & { dataObject: typeof array } {
return asSetTag.for(array) as any
}
/**
* Converts a set to an array. If the set is a collection wrapper it will return the backed array.
*
* @param set
*/
export function setToArray<T>(set: Set<T>): Array<T> {
assertIsSet(set, "set")
const dataObject = (set as any).dataObject
if (dataObject) {
return dataObject
}
return Array.from(set.values())
}