* Change the mount utility to use Vue's createApp and defineComponent methods * Fix display layout memory leaks caused by `getSelectionContext` * fix some display layout leaks due to use of slots * Fix imagery memory leak (removed span tag). NOTE: CompassRose svg leaks memory - must test on firefox to see if this is a Chrome leak. * Fix ActionsAPI action collection and applicable actions leak. * Fix flexible layout memory leaks - remove listeners on unmount. NOTE: One type of overlay plot (Rover Yaw) is still leaking. * pass in the el on mount * e2e test config and spec changes * Remove mounting of limit lines. Use components directly * test: remove `.only()` * Fix display layout memory leaks * Enable passing tests * e2e README and appActions should be what master has. * lint: add word to cspell list * lint: fixes * lint:fix * fix: revert `el` change * fix: remove empty span * fix: creating shapes in displayLayout * fix: avoid `splice` as it loses reactivity * test: reduce timeout time * quick fixes * add prod mode and convert the test config to select the correct mode * Fix webpack prod config * Add launch flag for exposing window.gc * never worked * explicit naming * rename * We don't need to destroy view providers * test: increase timeout time * test: unskip all mem tests * fix(vue-loader): disable static hoisting * chore: run `test:perf:memory` * Don't destroy view providers * Move context menu once listener to beforeUnmount instead. * Disconnect all resize observers on unmount * Delete Test vue component * Use beforeUnmount and remove splice(0) in favor of [] for emptying arrays * re-structure * fix: unregister listener in pane.vue * test: tweak timeouts * chore: lint:fix * test: unskip perf tests * fix: unregister events properly * fix: unregister listener * fix: unregister listener * fix: unregister listener * fix: use `unmounted()` * fix: unregister listeners * fix: unregister listener properly * chore: lint:fix * test: fix imagery layer toggle test * test: increase timeout * Don't use anonymous functions for listeners * Destroy objects and event listeners properly * Delete config stores that are created by components * Use the right unmount hook. Destroy mounted view on unmount. * Use unmounted, not beforeUnmounted * Lint fixes * Fix time strip memory leak * Undo unneeded change for memory leaks. * chore: combine common webpack configs --------- Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov> Co-authored-by: John Hill <john.c.hill@nasa.gov>
229 lines
7.2 KiB
JavaScript
229 lines
7.2 KiB
JavaScript
/*****************************************************************************
|
|
* Open MCT, Copyright (c) 2014-2023, 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 LegendModel from './LegendModel';
|
|
import Model from './Model';
|
|
import SeriesCollection from './SeriesCollection';
|
|
import XAxisModel from './XAxisModel';
|
|
import YAxisModel from './YAxisModel';
|
|
|
|
const MAX_Y_AXES = 3;
|
|
const MAIN_Y_AXES_ID = 1;
|
|
const MAX_ADDITIONAL_AXES = MAX_Y_AXES - 1;
|
|
|
|
/**
|
|
* PlotConfiguration model stores the configuration of a plot and some
|
|
* limited state. The individual parts of the plot configuration model
|
|
* handle setting defaults and updating in response to various changes.
|
|
*
|
|
* @extends {Model<PlotConfigModelType, PlotConfigModelOptions>}
|
|
*/
|
|
export default class PlotConfigurationModel extends Model {
|
|
/**
|
|
* Initializes all sub models and then passes references to submodels
|
|
* to those that need it.
|
|
*
|
|
* @override
|
|
* @param {import('./Model').ModelOptions<PlotConfigModelType, PlotConfigModelOptions>} options
|
|
*/
|
|
initialize(options) {
|
|
this.openmct = options.openmct;
|
|
|
|
// This is a type assertion for TypeScript, this error is never thrown in practice.
|
|
if (!options.model) {
|
|
throw new Error('Not a collection model.');
|
|
}
|
|
|
|
this.xAxis = new XAxisModel({
|
|
model: options.model.xAxis,
|
|
plot: this,
|
|
openmct: options.openmct
|
|
});
|
|
this.yAxis = new YAxisModel({
|
|
model: options.model.yAxis,
|
|
plot: this,
|
|
openmct: options.openmct,
|
|
id: options.model.yAxis.id || MAIN_Y_AXES_ID
|
|
});
|
|
//Add any axes in addition to the main yAxis above - we must always have at least 1 y-axis
|
|
//Addition axes ids will be the MAIN_Y_AXES_ID + x where x is between 1 and MAX_ADDITIONAL_AXES
|
|
this.additionalYAxes = [];
|
|
const hasAdditionalAxesConfiguration = Array.isArray(options.model.additionalYAxes);
|
|
|
|
for (let yAxisCount = 0; yAxisCount < MAX_ADDITIONAL_AXES; yAxisCount++) {
|
|
const yAxisId = MAIN_Y_AXES_ID + yAxisCount + 1;
|
|
const yAxis =
|
|
hasAdditionalAxesConfiguration &&
|
|
options.model.additionalYAxes.find((additionalYAxis) => additionalYAxis?.id === yAxisId);
|
|
if (yAxis) {
|
|
this.additionalYAxes.push(
|
|
new YAxisModel({
|
|
model: yAxis,
|
|
plot: this,
|
|
openmct: options.openmct,
|
|
id: yAxis.id
|
|
})
|
|
);
|
|
} else {
|
|
this.additionalYAxes.push(
|
|
new YAxisModel({
|
|
plot: this,
|
|
openmct: options.openmct,
|
|
id: yAxisId
|
|
})
|
|
);
|
|
}
|
|
}
|
|
// end add additional axes
|
|
|
|
this.legend = new LegendModel({
|
|
model: options.model.legend,
|
|
plot: this,
|
|
openmct: options.openmct
|
|
});
|
|
this.series = new SeriesCollection({
|
|
models: options.model.series,
|
|
plot: this,
|
|
openmct: options.openmct,
|
|
palette: options.palette
|
|
});
|
|
|
|
if (this.get('domainObject').type === 'telemetry.plot.overlay') {
|
|
this.removeMutationListener = this.openmct.objects.observe(
|
|
this.get('domainObject'),
|
|
'*',
|
|
this.updateDomainObject.bind(this)
|
|
);
|
|
}
|
|
|
|
this.yAxis.listenToSeriesCollection(this.series);
|
|
this.additionalYAxes.forEach((yAxis) => {
|
|
yAxis.listenToSeriesCollection(this.series);
|
|
});
|
|
this.legend.listenToSeriesCollection(this.series);
|
|
|
|
this.listenTo(this, 'destroy', this.onDestroy, this);
|
|
}
|
|
/**
|
|
* Retrieve the persisted series config for a given identifier.
|
|
* @param {import('./PlotSeries').Identifier} identifier
|
|
* @returns {import('./PlotSeries').PlotSeriesModelType=}
|
|
*/
|
|
getPersistedSeriesConfig(identifier) {
|
|
const domainObject = this.get('domainObject');
|
|
if (!domainObject.configuration || !domainObject.configuration.series) {
|
|
return;
|
|
}
|
|
|
|
return domainObject.configuration.series.filter(function (seriesConfig) {
|
|
return (
|
|
seriesConfig.identifier.key === identifier.key &&
|
|
seriesConfig.identifier.namespace === identifier.namespace
|
|
);
|
|
})[0];
|
|
}
|
|
/**
|
|
* Retrieve the persisted filters for a given identifier.
|
|
*/
|
|
getPersistedFilters(identifier) {
|
|
const domainObject = this.get('domainObject');
|
|
const keystring = this.openmct.objects.makeKeyString(identifier);
|
|
|
|
if (!domainObject.configuration || !domainObject.configuration.filters) {
|
|
return;
|
|
}
|
|
|
|
return domainObject.configuration.filters[keystring];
|
|
}
|
|
/**
|
|
* Update the domain object with the given value.
|
|
*/
|
|
updateDomainObject(domainObject) {
|
|
this.set('domainObject', domainObject);
|
|
}
|
|
|
|
/**
|
|
* Clean up all objects and remove all listeners.
|
|
*/
|
|
onDestroy() {
|
|
this.xAxis.destroy();
|
|
this.yAxis.destroy();
|
|
this.additionalYAxes.forEach((additionalYAxis) => additionalYAxis.destroy());
|
|
this.series.destroy();
|
|
this.legend.destroy();
|
|
this.stopListening();
|
|
if (this.removeMutationListener) {
|
|
this.removeMutationListener();
|
|
}
|
|
}
|
|
/**
|
|
* Return defaults, which are extracted from the passed in domain
|
|
* object.
|
|
* @override
|
|
* @param {import('./Model').ModelOptions<PlotConfigModelType, PlotConfigModelOptions>} options
|
|
*/
|
|
defaultModel(options) {
|
|
return {
|
|
series: [],
|
|
domainObject: options.domainObject,
|
|
xAxis: {},
|
|
yAxis: _.cloneDeep(options.domainObject.configuration?.yAxis ?? {}),
|
|
additionalYAxes: _.cloneDeep(options.domainObject.configuration?.additionalYAxes ?? []),
|
|
legend: _.cloneDeep(options.domainObject.configuration?.legend ?? {})
|
|
};
|
|
}
|
|
}
|
|
|
|
/** @typedef {any} TODO */
|
|
|
|
/** @typedef {import('./PlotSeries').default} PlotSeries */
|
|
|
|
/**
|
|
@typedef {{
|
|
configuration: {
|
|
series: import('./PlotSeries').PlotSeriesModelType[]
|
|
yAxis: import('./YAxisModel').YAxisModelType
|
|
},
|
|
}} SomeDomainObject_NeedsName
|
|
*/
|
|
|
|
/**
|
|
@typedef {{
|
|
xAxis: import('./XAxisModel').XAxisModelType
|
|
yAxis: import('./YAxisModel').YAxisModelType
|
|
legend: TODO
|
|
series: PlotSeries[]
|
|
domainObject: SomeDomainObject_NeedsName
|
|
}} PlotConfigModelType
|
|
*/
|
|
|
|
/** @typedef {TODO} SomeOtherDomainObject */
|
|
|
|
/**
|
|
TODO: Is SomeOtherDomainObject the same domain object as with SomeDomainObject_NeedsName?
|
|
@typedef {{
|
|
plot: import('./PlotConfigurationModel').default
|
|
domainObject: SomeOtherDomainObject
|
|
}} PlotConfigModelOptions
|
|
*/
|