mirror of
https://github.com/nasa/openmct.git
synced 2025-05-03 17:22:56 +00:00
* docs: fix type imports in openmct.js * docs: fix type imports * docs: fix types for eventHelpers * docs: types for TypeRegistry * docs: types for StatusAPI * docs: fix ObjectAPI types and docs * docs: more types * docs: improved types for main entry * docs: improved types * fix: unbreak the linting * chore: remove EventEmitter webpack alias as it hide types * fix: return type * fix: parameter type * fix: types for composables * chore: add webpack consts to eslintrc * fix: remove usage of deprecated timeAPI methods and add a ton of docs and types * docs: update README.md * lint: clean up API.md * chore: upgrade eventemitter to v5.0.2 * refactor: update imports for EventEmitter to remove alias * format: lint * docs: update types for Views and ViewProviders * docs: expose common types at the base import level * docs(types): remove unnecessary tsconfig options * docs: ActionAPI * docs: AnnotationAPI * docs: import common types from the same origin * docs: FormsAPI & TelemetryAPI types * docs: FormController, IndicatorAPI * docs: MenuAPI, ActionsAPI * docs: `@memberof` is not supported by `tsc` and JSDoc generation so remove it * docs: RootRegistry and RootObjectProvider * docs: Transaction + Overlay * lint: words for the word god * fix: review comments
255 lines
8.0 KiB
JavaScript
255 lines
8.0 KiB
JavaScript
/*****************************************************************************
|
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
|
* as represented by the Administrator of the National Aeronautics and Space
|
|
* Administration. All rights reserved.
|
|
*
|
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
* Open MCT includes source code licensed under additional open source
|
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
* this source code distribution or the Licensing information page available
|
|
* at runtime from the About dialog for additional information.
|
|
*****************************************************************************/
|
|
import _ from 'lodash';
|
|
|
|
import { makeKeyString, parseKeyString } from '../objects/object-utils.js';
|
|
|
|
/**
|
|
* @typedef {import('openmct').DomainObject} DomainObject
|
|
*/
|
|
|
|
/**
|
|
* @typedef {import('openmct').Identifier} Identifier
|
|
*/
|
|
|
|
/**
|
|
* @typedef {import('./CompositionAPI').default} CompositionAPI
|
|
*/
|
|
|
|
/**
|
|
* @typedef {import('openmct').OpenMCT} OpenMCT
|
|
*/
|
|
|
|
/**
|
|
* A CompositionProvider provides the underlying implementation of
|
|
* composition-related behavior for certain types of domain object.
|
|
*
|
|
* By default, a composition provider will not support composition
|
|
* modification. You can add support for mutation of composition by
|
|
* defining `add` and/or `remove` methods.
|
|
*
|
|
* If the composition of an object can change over time-- perhaps via
|
|
* server updates or mutation via the add/remove methods, then one must
|
|
* trigger events as necessary.
|
|
*
|
|
*/
|
|
export default class CompositionProvider {
|
|
#publicAPI;
|
|
#listeningTo;
|
|
|
|
/**
|
|
* @param {OpenMCT} publicAPI
|
|
* @param {CompositionAPI} compositionAPI
|
|
*/
|
|
constructor(publicAPI, compositionAPI) {
|
|
this.#publicAPI = publicAPI;
|
|
this.#listeningTo = {};
|
|
|
|
compositionAPI.addPolicy(this.#cannotContainItself.bind(this));
|
|
compositionAPI.addPolicy(this.#supportsComposition.bind(this));
|
|
}
|
|
|
|
get listeningTo() {
|
|
return this.#listeningTo;
|
|
}
|
|
|
|
get establishTopicListener() {
|
|
return this.#establishTopicListener.bind(this);
|
|
}
|
|
|
|
get publicAPI() {
|
|
return this.#publicAPI;
|
|
}
|
|
|
|
/**
|
|
* Check if this provider should be used to load composition for a
|
|
* particular domain object.
|
|
* @method appliesTo
|
|
* @param {DomainObject} domainObject the domain object
|
|
* to check
|
|
* @returns {boolean} true if this provider can provide composition for a given domain object
|
|
*/
|
|
appliesTo(domainObject) {
|
|
throw new Error('This method must be implemented by a subclass.');
|
|
}
|
|
/**
|
|
* Load any domain objects contained in the composition of this domain
|
|
* object.
|
|
* @param {DomainObject} domainObject the domain object
|
|
* for which to load composition
|
|
* @returns {Promise<Identifier[]>} a promise for
|
|
* the Identifiers in this composition
|
|
*/
|
|
load(domainObject) {
|
|
throw new Error('This method must be implemented by a subclass.');
|
|
}
|
|
/**
|
|
* Attach listeners for changes to the composition of a given domain object.
|
|
* Supports `add` and `remove` events.
|
|
*
|
|
* @param {DomainObject} domainObject to listen to
|
|
* @param {string} event the event to bind to, either `add` or `remove`.
|
|
* @param {Function} callback callback to invoke when event is triggered.
|
|
* @param {any} [context] to use when invoking callback.
|
|
*/
|
|
on(domainObject, event, callback, context) {
|
|
throw new Error('This method must be implemented by a subclass.');
|
|
}
|
|
/**
|
|
* Remove a listener that was previously added for a given domain object.
|
|
* event name, callback, and context must be the same as when the listener
|
|
* was originally attached.
|
|
*
|
|
* @param {DomainObject} domainObject to remove listener for
|
|
* @param {string} event event to stop listening to: `add` or `remove`.
|
|
* @param {Function} callback callback to remove.
|
|
* @param {any} context of callback to remove.
|
|
*/
|
|
off(domainObject, event, callback, context) {
|
|
throw new Error('This method must be implemented by a subclass.');
|
|
}
|
|
/**
|
|
* Remove a domain object from another domain object's composition.
|
|
*
|
|
* This method is optional; if not present, adding to a domain object's
|
|
* composition using this provider will be disallowed.
|
|
*
|
|
* @param {DomainObject} domainObject the domain object
|
|
* which should have its composition modified
|
|
* @param {Identifier} childId the domain object to remove
|
|
*/
|
|
remove(domainObject, childId) {
|
|
throw new Error('This method must be implemented by a subclass.');
|
|
}
|
|
/**
|
|
* Add a domain object to another domain object's composition.
|
|
*
|
|
* This method is optional; if not present, adding to a domain object's
|
|
* composition using this provider will be disallowed.
|
|
*
|
|
* @param {DomainObject} parent the domain object
|
|
* which should have its composition modified
|
|
* @param {Identifier} childId the domain object to add
|
|
*/
|
|
add(parent, childId) {
|
|
throw new Error('This method must be implemented by a subclass.');
|
|
}
|
|
|
|
/**
|
|
* @param {DomainObject} parent
|
|
* @param {Identifier} childId
|
|
* @returns {boolean}
|
|
*/
|
|
includes(parent, childId) {
|
|
throw new Error('This method must be implemented by a subclass.');
|
|
}
|
|
|
|
/**
|
|
* @param {DomainObject} domainObject
|
|
* @param {number} oldIndex
|
|
* @param {number} newIndex
|
|
* @returns
|
|
*/
|
|
reorder(domainObject, oldIndex, newIndex) {
|
|
throw new Error('This method must be implemented by a subclass.');
|
|
}
|
|
|
|
/**
|
|
* Listens on general mutation topic, using injector to fetch to avoid
|
|
* circular dependencies.
|
|
*/
|
|
#establishTopicListener() {
|
|
if (this.topicListener) {
|
|
return;
|
|
}
|
|
|
|
const onMutation = this.#onMutation.bind(this);
|
|
this.#publicAPI.objects.eventEmitter.on('mutation', onMutation);
|
|
this.topicListener = () => {
|
|
this.#publicAPI.objects.eventEmitter.off('mutation', onMutation);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {DomainObject} parent
|
|
* @param {DomainObject} child
|
|
* @returns {boolean}
|
|
*/
|
|
#cannotContainItself(parent, child) {
|
|
return !(
|
|
parent.identifier.namespace === child.identifier.namespace &&
|
|
parent.identifier.key === child.identifier.key
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param {DomainObject} parent
|
|
* @returns {boolean}
|
|
*/
|
|
#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.
|
|
*
|
|
* @param {DomainObject} oldDomainObject
|
|
*/
|
|
#onMutation(newDomainObject, oldDomainObject) {
|
|
const id = makeKeyString(oldDomainObject.identifier);
|
|
const listeners = this.#listeningTo[id];
|
|
|
|
if (!listeners) {
|
|
return;
|
|
}
|
|
|
|
if (oldDomainObject.composition === undefined || newDomainObject.composition === undefined) {
|
|
return;
|
|
}
|
|
|
|
const oldComposition = oldDomainObject.composition.map(makeKeyString);
|
|
const newComposition = newDomainObject.composition.map(makeKeyString);
|
|
|
|
const added = _.difference(newComposition, oldComposition).map(parseKeyString);
|
|
const removed = _.difference(oldComposition, newComposition).map(parseKeyString);
|
|
|
|
function notify(value) {
|
|
return function (listener) {
|
|
if (listener.context) {
|
|
listener.callback.call(listener.context, value);
|
|
} else {
|
|
listener.callback(value);
|
|
}
|
|
};
|
|
}
|
|
|
|
added.forEach(function (addedChild) {
|
|
listeners.add.forEach(notify(addedChild));
|
|
});
|
|
|
|
removed.forEach(function (removedChild) {
|
|
listeners.remove.forEach(notify(removedChild));
|
|
});
|
|
}
|
|
}
|