mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-02-04 08:50:33 +08:00
106 lines
3.9 KiB
JavaScript
106 lines
3.9 KiB
JavaScript
export default class Observable {
|
|
|
|
/** @type {Map<String, Object[]>} */
|
|
#observers = new Map()
|
|
|
|
/**
|
|
* @param {String} property
|
|
* @param {(value: any) => {}} observer
|
|
*/
|
|
subscribe(property, observer) {
|
|
let observers = this.#observers
|
|
if (observers.has(property)) {
|
|
let propertyObservers = observers.get(property)
|
|
if (propertyObservers.includes(observer)) {
|
|
return false
|
|
} else {
|
|
propertyObservers.push(observer)
|
|
}
|
|
} else {
|
|
let fromPrototype = false
|
|
let propertyDescriptor = Object.getOwnPropertyDescriptor(this, property)
|
|
if (!propertyDescriptor) {
|
|
fromPrototype = true
|
|
propertyDescriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), property) ?? {}
|
|
if (!propertyDescriptor) {
|
|
return false
|
|
}
|
|
}
|
|
observers.set(property, [observer])
|
|
const isValue = "value" in propertyDescriptor
|
|
const hasSetter = "set" in propertyDescriptor
|
|
if (!(isValue || hasSetter)) {
|
|
throw new Error(`Property ${property} is not a value or a setter`)
|
|
}
|
|
// A Symbol so it does not show up in Object.getOwnPropertyNames()
|
|
const storageKey = Symbol.for(property + "Storage")
|
|
const valInfoKey = Symbol.for(property + "ValInfo")
|
|
Object.defineProperties(
|
|
fromPrototype ? Object.getPrototypeOf(this) : this,
|
|
{
|
|
[storageKey]: {
|
|
configurable: true,
|
|
enumerable: false, // Non enumerable so it does not show up in for...in or Object.keys()
|
|
...(isValue
|
|
? {
|
|
value: this[property],
|
|
writable: true,
|
|
}
|
|
: {
|
|
get: propertyDescriptor.get,
|
|
set: propertyDescriptor.set,
|
|
}
|
|
)
|
|
},
|
|
[valInfoKey]: {
|
|
configurable: true,
|
|
enumerable: false,
|
|
value: [fromPrototype, isValue]
|
|
},
|
|
[property]: {
|
|
configurable: true,
|
|
...(isValue && {
|
|
get() {
|
|
return this[storageKey]
|
|
}
|
|
}),
|
|
set(v) {
|
|
this[storageKey] = v
|
|
observers.get(property).forEach(observer => {
|
|
observer(this[property])
|
|
})
|
|
},
|
|
}
|
|
}
|
|
)
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* @param {String} property
|
|
* @param {Object} observer
|
|
*/
|
|
unsubscribe(property, observer) {
|
|
let observers = this.#observers.get(property)
|
|
if (!observers?.includes(observer)) {
|
|
return false
|
|
}
|
|
observers.splice(observers.indexOf(observer), 1)
|
|
if (observers.length == 0) {
|
|
const storageKey = Symbol.for(property + "Storage")
|
|
const valInfoKey = Symbol.for(property + "ValInfo")
|
|
const fromPrototype = this[valInfoKey][0]
|
|
const isValue = this[valInfoKey][1]
|
|
Object.defineProperty(
|
|
fromPrototype ? Object.getPrototypeOf(this) : this,
|
|
property,
|
|
Object.getOwnPropertyDescriptor(fromPrototype ? Object.getPrototypeOf(this) : this, storageKey),
|
|
)
|
|
delete this[valInfoKey]
|
|
delete this[storageKey]
|
|
}
|
|
return true
|
|
}
|
|
}
|