Compare commits

...

1 Commits

Author SHA1 Message Date
d16c60aa7a Initial pass at refactoring composition. Completely broken 2023-01-25 18:23:05 -08:00
3 changed files with 59 additions and 92 deletions

View File

@ -20,6 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
/**
* @typedef {import('../objects/ObjectAPI').DomainObject} DomainObject
*/
@ -60,6 +61,10 @@ export default class CompositionCollection {
#publicAPI;
#listeners;
#mutables;
#onGlobalAdd;
#onGlobalRemove;
static #globalEvents = new EventEmitter();
/**
* @constructor
* @param {DomainObject} domainObject the domain object
@ -95,6 +100,21 @@ export default class CompositionCollection {
unobserve();
});
}
const keyString = publicAPI.objects.makeKeyString(domainObject.identifier);
this.#onGlobalAdd = this._onGlobalAdd.bind(this);
this.#onGlobalRemove = this._onGlobalRemove.bind(this);
CompositionCollection.#globalEvents.on(`add:${keyString}`, this.#onGlobalAdd);
CompositionCollection.#globalEvents.on(`remove:${keyString}`, this.#onGlobalRemove);
}
_onGlobalAdd(object) {
this.#emit('add', object);
}
_onGlobalRemove(identifier) {
this.#emit('remove', identifier);
}
/**
* Listen for changes to this composition. Supports 'add', 'remove', and
@ -209,23 +229,21 @@ export default class CompositionCollection {
* **Intended for internal use ONLY.**
* true if the underlying provider should not be updated.
*/
add(child, skipMutate) {
if (!skipMutate) {
if (!this.#publicAPI.composition.checkPolicy(this.domainObject, child)) {
throw `Object of type ${child.type} cannot be added to object of type ${this.domainObject.type}`;
}
this.#provider.add(this.domainObject, child.identifier);
} else {
if (this.returnMutables && this.#publicAPI.objects.supportsMutation(child.identifier)) {
let keyString = this.#publicAPI.objects.makeKeyString(child.identifier);
child = this.#publicAPI.objects.toMutable(child);
this.#mutables[keyString] = child;
}
this.#emit('add', child);
add(child) {
if (!this.#publicAPI.composition.checkPolicy(this.domainObject, child)) {
throw `Object of type ${child.type} cannot be added to object of type ${this.domainObject.type}`;
}
this.#provider.add(this.domainObject, child.identifier);
if (this.returnMutables && this.#publicAPI.objects.supportsMutation(child.identifier)) {
let keyString = this.#publicAPI.objects.makeKeyString(child.identifier);
child = this.#publicAPI.objects.toMutable(child);
this.#mutables[keyString] = child;
}
// const keyString = this.#publicAPI.objects.makeKeyString(this.domainObject.identifier);
// CompositionCollection.#globalEvents.emit(`add:${keyString}`, child);
}
/**
* Load the domain objects in this composition.
@ -240,7 +258,12 @@ export default class CompositionCollection {
this.#cleanUpMutables();
const children = await this.#provider.load(this.domainObject);
const childObjects = await Promise.all(children.map((c) => this.#publicAPI.objects.get(c, abortSignal)));
childObjects.forEach(c => this.add(c, true));
childObjects.forEach(c => {
this.add(c);
const keyString = this.#publicAPI.objects.makeKeyString(this.domainObject.identifier);
CompositionCollection.#globalEvents.emit(`add:${keyString}`, c);
});
this.#emit('load');
return childObjects;
@ -259,20 +282,18 @@ export default class CompositionCollection {
* true if the underlying provider should not be updated.
* @name remove
*/
remove(child, skipMutate) {
if (!skipMutate) {
this.#provider.remove(this.domainObject, child.identifier);
} else {
if (this.returnMutables) {
let keyString = this.#publicAPI.objects.makeKeyString(child);
if (this.#mutables[keyString] !== undefined && this.#mutables[keyString].isMutable) {
this.#publicAPI.objects.destroyMutable(this.#mutables[keyString]);
delete this.#mutables[keyString];
}
remove(child) {
this.#provider.remove(this.domainObject, child.identifier);
if (this.returnMutables) {
let keyString = this.#publicAPI.objects.makeKeyString(child);
if (this.#mutables[keyString] !== undefined && this.#mutables[keyString].isMutable) {
this.#publicAPI.objects.destroyMutable(this.#mutables[keyString]);
delete this.#mutables[keyString];
}
this.#emit('remove', child);
}
// const keyString = this.#publicAPI.objects.makeKeyString(this.domainObject.identifier);
// CompositionCollection.#globalEvents.emit(`remove:${keyString}`, child.identifier);
}
/**
* Reorder the domain objects in this composition.
@ -295,6 +316,10 @@ export default class CompositionCollection {
this.mutationListener();
delete this.mutationListener;
}
const keyString = this.#publicAPI.objects.makeKeyString(this.domainObject.identifier);
CompositionCollection.#globalEvents.off(`add:${keyString}`, this.#onGlobalAdd);
CompositionCollection.#globalEvents.off(`remove:${keyString}`, this.#onGlobalRemove);
}
/**
* Handle reorder from provider.

View File

@ -71,10 +71,6 @@ export default class CompositionProvider {
return this.#listeningTo;
}
get establishTopicListener() {
return this.#establishTopicListener.bind(this);
}
get publicAPI() {
return this.#publicAPI;
}
@ -181,22 +177,6 @@ export default class CompositionProvider {
throw new Error("This method must be implemented by a subclass.");
}
/**
* Listens on general mutation topic, using injector to fetch to avoid
* circular dependencies.
* @private
*/
#establishTopicListener() {
if (this.topicListener) {
return;
}
this.#publicAPI.objects.eventEmitter.on('mutation', this.#onMutation.bind(this));
this.topicListener = () => {
this.#publicAPI.objects.eventEmitter.off('mutation', this.#onMutation.bind(this));
};
}
/**
* @private
* @param {DomainObject} parent
@ -216,47 +196,5 @@ export default class CompositionProvider {
#supportsComposition(parent, _child) {
return this.#publicAPI.composition.supportsComposition(parent);
}
/**
* Handles mutation events. If there are active listeners for the mutated
* object, detects changes to composition and triggers necessary events.
*
* @private
* @param {DomainObject} oldDomainObject
*/
#onMutation(oldDomainObject) {
const id = objectUtils.makeKeyString(oldDomainObject.identifier);
const listeners = this.#listeningTo[id];
if (!listeners) {
return;
}
const oldComposition = listeners.composition.map(objectUtils.makeKeyString);
const newComposition = oldDomainObject.composition.map(objectUtils.makeKeyString);
const added = _.difference(newComposition, oldComposition).map(objectUtils.parseKeyString);
const removed = _.difference(oldComposition, newComposition).map(objectUtils.parseKeyString);
function notify(value) {
return function (listener) {
if (listener.context) {
listener.callback.call(listener.context, value);
} else {
listener.callback(value);
}
};
}
listeners.composition = newComposition.map(objectUtils.parseKeyString);
added.forEach(function (addedChild) {
listeners.add.forEach(notify(addedChild));
});
removed.forEach(function (removedChild) {
listeners.remove.forEach(notify(removedChild));
});
}
}

View File

@ -89,7 +89,7 @@ export default class DefaultCompositionProvider extends CompositionProvider {
event,
callback,
context) {
this.establishTopicListener();
//this.establishTopicListener();
/** @type {string} */
const keyString = objectUtils.makeKeyString(domainObject.identifier);
@ -157,6 +157,8 @@ export default class DefaultCompositionProvider extends CompositionProvider {
});
this.publicAPI.objects.mutate(domainObject, 'composition', composition);
this.objectListeners.remove?.forEach(listener => listener.callback.apply(listener.context, childId));
}
/**
* Add a domain object to another domain object's composition.
@ -174,6 +176,8 @@ export default class DefaultCompositionProvider extends CompositionProvider {
if (!this.includes(parent, childId)) {
parent.composition.push(childId);
this.publicAPI.objects.mutate(parent, 'composition', parent.composition);
this.objectListeners.add?.forEach(listener => listener.callback.apply(listener.context, childId));
}
}