mirror of
https://github.com/nasa/openmct.git
synced 2025-07-01 12:58:56 +00:00
Compare commits
1 Commits
couchdb-ob
...
vue-timers
Author | SHA1 | Date | |
---|---|---|---|
c46f10de74 |
20
.eslintrc.js
20
.eslintrc.js
@ -54,7 +54,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
"anonymous": "always",
|
"anonymous": "always",
|
||||||
"asyncArrow": "always",
|
"asyncArrow": "always",
|
||||||
"named": "never"
|
"named": "never",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"array-bracket-spacing": "error",
|
"array-bracket-spacing": "error",
|
||||||
@ -178,10 +178,7 @@ module.exports = {
|
|||||||
//https://eslint.org/docs/rules/no-whitespace-before-property
|
//https://eslint.org/docs/rules/no-whitespace-before-property
|
||||||
"no-whitespace-before-property": "error",
|
"no-whitespace-before-property": "error",
|
||||||
// https://eslint.org/docs/rules/object-curly-newline
|
// https://eslint.org/docs/rules/object-curly-newline
|
||||||
"object-curly-newline": ["error", {
|
"object-curly-newline": ["error", {"consistent": true, "multiline": true}],
|
||||||
"consistent": true,
|
|
||||||
"multiline": true
|
|
||||||
}],
|
|
||||||
// https://eslint.org/docs/rules/object-property-newline
|
// https://eslint.org/docs/rules/object-property-newline
|
||||||
"object-property-newline": "error",
|
"object-property-newline": "error",
|
||||||
// https://eslint.org/docs/rules/brace-style
|
// https://eslint.org/docs/rules/brace-style
|
||||||
@ -191,7 +188,7 @@ module.exports = {
|
|||||||
// https://eslint.org/docs/rules/operator-linebreak
|
// https://eslint.org/docs/rules/operator-linebreak
|
||||||
"operator-linebreak": ["error", "before", {"overrides": {"=": "after"}}],
|
"operator-linebreak": ["error", "before", {"overrides": {"=": "after"}}],
|
||||||
// https://eslint.org/docs/rules/padding-line-between-statements
|
// https://eslint.org/docs/rules/padding-line-between-statements
|
||||||
"padding-line-between-statements": ["error", {
|
"padding-line-between-statements":["error", {
|
||||||
"blankLine": "always",
|
"blankLine": "always",
|
||||||
"prev": "multiline-block-like",
|
"prev": "multiline-block-like",
|
||||||
"next": "*"
|
"next": "*"
|
||||||
@ -203,17 +200,11 @@ module.exports = {
|
|||||||
// https://eslint.org/docs/rules/space-infix-ops
|
// https://eslint.org/docs/rules/space-infix-ops
|
||||||
"space-infix-ops": "error",
|
"space-infix-ops": "error",
|
||||||
// https://eslint.org/docs/rules/space-unary-ops
|
// https://eslint.org/docs/rules/space-unary-ops
|
||||||
"space-unary-ops": ["error", {
|
"space-unary-ops": ["error", {"words": true, "nonwords": false}],
|
||||||
"words": true,
|
|
||||||
"nonwords": false
|
|
||||||
}],
|
|
||||||
// https://eslint.org/docs/rules/arrow-spacing
|
// https://eslint.org/docs/rules/arrow-spacing
|
||||||
"arrow-spacing": "error",
|
"arrow-spacing": "error",
|
||||||
// https://eslint.org/docs/rules/semi-spacing
|
// https://eslint.org/docs/rules/semi-spacing
|
||||||
"semi-spacing": ["error", {
|
"semi-spacing": ["error", {"before": false, "after": true}],
|
||||||
"before": false,
|
|
||||||
"after": true
|
|
||||||
}],
|
|
||||||
|
|
||||||
"vue/html-indent": [
|
"vue/html-indent": [
|
||||||
"error",
|
"error",
|
||||||
@ -246,7 +237,6 @@ module.exports = {
|
|||||||
}],
|
}],
|
||||||
"vue/multiline-html-element-content-newline": "off",
|
"vue/multiline-html-element-content-newline": "off",
|
||||||
"vue/singleline-html-element-content-newline": "off",
|
"vue/singleline-html-element-content-newline": "off",
|
||||||
"vue/no-mutating-props": "off"
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
|
@ -138,7 +138,7 @@ define([
|
|||||||
"id": "styleguide:home",
|
"id": "styleguide:home",
|
||||||
"priority": "preferred",
|
"priority": "preferred",
|
||||||
"model": {
|
"model": {
|
||||||
"type": "noneditable.folder",
|
"type": "folder",
|
||||||
"name": "Style Guide Home",
|
"name": "Style Guide Home",
|
||||||
"location": "ROOT",
|
"location": "ROOT",
|
||||||
"composition": [
|
"composition": [
|
||||||
@ -155,7 +155,7 @@ define([
|
|||||||
"id": "styleguide:ui-elements",
|
"id": "styleguide:ui-elements",
|
||||||
"priority": "preferred",
|
"priority": "preferred",
|
||||||
"model": {
|
"model": {
|
||||||
"type": "noneditable.folder",
|
"type": "folder",
|
||||||
"name": "UI Elements",
|
"name": "UI Elements",
|
||||||
"location": "styleguide:home",
|
"location": "styleguide:home",
|
||||||
"composition": [
|
"composition": [
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "1.6.2-SNAPSHOT",
|
"version": "1.5.0-SNAPSHOT",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"d3-time": "1.0.x",
|
"d3-time": "1.0.x",
|
||||||
"d3-time-format": "2.1.x",
|
"d3-time-format": "2.1.x",
|
||||||
"eslint": "7.0.0",
|
"eslint": "7.0.0",
|
||||||
"eslint-plugin-vue": "^7.5.0",
|
"eslint-plugin-vue": "^6.0.0",
|
||||||
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
|
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
|
||||||
"eventemitter3": "^1.2.0",
|
"eventemitter3": "^1.2.0",
|
||||||
"exports-loader": "^0.7.0",
|
"exports-loader": "^0.7.0",
|
||||||
|
@ -44,9 +44,9 @@ define(
|
|||||||
// is also invoked during the create process which should be allowed,
|
// is also invoked during the create process which should be allowed,
|
||||||
// because it may be saved elsewhere
|
// because it may be saved elsewhere
|
||||||
if ((key === 'edit' && category === 'view-control') || key === 'properties') {
|
if ((key === 'edit' && category === 'view-control') || key === 'properties') {
|
||||||
let identifier = this.openmct.objects.parseKeyString(domainObject.getId());
|
let newStyleObject = objectUtils.toNewFormat(domainObject, domainObject.getId());
|
||||||
|
|
||||||
return this.openmct.objects.isPersistable(identifier);
|
return this.openmct.objects.isPersistable(newStyleObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -43,8 +43,7 @@ define(
|
|||||||
);
|
);
|
||||||
|
|
||||||
mockObjectAPI = jasmine.createSpyObj('objectAPI', [
|
mockObjectAPI = jasmine.createSpyObj('objectAPI', [
|
||||||
'isPersistable',
|
'isPersistable'
|
||||||
'parseKeyString'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mockAPI = {
|
mockAPI = {
|
||||||
|
@ -48,9 +48,9 @@ define(
|
|||||||
// prevents editing of objects that cannot be persisted, so we can assume that this
|
// prevents editing of objects that cannot be persisted, so we can assume that this
|
||||||
// is a new object.
|
// is a new object.
|
||||||
if (!(parent.hasCapability('editor') && parent.getCapability('editor').isEditContextRoot())) {
|
if (!(parent.hasCapability('editor') && parent.getCapability('editor').isEditContextRoot())) {
|
||||||
let identifier = this.openmct.objects.parseKeyString(parent.getId());
|
let newStyleObject = objectUtils.toNewFormat(parent, parent.getId());
|
||||||
|
|
||||||
return this.openmct.objects.isPersistable(identifier);
|
return this.openmct.objects.isPersistable(newStyleObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -33,8 +33,7 @@ define(
|
|||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
objectAPI = jasmine.createSpyObj('objectsAPI', [
|
objectAPI = jasmine.createSpyObj('objectsAPI', [
|
||||||
'isPersistable',
|
'isPersistable'
|
||||||
'parseKeyString'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mockOpenMCT = {
|
mockOpenMCT = {
|
||||||
|
@ -146,16 +146,11 @@ define([
|
|||||||
* @param {String} id to be indexed.
|
* @param {String} id to be indexed.
|
||||||
*/
|
*/
|
||||||
GenericSearchProvider.prototype.scheduleForIndexing = function (id) {
|
GenericSearchProvider.prototype.scheduleForIndexing = function (id) {
|
||||||
const identifier = objectUtils.parseKeyString(id);
|
|
||||||
const objectProvider = this.openmct.objects.getProvider(identifier);
|
|
||||||
|
|
||||||
if (objectProvider === undefined || objectProvider.search === undefined) {
|
|
||||||
if (!this.indexedIds[id] && !this.pendingIndex[id]) {
|
if (!this.indexedIds[id] && !this.pendingIndex[id]) {
|
||||||
this.indexedIds[id] = true;
|
this.indexedIds[id] = true;
|
||||||
this.pendingIndex[id] = true;
|
this.pendingIndex[id] = true;
|
||||||
this.idsToIndex.push(id);
|
this.idsToIndex.push(id);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.keepIndexing();
|
this.keepIndexing();
|
||||||
};
|
};
|
||||||
|
@ -219,7 +219,7 @@ define([
|
|||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name objects
|
* @name objects
|
||||||
*/
|
*/
|
||||||
this.objects = new api.ObjectAPI.default(this.types, this);
|
this.objects = new api.ObjectAPI();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for retrieving and interpreting telemetry data associated
|
* An interface for retrieving and interpreting telemetry data associated
|
||||||
@ -283,7 +283,6 @@ define([
|
|||||||
this.install(this.plugins.NewFolderAction());
|
this.install(this.plugins.NewFolderAction());
|
||||||
this.install(this.plugins.ViewDatumAction());
|
this.install(this.plugins.ViewDatumAction());
|
||||||
this.install(this.plugins.ObjectInterceptors());
|
this.install(this.plugins.ObjectInterceptors());
|
||||||
this.install(this.plugins.NonEditableFolder());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
@ -372,7 +371,7 @@ define([
|
|||||||
* MCT; if undefined, MCT will be run in the body of the document
|
* MCT; if undefined, MCT will be run in the body of the document
|
||||||
*/
|
*/
|
||||||
MCT.prototype.start = function (domElement = document.body, isHeadlessMode = false) {
|
MCT.prototype.start = function (domElement = document.body, isHeadlessMode = false) {
|
||||||
if (this.types.get('layout') === undefined) {
|
if (!this.plugins.DisplayLayout._installed) {
|
||||||
this.install(this.plugins.DisplayLayout({
|
this.install(this.plugins.DisplayLayout({
|
||||||
showAsView: ['summary-widget']
|
showAsView: ['summary-widget']
|
||||||
}));
|
}));
|
||||||
|
@ -61,7 +61,6 @@ define([
|
|||||||
const newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
|
const newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
|
||||||
const keystring = utils.makeKeyString(newStyleObject.identifier);
|
const keystring = utils.makeKeyString(newStyleObject.identifier);
|
||||||
|
|
||||||
this.eventEmitter.emit(keystring + ':$_synchronize_model', newStyleObject);
|
|
||||||
this.eventEmitter.emit(keystring + ":*", newStyleObject);
|
this.eventEmitter.emit(keystring + ":*", newStyleObject);
|
||||||
this.eventEmitter.emit('mutation', newStyleObject);
|
this.eventEmitter.emit('mutation', newStyleObject);
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
@ -139,12 +138,6 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ObjectServiceProvider.prototype.superSecretFallbackSearch = function (query, options) {
|
|
||||||
const searchService = this.$injector.get('searchService');
|
|
||||||
|
|
||||||
return searchService.query(query);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Injects new object API as a decorator so that it hijacks all requests.
|
// Injects new object API as a decorator so that it hijacks all requests.
|
||||||
// Object providers implemented on new API should just work, old API should just work, many things may break.
|
// Object providers implemented on new API should just work, old API should just work, many things may break.
|
||||||
function LegacyObjectAPIInterceptor(openmct, ROOTS, instantiate, topic, objectService) {
|
function LegacyObjectAPIInterceptor(openmct, ROOTS, instantiate, topic, objectService) {
|
||||||
|
@ -50,10 +50,6 @@ describe('The ActionCollection', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
openmct.objects.addProvider('', jasmine.createSpyObj('mockMutableObjectProvider', [
|
|
||||||
'create',
|
|
||||||
'update'
|
|
||||||
]));
|
|
||||||
mockView = {
|
mockView = {
|
||||||
getViewContext: () => {
|
getViewContext: () => {
|
||||||
return {
|
return {
|
||||||
|
@ -60,17 +60,6 @@ define([
|
|||||||
};
|
};
|
||||||
this.onProviderAdd = this.onProviderAdd.bind(this);
|
this.onProviderAdd = this.onProviderAdd.bind(this);
|
||||||
this.onProviderRemove = this.onProviderRemove.bind(this);
|
this.onProviderRemove = this.onProviderRemove.bind(this);
|
||||||
this.mutables = {};
|
|
||||||
|
|
||||||
if (this.domainObject.isMutable) {
|
|
||||||
this.returnMutables = true;
|
|
||||||
let unobserve = this.domainObject.$on('$_destroy', () => {
|
|
||||||
Object.values(this.mutables).forEach(mutable => {
|
|
||||||
this.publicAPI.objects.destroyMutable(mutable);
|
|
||||||
});
|
|
||||||
unobserve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,6 +75,10 @@ define([
|
|||||||
throw new Error('Event not supported by composition: ' + event);
|
throw new Error('Event not supported by composition: ' + event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.mutationListener) {
|
||||||
|
this._synchronize();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.provider.on && this.provider.off) {
|
if (this.provider.on && this.provider.off) {
|
||||||
if (event === 'add') {
|
if (event === 'add') {
|
||||||
this.provider.on(
|
this.provider.on(
|
||||||
@ -196,13 +189,6 @@ define([
|
|||||||
|
|
||||||
this.provider.add(this.domainObject, child.identifier);
|
this.provider.add(this.domainObject, child.identifier);
|
||||||
} else {
|
} 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);
|
this.emit('add', child);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -216,8 +202,6 @@ define([
|
|||||||
* @name load
|
* @name load
|
||||||
*/
|
*/
|
||||||
CompositionCollection.prototype.load = function () {
|
CompositionCollection.prototype.load = function () {
|
||||||
this.cleanUpMutables();
|
|
||||||
|
|
||||||
return this.provider.load(this.domainObject)
|
return this.provider.load(this.domainObject)
|
||||||
.then(function (children) {
|
.then(function (children) {
|
||||||
return Promise.all(children.map((c) => this.publicAPI.objects.get(c)));
|
return Promise.all(children.map((c) => this.publicAPI.objects.get(c)));
|
||||||
@ -250,14 +234,6 @@ define([
|
|||||||
if (!skipMutate) {
|
if (!skipMutate) {
|
||||||
this.provider.remove(this.domainObject, child.identifier);
|
this.provider.remove(this.domainObject, child.identifier);
|
||||||
} else {
|
} 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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('remove', child);
|
this.emit('remove', child);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -305,6 +281,12 @@ define([
|
|||||||
this.remove(child, true);
|
this.remove(child, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CompositionCollection.prototype._synchronize = function () {
|
||||||
|
this.mutationListener = this.publicAPI.objects.observe(this.domainObject, '*', (newDomainObject) => {
|
||||||
|
this.domainObject = JSON.parse(JSON.stringify(newDomainObject));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
CompositionCollection.prototype._destroy = function () {
|
CompositionCollection.prototype._destroy = function () {
|
||||||
if (this.mutationListener) {
|
if (this.mutationListener) {
|
||||||
this.mutationListener();
|
this.mutationListener();
|
||||||
@ -326,11 +308,5 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CompositionCollection.prototype.cleanUpMutables = function () {
|
|
||||||
Object.values(this.mutables).forEach(mutable => {
|
|
||||||
this.publicAPI.objects.destroyMutable(mutable);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return CompositionCollection;
|
return CompositionCollection;
|
||||||
});
|
});
|
||||||
|
@ -30,12 +30,12 @@ class Menu extends EventEmitter {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
|
|
||||||
this.component = new Vue({
|
this.component = new Vue({
|
||||||
components: {
|
|
||||||
MenuComponent
|
|
||||||
},
|
|
||||||
provide: {
|
provide: {
|
||||||
actions: options.actions
|
actions: options.actions
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
MenuComponent
|
||||||
|
},
|
||||||
template: '<menu-component />'
|
template: '<menu-component />'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -75,20 +75,13 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
* Info notifications are low priority informational messages for the user. They will be auto-destroy after a brief
|
* Info notifications are low priority informational messages for the user. They will be auto-destroy after a brief
|
||||||
* period of time.
|
* period of time.
|
||||||
* @param {string} message The message to display to the user
|
* @param {string} message The message to display to the user
|
||||||
* @param {Object} [options] object with following properties
|
|
||||||
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
|
|
||||||
* link: {Object} Add a link to notifications for navigation
|
|
||||||
* onClick: callback function
|
|
||||||
* cssClass: css class name to add style on link
|
|
||||||
* text: text to display for link
|
|
||||||
* @returns {InfoNotification}
|
* @returns {InfoNotification}
|
||||||
*/
|
*/
|
||||||
info(message, options = {}) {
|
info(message) {
|
||||||
let notificationModel = {
|
let notificationModel = {
|
||||||
message: message,
|
message: message,
|
||||||
autoDismiss: true,
|
autoDismiss: true,
|
||||||
severity: "info",
|
severity: "info"
|
||||||
options
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return this._notify(notificationModel);
|
return this._notify(notificationModel);
|
||||||
@ -97,19 +90,12 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Present an alert to the user.
|
* Present an alert to the user.
|
||||||
* @param {string} message The message to display to the user.
|
* @param {string} message The message to display to the user.
|
||||||
* @param {Object} [options] object with following properties
|
|
||||||
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
|
|
||||||
* link: {Object} Add a link to notifications for navigation
|
|
||||||
* onClick: callback function
|
|
||||||
* cssClass: css class name to add style on link
|
|
||||||
* text: text to display for link
|
|
||||||
* @returns {Notification}
|
* @returns {Notification}
|
||||||
*/
|
*/
|
||||||
alert(message, options = {}) {
|
alert(message) {
|
||||||
let notificationModel = {
|
let notificationModel = {
|
||||||
message: message,
|
message: message,
|
||||||
severity: "alert",
|
severity: "alert"
|
||||||
options
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return this._notify(notificationModel);
|
return this._notify(notificationModel);
|
||||||
@ -118,19 +104,12 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Present an error message to the user
|
* Present an error message to the user
|
||||||
* @param {string} message
|
* @param {string} message
|
||||||
* @param {Object} [options] object with following properties
|
|
||||||
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
|
|
||||||
* link: {Object} Add a link to notifications for navigation
|
|
||||||
* onClick: callback function
|
|
||||||
* cssClass: css class name to add style on link
|
|
||||||
* text: text to display for link
|
|
||||||
* @returns {Notification}
|
* @returns {Notification}
|
||||||
*/
|
*/
|
||||||
error(message, options = {}) {
|
error(message) {
|
||||||
let notificationModel = {
|
let notificationModel = {
|
||||||
message: message,
|
message: message,
|
||||||
severity: "error",
|
severity: "error"
|
||||||
options
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return this._notify(notificationModel);
|
return this._notify(notificationModel);
|
||||||
@ -346,11 +325,9 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
this.emit('notification', notification);
|
this.emit('notification', notification);
|
||||||
|
|
||||||
if (notification.model.autoDismiss || this._selectNextNotification()) {
|
if (notification.model.autoDismiss || this._selectNextNotification()) {
|
||||||
const autoDismissTimeout = notification.model.options.autoDismissTimeout
|
|
||||||
|| DEFAULT_AUTO_DISMISS_TIMEOUT;
|
|
||||||
this.activeTimeout = setTimeout(() => {
|
this.activeTimeout = setTimeout(() => {
|
||||||
this._dismissOrMinimize(notification);
|
this._dismissOrMinimize(notification);
|
||||||
}, autoDismissTimeout);
|
}, DEFAULT_AUTO_DISMISS_TIMEOUT);
|
||||||
} else {
|
} else {
|
||||||
delete this.activeTimeout;
|
delete this.activeTimeout;
|
||||||
}
|
}
|
||||||
|
@ -1,154 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 NotificationAPI from './NotificationAPI';
|
|
||||||
|
|
||||||
describe('The Notifiation API', () => {
|
|
||||||
let notificationAPIInstance;
|
|
||||||
let defaultTimeout = 4000;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
notificationAPIInstance = new NotificationAPI();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('the info method', () => {
|
|
||||||
let message = 'Example Notification Message';
|
|
||||||
let severity = 'info';
|
|
||||||
let notificationModel;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
notificationModel = notificationAPIInstance.info(message).model;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
notificationAPIInstance.dismissAllNotifications();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows a string message with info severity', () => {
|
|
||||||
expect(notificationModel.message).toEqual(message);
|
|
||||||
expect(notificationModel.severity).toEqual(severity);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('auto dismisses the notification after a brief timeout', (done) => {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
expect(notificationAPIInstance.notifications.length).toEqual(0);
|
|
||||||
done();
|
|
||||||
}, defaultTimeout);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('the alert method', () => {
|
|
||||||
let message = 'Example alert message';
|
|
||||||
let severity = 'alert';
|
|
||||||
let notificationModel;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
notificationModel = notificationAPIInstance.alert(message).model;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
notificationAPIInstance.dismissAllNotifications();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows a string message, with alert severity', () => {
|
|
||||||
expect(notificationModel.message).toEqual(message);
|
|
||||||
expect(notificationModel.severity).toEqual(severity);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not auto dismiss the notification', (done) => {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
expect(notificationAPIInstance.notifications.length).toEqual(1);
|
|
||||||
done();
|
|
||||||
}, defaultTimeout);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('the error method', () => {
|
|
||||||
let message = 'Example error message';
|
|
||||||
let severity = 'error';
|
|
||||||
let notificationModel;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
notificationModel = notificationAPIInstance.error(message).model;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
notificationAPIInstance.dismissAllNotifications();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows a string message, with severity error', () => {
|
|
||||||
expect(notificationModel.message).toEqual(message);
|
|
||||||
expect(notificationModel.severity).toEqual(severity);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not auto dismiss the notification', (done) => {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
expect(notificationAPIInstance.notifications.length).toEqual(1);
|
|
||||||
done();
|
|
||||||
}, defaultTimeout);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('the progress method', () => {
|
|
||||||
let title = 'This is a progress notification';
|
|
||||||
let message1 = 'Example progress message 1';
|
|
||||||
let message2 = 'Example progress message 2';
|
|
||||||
let percentage1 = 50;
|
|
||||||
let percentage2 = 99.9;
|
|
||||||
let severity = 'info';
|
|
||||||
let notification;
|
|
||||||
let updatedPercentage;
|
|
||||||
let updatedMessage;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
notification = notificationAPIInstance.progress(title, percentage1, message1);
|
|
||||||
notification.on('progress', (percentage, text) => {
|
|
||||||
updatedPercentage = percentage;
|
|
||||||
updatedMessage = text;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
notificationAPIInstance.dismissAllNotifications();
|
|
||||||
});
|
|
||||||
|
|
||||||
it ('shows a notification with a message, progress message, percentage and info severity', () => {
|
|
||||||
expect(notification.model.message).toEqual(title);
|
|
||||||
expect(notification.model.severity).toEqual(severity);
|
|
||||||
expect(notification.model.progressText).toEqual(message1);
|
|
||||||
expect(notification.model.progressPerc).toEqual(percentage1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it ('allows dynamically updating the progress attributes', () => {
|
|
||||||
notification.progress(percentage2, message2);
|
|
||||||
|
|
||||||
expect(updatedPercentage).toEqual(percentage2);
|
|
||||||
expect(updatedMessage).toEqual(message2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it ('allows dynamically dismissing of progress notification', () => {
|
|
||||||
notification.dismiss();
|
|
||||||
|
|
||||||
expect(notificationAPIInstance.notifications.length).toEqual(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,147 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 utils from './object-utils.js';
|
|
||||||
import EventEmitter from 'EventEmitter';
|
|
||||||
|
|
||||||
const ANY_OBJECT_EVENT = 'mutation';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a domain object to keep its model synchronized with other instances of the same object.
|
|
||||||
*
|
|
||||||
* Creating a MutableDomainObject will automatically register listeners to keep its model in sync. As such, developers
|
|
||||||
* should be careful to destroy MutableDomainObject in order to avoid memory leaks.
|
|
||||||
*
|
|
||||||
* All Open MCT API functions that provide objects will provide MutableDomainObjects where possible, except
|
|
||||||
* `openmct.objects.get()`, and will manage that object's lifecycle for you. Calling `openmct.objects.getMutable()`
|
|
||||||
* will result in the creation of a new MutableDomainObject and you will be responsible for destroying it
|
|
||||||
* (via openmct.objects.destroy) when you're done with it.
|
|
||||||
*
|
|
||||||
* @typedef MutableDomainObject
|
|
||||||
* @memberof module:openmct
|
|
||||||
*/
|
|
||||||
class MutableDomainObject {
|
|
||||||
constructor(eventEmitter) {
|
|
||||||
Object.defineProperties(this, {
|
|
||||||
_globalEventEmitter: {
|
|
||||||
value: eventEmitter,
|
|
||||||
// Property should not be serialized
|
|
||||||
enumerable: false
|
|
||||||
},
|
|
||||||
_instanceEventEmitter: {
|
|
||||||
value: new EventEmitter(),
|
|
||||||
// Property should not be serialized
|
|
||||||
enumerable: false
|
|
||||||
},
|
|
||||||
_observers: {
|
|
||||||
value: [],
|
|
||||||
// Property should not be serialized
|
|
||||||
enumerable: false
|
|
||||||
},
|
|
||||||
isMutable: {
|
|
||||||
value: true,
|
|
||||||
// Property should not be serialized
|
|
||||||
enumerable: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$observe(path, callback) {
|
|
||||||
let fullPath = qualifiedEventName(this, path);
|
|
||||||
let eventOff =
|
|
||||||
this._globalEventEmitter.off.bind(this._globalEventEmitter, fullPath, callback);
|
|
||||||
|
|
||||||
this._globalEventEmitter.on(fullPath, callback);
|
|
||||||
this._observers.push(eventOff);
|
|
||||||
|
|
||||||
return eventOff;
|
|
||||||
}
|
|
||||||
$set(path, value) {
|
|
||||||
_.set(this, path, value);
|
|
||||||
_.set(this, 'modified', Date.now());
|
|
||||||
|
|
||||||
//Emit secret synchronization event first, so that all objects are in sync before subsequent events fired.
|
|
||||||
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this);
|
|
||||||
|
|
||||||
//Emit a general "any object" event
|
|
||||||
this._globalEventEmitter.emit(ANY_OBJECT_EVENT, this);
|
|
||||||
//Emit wildcard event, with path so that callback knows what changed
|
|
||||||
this._globalEventEmitter.emit(qualifiedEventName(this, '*'), this, path, value);
|
|
||||||
|
|
||||||
//Emit events specific to properties affected
|
|
||||||
let parentPropertiesList = path.split('.');
|
|
||||||
for (let index = parentPropertiesList.length; index > 0; index--) {
|
|
||||||
let parentPropertyPath = parentPropertiesList.slice(0, index).join('.');
|
|
||||||
this._globalEventEmitter.emit(qualifiedEventName(this, parentPropertyPath), _.get(this, parentPropertyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Emit events for listeners of child properties when parent changes.
|
|
||||||
// Do it at observer time - also register observers for parent attribute path.
|
|
||||||
}
|
|
||||||
|
|
||||||
$refresh(model) {
|
|
||||||
//TODO: Currently we are updating the entire object.
|
|
||||||
// In the future we could update a specific property of the object using the 'path' parameter.
|
|
||||||
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), model);
|
|
||||||
|
|
||||||
//Emit wildcard event, with path so that callback knows what changed
|
|
||||||
this._globalEventEmitter.emit(qualifiedEventName(this, '*'), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
$on(event, callback) {
|
|
||||||
this._instanceEventEmitter.on(event, callback);
|
|
||||||
|
|
||||||
return () => this._instanceEventEmitter.off(event, callback);
|
|
||||||
}
|
|
||||||
$destroy() {
|
|
||||||
this._observers.forEach(observer => observer());
|
|
||||||
delete this._globalEventEmitter;
|
|
||||||
delete this._observers;
|
|
||||||
this._instanceEventEmitter.emit('$_destroy');
|
|
||||||
}
|
|
||||||
|
|
||||||
static createMutable(object, mutationTopic) {
|
|
||||||
let mutable = Object.create(new MutableDomainObject(mutationTopic));
|
|
||||||
Object.assign(mutable, object);
|
|
||||||
|
|
||||||
mutable.$observe('$_synchronize_model', (updatedObject) => {
|
|
||||||
let clone = JSON.parse(JSON.stringify(updatedObject));
|
|
||||||
let deleted = _.difference(Object.keys(mutable), Object.keys(updatedObject));
|
|
||||||
deleted.forEach((propertyName) => delete mutable[propertyName]);
|
|
||||||
Object.assign(mutable, clone);
|
|
||||||
});
|
|
||||||
|
|
||||||
return mutable;
|
|
||||||
}
|
|
||||||
|
|
||||||
static mutateObject(object, path, value) {
|
|
||||||
_.set(object, path, value);
|
|
||||||
_.set(object, 'modified', Date.now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function qualifiedEventName(object, eventName) {
|
|
||||||
let keystring = utils.makeKeyString(object.identifier);
|
|
||||||
|
|
||||||
return [keystring, eventName].join(':');
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MutableDomainObject;
|
|
102
src/api/objects/MutableObject.js
Normal file
102
src/api/objects/MutableObject.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define([
|
||||||
|
'objectUtils',
|
||||||
|
'lodash'
|
||||||
|
], function (
|
||||||
|
utils,
|
||||||
|
_
|
||||||
|
) {
|
||||||
|
const ANY_OBJECT_EVENT = "mutation";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MutableObject wraps a DomainObject and provides getters and
|
||||||
|
* setters for
|
||||||
|
* @param eventEmitter
|
||||||
|
* @param object
|
||||||
|
* @interface MutableObject
|
||||||
|
*/
|
||||||
|
function MutableObject(eventEmitter, object) {
|
||||||
|
this.eventEmitter = eventEmitter;
|
||||||
|
this.object = object;
|
||||||
|
this.unlisteners = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function qualifiedEventName(object, eventName) {
|
||||||
|
const keystring = utils.makeKeyString(object.identifier);
|
||||||
|
|
||||||
|
return [keystring, eventName].join(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableObject.prototype.stopListening = function () {
|
||||||
|
this.unlisteners.forEach(function (unlisten) {
|
||||||
|
unlisten();
|
||||||
|
});
|
||||||
|
this.unlisteners = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe changes to this domain object.
|
||||||
|
* @param {string} path the property to observe
|
||||||
|
* @param {Function} callback a callback to invoke when new values for
|
||||||
|
* this property are observed
|
||||||
|
* @method on
|
||||||
|
* @memberof module:openmct.MutableObject#
|
||||||
|
*/
|
||||||
|
MutableObject.prototype.on = function (path, callback) {
|
||||||
|
const fullPath = qualifiedEventName(this.object, path);
|
||||||
|
const eventOff =
|
||||||
|
this.eventEmitter.off.bind(this.eventEmitter, fullPath, callback);
|
||||||
|
|
||||||
|
this.eventEmitter.on(fullPath, callback);
|
||||||
|
this.unlisteners.push(eventOff);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify this domain object.
|
||||||
|
* @param {string} path the property to modify
|
||||||
|
* @param {*} value the new value for this property
|
||||||
|
* @method set
|
||||||
|
* @memberof module:openmct.MutableObject#
|
||||||
|
*/
|
||||||
|
MutableObject.prototype.set = function (path, value) {
|
||||||
|
_.set(this.object, path, value);
|
||||||
|
_.set(this.object, 'modified', Date.now());
|
||||||
|
|
||||||
|
const handleRecursiveMutation = function (newObject) {
|
||||||
|
this.object = newObject;
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
//Emit wildcard event
|
||||||
|
this.eventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
|
||||||
|
//Emit a general "any object" event
|
||||||
|
this.eventEmitter.emit(ANY_OBJECT_EVENT, this.object);
|
||||||
|
|
||||||
|
this.eventEmitter.on(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
|
||||||
|
//Emit event specific to property
|
||||||
|
this.eventEmitter.emit(qualifiedEventName(this.object, path), value);
|
||||||
|
this.eventEmitter.off(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
|
||||||
|
};
|
||||||
|
|
||||||
|
return MutableObject;
|
||||||
|
});
|
@ -20,79 +20,68 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import utils from 'objectUtils';
|
define([
|
||||||
import MutableDomainObject from './MutableDomainObject';
|
'lodash',
|
||||||
import RootRegistry from './RootRegistry';
|
'objectUtils',
|
||||||
import RootObjectProvider from './RootObjectProvider';
|
'./MutableObject',
|
||||||
import EventEmitter from 'EventEmitter';
|
'./RootRegistry',
|
||||||
import InterceptorRegistry from './InterceptorRegistry';
|
'./RootObjectProvider',
|
||||||
|
'./InterceptorRegistry',
|
||||||
|
'EventEmitter'
|
||||||
|
], function (
|
||||||
|
_,
|
||||||
|
utils,
|
||||||
|
MutableObject,
|
||||||
|
RootRegistry,
|
||||||
|
RootObjectProvider,
|
||||||
|
InterceptorRegistry,
|
||||||
|
EventEmitter
|
||||||
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for loading, saving, and manipulating domain objects.
|
* Utilities for loading, saving, and manipulating domain objects.
|
||||||
* @interface ObjectAPI
|
* @interface ObjectAPI
|
||||||
* @memberof module:openmct
|
* @memberof module:openmct
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function ObjectAPI(typeRegistry, openmct) {
|
function ObjectAPI() {
|
||||||
this.typeRegistry = typeRegistry;
|
|
||||||
this.eventEmitter = new EventEmitter();
|
this.eventEmitter = new EventEmitter();
|
||||||
this.providers = {};
|
this.providers = {};
|
||||||
this.rootRegistry = new RootRegistry();
|
this.rootRegistry = new RootRegistry();
|
||||||
this.injectIdentifierService = function () {
|
this.rootProvider = new RootObjectProvider.default(this.rootRegistry);
|
||||||
this.identifierService = openmct.$injector.get("identifierService");
|
|
||||||
};
|
|
||||||
|
|
||||||
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
this.interceptorRegistry = new InterceptorRegistry();
|
this.interceptorRegistry = new InterceptorRegistry.default();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set fallback provider, this is an internal API for legacy reasons.
|
* Set fallback provider, this is an internal API for legacy reasons.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.supersecretSetFallbackProvider = function (p) {
|
ObjectAPI.prototype.supersecretSetFallbackProvider = function (p) {
|
||||||
this.fallbackProvider = p;
|
this.fallbackProvider = p;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ObjectAPI.prototype.getIdentifierService = function () {
|
|
||||||
// Lazily acquire identifier service
|
|
||||||
if (!this.identifierService) {
|
|
||||||
this.injectIdentifierService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.identifierService;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the provider for a given identifier.
|
* Retrieve the provider for a given identifier.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.getProvider = function (identifier) {
|
ObjectAPI.prototype.getProvider = function (identifier) {
|
||||||
//handles the '' vs 'mct' namespace issue
|
|
||||||
const keyString = utils.makeKeyString(identifier);
|
|
||||||
const identifierService = this.getIdentifierService();
|
|
||||||
const namespace = identifierService.parse(keyString).getSpace();
|
|
||||||
|
|
||||||
if (identifier.key === 'ROOT') {
|
if (identifier.key === 'ROOT') {
|
||||||
return this.rootProvider;
|
return this.rootProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.providers[namespace] || this.fallbackProvider;
|
return this.providers[identifier.namespace] || this.fallbackProvider;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the root-level object.
|
* Get the root-level object.
|
||||||
* @returns {Promise.<DomainObject>} a promise for the root object
|
* @returns {Promise.<DomainObject>} a promise for the root object
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.getRoot = function () {
|
ObjectAPI.prototype.getRoot = function () {
|
||||||
return this.rootProvider.get();
|
return this.rootProvider.get();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new object provider for a particular namespace.
|
* Register a new object provider for a particular namespace.
|
||||||
*
|
*
|
||||||
* @param {string} namespace the namespace for which to provide objects
|
* @param {string} namespace the namespace for which to provide objects
|
||||||
@ -101,11 +90,11 @@ ObjectAPI.prototype.getRoot = function () {
|
|||||||
* @memberof {module:openmct.ObjectAPI#}
|
* @memberof {module:openmct.ObjectAPI#}
|
||||||
* @name addProvider
|
* @name addProvider
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
||||||
this.providers[namespace] = provider;
|
this.providers[namespace] = provider;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the ability to read, write, and delete domain objects.
|
* Provides the ability to read, write, and delete domain objects.
|
||||||
*
|
*
|
||||||
* When registering a new object provider, all methods on this interface
|
* When registering a new object provider, all methods on this interface
|
||||||
@ -115,7 +104,7 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
|||||||
* @memberof module:openmct
|
* @memberof module:openmct
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the given domain object in the corresponding persistence store
|
* Create the given domain object in the corresponding persistence store
|
||||||
*
|
*
|
||||||
* @method create
|
* @method create
|
||||||
@ -126,7 +115,7 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
|||||||
* has been created, or be rejected if it cannot be saved
|
* has been created, or be rejected if it cannot be saved
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update this domain object in its persistence store
|
* Update this domain object in its persistence store
|
||||||
*
|
*
|
||||||
* @method update
|
* @method update
|
||||||
@ -137,7 +126,7 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
|||||||
* has been updated, or be rejected if it cannot be saved
|
* has been updated, or be rejected if it cannot be saved
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete this domain object.
|
* Delete this domain object.
|
||||||
*
|
*
|
||||||
* @method delete
|
* @method delete
|
||||||
@ -148,7 +137,7 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
|||||||
* has been deleted, or be rejected if it cannot be deleted
|
* has been deleted, or be rejected if it cannot be deleted
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a domain object.
|
* Get a domain object.
|
||||||
*
|
*
|
||||||
* @method get
|
* @method get
|
||||||
@ -158,7 +147,17 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
|||||||
* has been saved, or be rejected if it cannot be saved
|
* has been saved, or be rejected if it cannot be saved
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ObjectAPI.prototype.get = function (identifier) {
|
/**
|
||||||
|
* Get a domain object.
|
||||||
|
*
|
||||||
|
* @method get
|
||||||
|
* @memberof module:openmct.ObjectAPI#
|
||||||
|
* @param {module:openmct.ObjectAPI~Identifier} identifier
|
||||||
|
* the identifier for the domain object to load
|
||||||
|
* @returns {Promise} a promise which will resolve when the domain object
|
||||||
|
* has been saved, or be rejected if it cannot be saved
|
||||||
|
*/
|
||||||
|
ObjectAPI.prototype.get = function (identifier) {
|
||||||
let keystring = this.makeKeyString(identifier);
|
let keystring = this.makeKeyString(identifier);
|
||||||
if (this.cache[keystring] !== undefined) {
|
if (this.cache[keystring] !== undefined) {
|
||||||
return this.cache[keystring];
|
return this.cache[keystring];
|
||||||
@ -176,6 +175,7 @@ ObjectAPI.prototype.get = function (identifier) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let objectPromise = provider.get(identifier);
|
let objectPromise = provider.get(identifier);
|
||||||
|
|
||||||
this.cache[keystring] = objectPromise;
|
this.cache[keystring] = objectPromise;
|
||||||
|
|
||||||
return objectPromise.then(result => {
|
return objectPromise.then(result => {
|
||||||
@ -187,101 +187,21 @@ ObjectAPI.prototype.get = function (identifier) {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
ObjectAPI.prototype.delete = function () {
|
||||||
* Search for domain objects.
|
|
||||||
*
|
|
||||||
* Object providersSearches and combines results of each object provider search.
|
|
||||||
* Objects without search provided will have been indexed
|
|
||||||
* and will be searched using the fallback indexed search.
|
|
||||||
* Search results are asynchronous and resolve in parallel.
|
|
||||||
*
|
|
||||||
* @method search
|
|
||||||
* @memberof module:openmct.ObjectAPI#
|
|
||||||
* @param {string} query the term to search for
|
|
||||||
* @param {Object} options search options
|
|
||||||
* @returns {Array.<Promise.<module:openmct.DomainObject>>}
|
|
||||||
* an array of promises returned from each object provider's search function
|
|
||||||
* each resolving to domain objects matching provided search query and options.
|
|
||||||
*/
|
|
||||||
ObjectAPI.prototype.search = function (query, options) {
|
|
||||||
const searchPromises = Object.values(this.providers)
|
|
||||||
.filter(provider => provider.search !== undefined)
|
|
||||||
.map(provider => provider.search(query, options));
|
|
||||||
|
|
||||||
searchPromises.push(this.fallbackProvider.superSecretFallbackSearch(query, options)
|
|
||||||
.then(results => results.hits
|
|
||||||
.map(hit => utils.toNewFormat(hit.object.getModel(), hit.object.getId()))));
|
|
||||||
|
|
||||||
return searchPromises;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will fetch object for the given identifier, returning a version of the object that will automatically keep
|
|
||||||
* itself updated as it is mutated. Before using this function, you should ask yourself whether you really need it.
|
|
||||||
* The platform will provide mutable objects to views automatically if the underlying object can be mutated. The
|
|
||||||
* platform will manage the lifecycle of any mutable objects that it provides. If you use `getMutable` you are
|
|
||||||
* committing to managing that lifecycle yourself. `.destroy` should be called when the object is no longer needed.
|
|
||||||
*
|
|
||||||
* @memberof {module:openmct.ObjectAPI#}
|
|
||||||
* @returns {Promise.<MutableDomainObject>} a promise that will resolve with a MutableDomainObject if
|
|
||||||
* the object can be mutated.
|
|
||||||
*/
|
|
||||||
ObjectAPI.prototype.getMutable = function (idOrKeyString) {
|
|
||||||
if (!this.supportsMutation(idOrKeyString)) {
|
|
||||||
throw new Error(`Object "${this.makeKeyString(idOrKeyString)}" does not support mutation.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.get(idOrKeyString).then((object) => {
|
|
||||||
const mutableDomainObject = this._toMutable(object);
|
|
||||||
|
|
||||||
// Check if provider supports realtime updates
|
|
||||||
let identifier = utils.parseKeyString(idOrKeyString);
|
|
||||||
let provider = this.getProvider(identifier);
|
|
||||||
|
|
||||||
if (provider !== undefined
|
|
||||||
&& provider.observe !== undefined) {
|
|
||||||
let unobserve = provider.observe(identifier, (updatedModel) => {
|
|
||||||
mutableDomainObject.$refresh(updatedModel);
|
|
||||||
});
|
|
||||||
mutableDomainObject.$on('$destroy', () => {
|
|
||||||
unobserve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return mutableDomainObject;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is for cleaning up a mutable domain object when you're done with it.
|
|
||||||
* You only need to use this if you retrieved the object using `getMutable()`. If the object was provided by the
|
|
||||||
* platform (eg. passed into a `view()` function) then the platform is responsible for its lifecycle.
|
|
||||||
* @param {MutableDomainObject} domainObject
|
|
||||||
*/
|
|
||||||
ObjectAPI.prototype.destroyMutable = function (domainObject) {
|
|
||||||
if (domainObject.isMutable) {
|
|
||||||
return domainObject.$destroy();
|
|
||||||
} else {
|
|
||||||
throw new Error("Attempted to destroy non-mutable domain object");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ObjectAPI.prototype.delete = function () {
|
|
||||||
throw new Error('Delete not implemented');
|
throw new Error('Delete not implemented');
|
||||||
};
|
};
|
||||||
|
|
||||||
ObjectAPI.prototype.isPersistable = function (idOrKeyString) {
|
ObjectAPI.prototype.isPersistable = function (domainObject) {
|
||||||
let identifier = utils.parseKeyString(idOrKeyString);
|
let provider = this.getProvider(domainObject.identifier);
|
||||||
let provider = this.getProvider(identifier);
|
|
||||||
|
|
||||||
return provider !== undefined
|
return provider !== undefined
|
||||||
&& provider.create !== undefined
|
&& provider.create !== undefined
|
||||||
&& provider.update !== undefined;
|
&& provider.update !== undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save this domain object in its current state. EXPERIMENTAL
|
* Save this domain object in its current state. EXPERIMENTAL
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
@ -291,12 +211,12 @@ ObjectAPI.prototype.isPersistable = function (idOrKeyString) {
|
|||||||
* @returns {Promise} a promise which will resolve when the domain object
|
* @returns {Promise} a promise which will resolve when the domain object
|
||||||
* has been saved, or be rejected if it cannot be saved
|
* has been saved, or be rejected if it cannot be saved
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.save = function (domainObject) {
|
ObjectAPI.prototype.save = function (domainObject) {
|
||||||
let provider = this.getProvider(domainObject.identifier);
|
let provider = this.getProvider(domainObject.identifier);
|
||||||
let savedResolve;
|
let savedResolve;
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
if (!this.isPersistable(domainObject.identifier)) {
|
if (!this.isPersistable(domainObject)) {
|
||||||
result = Promise.reject('Object provider does not support saving');
|
result = Promise.reject('Object provider does not support saving');
|
||||||
} else if (hasAlreadyBeenPersisted(domainObject)) {
|
} else if (hasAlreadyBeenPersisted(domainObject)) {
|
||||||
result = Promise.resolve(true);
|
result = Promise.resolve(true);
|
||||||
@ -319,9 +239,9 @@ ObjectAPI.prototype.save = function (domainObject) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a root-level object.
|
* Add a root-level object.
|
||||||
* @param {module:openmct.ObjectAPI~Identifier|function} an array of
|
* @param {module:openmct.ObjectAPI~Identifier|function} an array of
|
||||||
* identifiers for root level objects, or a function that returns a
|
* identifiers for root level objects, or a function that returns a
|
||||||
@ -329,32 +249,11 @@ ObjectAPI.prototype.save = function (domainObject) {
|
|||||||
* @method addRoot
|
* @method addRoot
|
||||||
* @memberof module:openmct.ObjectAPI#
|
* @memberof module:openmct.ObjectAPI#
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.addRoot = function (key) {
|
ObjectAPI.prototype.addRoot = function (key) {
|
||||||
this.rootRegistry.addRoot(key);
|
this.rootRegistry.addRoot(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register an object interceptor that transforms a domain object requested via module:openmct.ObjectAPI.get
|
|
||||||
* The domain object will be transformed after it is retrieved from the persistence store
|
|
||||||
* The domain object will be transformed only if the interceptor is applicable to that domain object as defined by the InterceptorDef
|
|
||||||
*
|
|
||||||
* @param {module:openmct.InterceptorDef} interceptorDef the interceptor definition to add
|
|
||||||
* @method addGetInterceptor
|
|
||||||
* @memberof module:openmct.InterceptorRegistry#
|
|
||||||
*/
|
|
||||||
ObjectAPI.prototype.addGetInterceptor = function (interceptorDef) {
|
|
||||||
this.interceptorRegistry.addInterceptor(interceptorDef);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the interceptors for a given domain object.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ObjectAPI.prototype.listGetInterceptors = function (identifier, object) {
|
|
||||||
return this.interceptorRegistry.getInterceptors(identifier, object);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modify a domain object.
|
* Modify a domain object.
|
||||||
* @param {module:openmct.DomainObject} object the object to mutate
|
* @param {module:openmct.DomainObject} object the object to mutate
|
||||||
* @param {string} path the property to modify
|
* @param {string} path the property to modify
|
||||||
@ -362,49 +261,14 @@ ObjectAPI.prototype.listGetInterceptors = function (identifier, object) {
|
|||||||
* @method mutate
|
* @method mutate
|
||||||
* @memberof module:openmct.ObjectAPI#
|
* @memberof module:openmct.ObjectAPI#
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.mutate = function (domainObject, path, value) {
|
ObjectAPI.prototype.mutate = function (domainObject, path, value) {
|
||||||
if (!this.supportsMutation(domainObject.identifier)) {
|
const mutableObject =
|
||||||
throw `Error: Attempted to mutate immutable object ${domainObject.name}`;
|
new MutableObject(this.eventEmitter, domainObject);
|
||||||
}
|
|
||||||
|
|
||||||
if (domainObject.isMutable) {
|
return mutableObject.set(path, value);
|
||||||
domainObject.$set(path, value);
|
};
|
||||||
} else {
|
|
||||||
//Creating a temporary mutable domain object allows other mutable instances of the
|
|
||||||
//object to be kept in sync.
|
|
||||||
let mutableDomainObject = this._toMutable(domainObject);
|
|
||||||
|
|
||||||
//Mutate original object
|
/**
|
||||||
MutableDomainObject.mutateObject(domainObject, path, value);
|
|
||||||
|
|
||||||
//Mutate temporary mutable object, in the process informing any other mutable instances
|
|
||||||
mutableDomainObject.$set(path, value);
|
|
||||||
|
|
||||||
//Destroy temporary mutable object
|
|
||||||
this.destroyMutable(mutableDomainObject);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ObjectAPI.prototype._toMutable = function (object) {
|
|
||||||
if (object.isMutable) {
|
|
||||||
return object;
|
|
||||||
} else {
|
|
||||||
return MutableDomainObject.createMutable(object, this.eventEmitter);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param module:openmct.ObjectAPI~Identifier identifier An object identifier
|
|
||||||
* @returns {boolean} true if the object can be mutated, otherwise returns false
|
|
||||||
*/
|
|
||||||
ObjectAPI.prototype.supportsMutation = function (identifier) {
|
|
||||||
return this.isPersistable(identifier);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observe changes to a domain object.
|
* Observe changes to a domain object.
|
||||||
* @param {module:openmct.DomainObject} object the object to observe
|
* @param {module:openmct.DomainObject} object the object to observe
|
||||||
* @param {string} path the property to observe
|
* @param {string} path the property to observe
|
||||||
@ -413,47 +277,36 @@ ObjectAPI.prototype.supportsMutation = function (identifier) {
|
|||||||
* @method observe
|
* @method observe
|
||||||
* @memberof module:openmct.ObjectAPI#
|
* @memberof module:openmct.ObjectAPI#
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.observe = function (domainObject, path, callback) {
|
ObjectAPI.prototype.observe = function (domainObject, path, callback) {
|
||||||
if (domainObject.isMutable) {
|
const mutableObject =
|
||||||
return domainObject.$observe(path, callback);
|
new MutableObject(this.eventEmitter, domainObject);
|
||||||
} else {
|
mutableObject.on(path, callback);
|
||||||
let mutable = this._toMutable(domainObject);
|
|
||||||
mutable.$observe(path, callback);
|
|
||||||
|
|
||||||
return () => mutable.$destroy();
|
return mutableObject.stopListening.bind(mutableObject);
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {module:openmct.ObjectAPI~Identifier} identifier
|
* @param {module:openmct.ObjectAPI~Identifier} identifier
|
||||||
* @returns {string} A string representation of the given identifier, including namespace and key
|
* @returns {string} A string representation of the given identifier, including namespace and key
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.makeKeyString = function (identifier) {
|
ObjectAPI.prototype.makeKeyString = function (identifier) {
|
||||||
return utils.makeKeyString(identifier);
|
return utils.makeKeyString(identifier);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} keyString A string representation of the given identifier, that is, a namespace and key separated by a colon.
|
|
||||||
* @returns {module:openmct.ObjectAPI~Identifier} An identifier object
|
|
||||||
*/
|
|
||||||
ObjectAPI.prototype.parseKeyString = function (keyString) {
|
|
||||||
return utils.parseKeyString(keyString);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given any number of identifiers, will return true if they are all equal, otherwise false.
|
* Given any number of identifiers, will return true if they are all equal, otherwise false.
|
||||||
* @param {module:openmct.ObjectAPI~Identifier[]} identifiers
|
* @param {module:openmct.ObjectAPI~Identifier[]} identifiers
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.areIdsEqual = function (...identifiers) {
|
ObjectAPI.prototype.areIdsEqual = function (...identifiers) {
|
||||||
return identifiers.map(utils.parseKeyString)
|
return identifiers.map(utils.parseKeyString)
|
||||||
.every(identifier => {
|
.every(identifier => {
|
||||||
return identifier === identifiers[0]
|
return identifier === identifiers[0]
|
||||||
|| (identifier.namespace === identifiers[0].namespace
|
|| (identifier.namespace === identifiers[0].namespace
|
||||||
&& identifier.key === identifiers[0].key);
|
&& identifier.key === identifiers[0].key);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) {
|
ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) {
|
||||||
return this.get(identifier).then((domainObject) => {
|
return this.get(identifier).then((domainObject) => {
|
||||||
path.push(domainObject);
|
path.push(domainObject);
|
||||||
let location = domainObject.location;
|
let location = domainObject.location;
|
||||||
@ -464,9 +317,30 @@ ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Register an object interceptor that transforms a domain object requested via module:openmct.ObjectAPI.get
|
||||||
|
* The domain object will be transformed after it is retrieved from the persistence store
|
||||||
|
* The domain object will be transformed only if the interceptor is applicable to that domain object as defined by the InterceptorDef
|
||||||
|
*
|
||||||
|
* @param {module:openmct.InterceptorDef} interceptorDef the interceptor definition to add
|
||||||
|
* @method addGetInterceptor
|
||||||
|
* @memberof module:openmct.InterceptorRegistry#
|
||||||
|
*/
|
||||||
|
ObjectAPI.prototype.addGetInterceptor = function (interceptorDef) {
|
||||||
|
this.interceptorRegistry.addInterceptor(interceptorDef);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the interceptors for a given domain object.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ObjectAPI.prototype.listGetInterceptors = function (identifier, object) {
|
||||||
|
return this.interceptorRegistry.getInterceptors(identifier, object);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
* Uniquely identifies a domain object.
|
* Uniquely identifies a domain object.
|
||||||
*
|
*
|
||||||
* @typedef Identifier
|
* @typedef Identifier
|
||||||
@ -477,7 +351,7 @@ ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) {
|
|||||||
* within that namespace
|
* within that namespace
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A domain object is an entity of relevance to a user's workflow, that
|
* A domain object is an entity of relevance to a user's workflow, that
|
||||||
* should appear as a distinct and meaningful object within the user
|
* should appear as a distinct and meaningful object within the user
|
||||||
* interface. Examples of domain objects are folders, telemetry sensors,
|
* interface. Examples of domain objects are folders, telemetry sensors,
|
||||||
@ -501,9 +375,10 @@ ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) {
|
|||||||
* @memberof module:openmct
|
* @memberof module:openmct
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function hasAlreadyBeenPersisted(domainObject) {
|
function hasAlreadyBeenPersisted(domainObject) {
|
||||||
return domainObject.persisted !== undefined
|
return domainObject.persisted !== undefined
|
||||||
&& domainObject.persisted === domainObject.modified;
|
&& domainObject.persisted === domainObject.modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectAPI;
|
return ObjectAPI;
|
||||||
|
});
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
import ObjectAPI from './ObjectAPI.js';
|
|
||||||
|
|
||||||
describe("The Object API Search Function", () => {
|
|
||||||
const MOCK_PROVIDER_KEY = 'mockProvider';
|
|
||||||
const ANOTHER_MOCK_PROVIDER_KEY = 'anotherMockProvider';
|
|
||||||
const MOCK_PROVIDER_SEARCH_DELAY = 15000;
|
|
||||||
const ANOTHER_MOCK_PROVIDER_SEARCH_DELAY = 20000;
|
|
||||||
const TOTAL_TIME_ELAPSED = 21000;
|
|
||||||
const BASE_TIME = new Date(2021, 0, 1);
|
|
||||||
|
|
||||||
let objectAPI;
|
|
||||||
let mockObjectProvider;
|
|
||||||
let anotherMockObjectProvider;
|
|
||||||
let mockFallbackProvider;
|
|
||||||
let fallbackProviderSearchResults;
|
|
||||||
let resultsPromises;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jasmine.clock().install();
|
|
||||||
jasmine.clock().mockDate(BASE_TIME);
|
|
||||||
|
|
||||||
resultsPromises = [];
|
|
||||||
fallbackProviderSearchResults = {
|
|
||||||
hits: []
|
|
||||||
};
|
|
||||||
|
|
||||||
objectAPI = new ObjectAPI();
|
|
||||||
|
|
||||||
mockObjectProvider = jasmine.createSpyObj("mock object provider", [
|
|
||||||
"search"
|
|
||||||
]);
|
|
||||||
anotherMockObjectProvider = jasmine.createSpyObj("another mock object provider", [
|
|
||||||
"search"
|
|
||||||
]);
|
|
||||||
mockFallbackProvider = jasmine.createSpyObj("super secret fallback provider", [
|
|
||||||
"superSecretFallbackSearch"
|
|
||||||
]);
|
|
||||||
objectAPI.addProvider('objects', mockObjectProvider);
|
|
||||||
objectAPI.addProvider('other-objects', anotherMockObjectProvider);
|
|
||||||
objectAPI.supersecretSetFallbackProvider(mockFallbackProvider);
|
|
||||||
|
|
||||||
mockObjectProvider.search.and.callFake(() => {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const mockProviderSearch = {
|
|
||||||
name: MOCK_PROVIDER_KEY,
|
|
||||||
start: new Date()
|
|
||||||
};
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
mockProviderSearch.end = new Date();
|
|
||||||
|
|
||||||
return resolve(mockProviderSearch);
|
|
||||||
}, MOCK_PROVIDER_SEARCH_DELAY);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
anotherMockObjectProvider.search.and.callFake(() => {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const anotherMockProviderSearch = {
|
|
||||||
name: ANOTHER_MOCK_PROVIDER_KEY,
|
|
||||||
start: new Date()
|
|
||||||
};
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
anotherMockProviderSearch.end = new Date();
|
|
||||||
|
|
||||||
return resolve(anotherMockProviderSearch);
|
|
||||||
}, ANOTHER_MOCK_PROVIDER_SEARCH_DELAY);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
mockFallbackProvider.superSecretFallbackSearch.and.callFake(
|
|
||||||
() => new Promise(
|
|
||||||
resolve => setTimeout(
|
|
||||||
() => resolve(fallbackProviderSearchResults),
|
|
||||||
50
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
resultsPromises = objectAPI.search('foo');
|
|
||||||
|
|
||||||
jasmine.clock().tick(TOTAL_TIME_ELAPSED);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jasmine.clock().uninstall();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses each objects given provider's search function", () => {
|
|
||||||
expect(mockObjectProvider.search).toHaveBeenCalled();
|
|
||||||
expect(anotherMockObjectProvider.search).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses the fallback indexed search for objects without a search function provided", () => {
|
|
||||||
expect(mockFallbackProvider.superSecretFallbackSearch).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("provides each providers results as promises that resolve in parallel", async () => {
|
|
||||||
const results = await Promise.all(resultsPromises);
|
|
||||||
const mockProviderResults = results.find(
|
|
||||||
result => result.name === MOCK_PROVIDER_KEY
|
|
||||||
);
|
|
||||||
const anotherMockProviderResults = results.find(
|
|
||||||
result => result.name === ANOTHER_MOCK_PROVIDER_KEY
|
|
||||||
);
|
|
||||||
const mockProviderStart = mockProviderResults.start.getTime();
|
|
||||||
const mockProviderEnd = mockProviderResults.end.getTime();
|
|
||||||
const anotherMockProviderStart = anotherMockProviderResults.start.getTime();
|
|
||||||
const anotherMockProviderEnd = anotherMockProviderResults.end.getTime();
|
|
||||||
const searchElapsedTime = Math.max(mockProviderEnd, anotherMockProviderEnd)
|
|
||||||
- Math.min(mockProviderEnd, anotherMockProviderEnd);
|
|
||||||
|
|
||||||
expect(mockProviderStart).toBeLessThan(anotherMockProviderEnd);
|
|
||||||
expect(anotherMockProviderStart).toBeLessThan(mockProviderEnd);
|
|
||||||
expect(searchElapsedTime).toBeLessThan(
|
|
||||||
MOCK_PROVIDER_SEARCH_DELAY
|
|
||||||
+ ANOTHER_MOCK_PROVIDER_SEARCH_DELAY
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@ -2,30 +2,12 @@ import ObjectAPI from './ObjectAPI.js';
|
|||||||
|
|
||||||
describe("The Object API", () => {
|
describe("The Object API", () => {
|
||||||
let objectAPI;
|
let objectAPI;
|
||||||
let typeRegistry;
|
|
||||||
let openmct = {};
|
|
||||||
let mockIdentifierService;
|
|
||||||
let mockDomainObject;
|
let mockDomainObject;
|
||||||
const TEST_NAMESPACE = "test-namespace";
|
const TEST_NAMESPACE = "test-namespace";
|
||||||
const FIFTEEN_MINUTES = 15 * 60 * 1000;
|
const FIFTEEN_MINUTES = 15 * 60 * 1000;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
typeRegistry = jasmine.createSpyObj('typeRegistry', [
|
objectAPI = new ObjectAPI();
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
|
||||||
mockIdentifierService = jasmine.createSpyObj(
|
|
||||||
'identifierService',
|
|
||||||
['parse']
|
|
||||||
);
|
|
||||||
mockIdentifierService.parse.and.returnValue({
|
|
||||||
getSpace: () => {
|
|
||||||
return TEST_NAMESPACE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.$injector.get.and.returnValue(mockIdentifierService);
|
|
||||||
objectAPI = new ObjectAPI(typeRegistry, openmct);
|
|
||||||
mockDomainObject = {
|
mockDomainObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
namespace: TEST_NAMESPACE,
|
namespace: TEST_NAMESPACE,
|
||||||
@ -51,7 +33,6 @@ describe("The Object API", () => {
|
|||||||
"update"
|
"update"
|
||||||
]);
|
]);
|
||||||
mockProvider.create.and.returnValue(Promise.resolve(true));
|
mockProvider.create.and.returnValue(Promise.resolve(true));
|
||||||
mockProvider.update.and.returnValue(Promise.resolve(true));
|
|
||||||
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
|
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
|
||||||
});
|
});
|
||||||
it("Calls 'create' on provider if object is new", () => {
|
it("Calls 'create' on provider if object is new", () => {
|
||||||
@ -147,155 +128,4 @@ describe("The Object API", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("the mutation API", () => {
|
|
||||||
let testObject;
|
|
||||||
let updatedTestObject;
|
|
||||||
let mutable;
|
|
||||||
let mockProvider;
|
|
||||||
let callbacks = [];
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
objectAPI = new ObjectAPI(typeRegistry, openmct);
|
|
||||||
testObject = {
|
|
||||||
identifier: {
|
|
||||||
namespace: TEST_NAMESPACE,
|
|
||||||
key: 'test-key'
|
|
||||||
},
|
|
||||||
name: 'test object',
|
|
||||||
otherAttribute: 'other-attribute-value',
|
|
||||||
objectAttribute: {
|
|
||||||
embeddedObject: {
|
|
||||||
embeddedKey: 'embedded-value'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
updatedTestObject = Object.assign({otherAttribute: 'changed-attribute-value'}, testObject);
|
|
||||||
mockProvider = jasmine.createSpyObj("mock provider", [
|
|
||||||
"get",
|
|
||||||
"create",
|
|
||||||
"update",
|
|
||||||
"observe",
|
|
||||||
"observeObjectChanges"
|
|
||||||
]);
|
|
||||||
mockProvider.get.and.returnValue(Promise.resolve(testObject));
|
|
||||||
mockProvider.observeObjectChanges.and.callFake(() => {
|
|
||||||
callbacks[0](updatedTestObject);
|
|
||||||
callbacks.splice(0, 1);
|
|
||||||
});
|
|
||||||
mockProvider.observe.and.callFake((id, callback) => {
|
|
||||||
if (callbacks.length === 0) {
|
|
||||||
callbacks.push(callback);
|
|
||||||
} else {
|
|
||||||
callbacks[0] = callback;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
|
|
||||||
|
|
||||||
return objectAPI.getMutable(testObject.identifier)
|
|
||||||
.then(object => {
|
|
||||||
mutable = object;
|
|
||||||
|
|
||||||
return mutable;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mutable.$destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('mutates the original object', () => {
|
|
||||||
const MUTATED_NAME = 'mutated name';
|
|
||||||
objectAPI.mutate(testObject, 'name', MUTATED_NAME);
|
|
||||||
expect(testObject.name).toBe(MUTATED_NAME);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe ('uses a MutableDomainObject', () => {
|
|
||||||
it('and retains properties of original object ', function () {
|
|
||||||
expect(hasOwnProperty(mutable, 'identifier')).toBe(true);
|
|
||||||
expect(hasOwnProperty(mutable, 'otherAttribute')).toBe(true);
|
|
||||||
expect(mutable.identifier).toEqual(testObject.identifier);
|
|
||||||
expect(mutable.otherAttribute).toEqual(testObject.otherAttribute);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('that is identical to original object when serialized', function () {
|
|
||||||
expect(JSON.stringify(mutable)).toEqual(JSON.stringify(testObject));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('that observes for object changes', function () {
|
|
||||||
let mockListener = jasmine.createSpy('mockListener');
|
|
||||||
objectAPI.observe(testObject, '*', mockListener);
|
|
||||||
mockProvider.observeObjectChanges();
|
|
||||||
expect(mockListener).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('uses events', function () {
|
|
||||||
let testObjectDuplicate;
|
|
||||||
let mutableSecondInstance;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
// Duplicate object to guarantee we are not sharing object instance, which would invalidate test
|
|
||||||
testObjectDuplicate = JSON.parse(JSON.stringify(testObject));
|
|
||||||
mutableSecondInstance = objectAPI._toMutable(testObjectDuplicate);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mutableSecondInstance.$destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('to stay synchronized when mutated', function () {
|
|
||||||
objectAPI.mutate(mutable, 'otherAttribute', 'new-attribute-value');
|
|
||||||
expect(mutableSecondInstance.otherAttribute).toBe('new-attribute-value');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('to indicate when a property changes', function () {
|
|
||||||
let mutationCallback = jasmine.createSpy('mutation-callback');
|
|
||||||
let unlisten;
|
|
||||||
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
mutationCallback.and.callFake(resolve);
|
|
||||||
unlisten = objectAPI.observe(mutableSecondInstance, 'otherAttribute', mutationCallback);
|
|
||||||
objectAPI.mutate(mutable, 'otherAttribute', 'some-new-value');
|
|
||||||
}).then(function () {
|
|
||||||
expect(mutationCallback).toHaveBeenCalledWith('some-new-value');
|
|
||||||
unlisten();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('to indicate when a child property has changed', function () {
|
|
||||||
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
|
|
||||||
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
|
|
||||||
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
|
|
||||||
let listeners = [];
|
|
||||||
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
objectAttributeCallback.and.callFake(resolve);
|
|
||||||
|
|
||||||
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback));
|
|
||||||
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject', embeddedObjectCallback));
|
|
||||||
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute', objectAttributeCallback));
|
|
||||||
|
|
||||||
objectAPI.mutate(mutable, 'objectAttribute.embeddedObject.embeddedKey', 'updated-embedded-value');
|
|
||||||
}).then(function () {
|
|
||||||
expect(embeddedKeyCallback).toHaveBeenCalledWith('updated-embedded-value');
|
|
||||||
expect(embeddedObjectCallback).toHaveBeenCalledWith({
|
|
||||||
embeddedKey: 'updated-embedded-value'
|
|
||||||
});
|
|
||||||
expect(objectAttributeCallback).toHaveBeenCalledWith({
|
|
||||||
embeddedObject: {
|
|
||||||
embeddedKey: 'updated-embedded-value'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
listeners.forEach(listener => listener());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function hasOwnProperty(object, property) {
|
|
||||||
return Object.prototype.hasOwnProperty.call(object, property);
|
|
||||||
}
|
|
||||||
|
@ -48,7 +48,7 @@ define([
|
|||||||
this.providers.push(function () {
|
this.providers.push(function () {
|
||||||
return key;
|
return key;
|
||||||
});
|
});
|
||||||
} else if (typeof key === "function") {
|
} else if (_.isFunction(key)) {
|
||||||
this.providers.push(key);
|
this.providers.push(key);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -20,12 +20,13 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import MCTChartSeriesElement from './MCTChartSeriesElement';
|
define([
|
||||||
|
"EventEmitter"
|
||||||
export default class MCTChartLineLinear extends MCTChartSeriesElement {
|
], function (
|
||||||
addPoint(point, start, count) {
|
EventEmitter
|
||||||
this.buffer[start] = point.x;
|
) {
|
||||||
this.buffer[start + 1] = point.y;
|
/**
|
||||||
}
|
* Provides a singleton event bus for sharing between objects.
|
||||||
}
|
*/
|
||||||
|
return new EventEmitter();
|
||||||
|
});
|
@ -6,9 +6,6 @@ class Dialog extends Overlay {
|
|||||||
constructor({iconClass, message, title, hint, timestamp, ...options}) {
|
constructor({iconClass, message, title, hint, timestamp, ...options}) {
|
||||||
|
|
||||||
let component = new Vue({
|
let component = new Vue({
|
||||||
components: {
|
|
||||||
DialogComponent: DialogComponent
|
|
||||||
},
|
|
||||||
provide: {
|
provide: {
|
||||||
iconClass,
|
iconClass,
|
||||||
message,
|
message,
|
||||||
@ -16,6 +13,9 @@ class Dialog extends Overlay {
|
|||||||
hint,
|
hint,
|
||||||
timestamp
|
timestamp
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
DialogComponent: DialogComponent
|
||||||
|
},
|
||||||
template: '<dialog-component></dialog-component>'
|
template: '<dialog-component></dialog-component>'
|
||||||
}).$mount();
|
}).$mount();
|
||||||
|
|
||||||
|
@ -7,9 +7,6 @@ let component;
|
|||||||
class ProgressDialog extends Overlay {
|
class ProgressDialog extends Overlay {
|
||||||
constructor({progressPerc, progressText, iconClass, message, title, hint, timestamp, ...options}) {
|
constructor({progressPerc, progressText, iconClass, message, title, hint, timestamp, ...options}) {
|
||||||
component = new Vue({
|
component = new Vue({
|
||||||
components: {
|
|
||||||
ProgressDialogComponent: ProgressDialogComponent
|
|
||||||
},
|
|
||||||
provide: {
|
provide: {
|
||||||
iconClass,
|
iconClass,
|
||||||
message,
|
message,
|
||||||
@ -17,6 +14,9 @@ class ProgressDialog extends Overlay {
|
|||||||
hint,
|
hint,
|
||||||
timestamp
|
timestamp
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
ProgressDialogComponent: ProgressDialogComponent
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
model: {
|
model: {
|
||||||
|
@ -38,12 +38,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
inject: ['dismiss', 'element', 'buttons', 'dismissable'],
|
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
focusIndex: -1
|
focusIndex: -1
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
inject: ['dismiss', 'element', 'buttons', 'dismissable'],
|
||||||
mounted() {
|
mounted() {
|
||||||
const element = this.$refs.element;
|
const element = this.$refs.element;
|
||||||
element.appendChild(this.element);
|
element.appendChild(this.element);
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
export default function (couchPlugin, searchFilter) {
|
|
||||||
return function install(openmct) {
|
|
||||||
const couchProvider = couchPlugin.couchProvider;
|
|
||||||
|
|
||||||
openmct.objects.addRoot({
|
|
||||||
namespace: 'couch-search',
|
|
||||||
key: 'couch-search'
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.objects.addProvider('couch-search', {
|
|
||||||
get(identifier) {
|
|
||||||
if (identifier.key !== 'couch-search') {
|
|
||||||
return undefined;
|
|
||||||
} else {
|
|
||||||
return Promise.resolve({
|
|
||||||
identifier,
|
|
||||||
type: 'folder',
|
|
||||||
name: "CouchDB Documents"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.composition.addProvider({
|
|
||||||
appliesTo(domainObject) {
|
|
||||||
return domainObject.identifier.namespace === 'couch-search'
|
|
||||||
&& domainObject.identifier.key === 'couch-search';
|
|
||||||
},
|
|
||||||
load() {
|
|
||||||
return couchProvider.getObjectsByFilter(searchFilter).then(objects => {
|
|
||||||
return objects.map(object => object.identifier);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 {createOpenMct, resetApplicationState} from "utils/testing";
|
|
||||||
import CouchDBSearchFolderPlugin from './plugin';
|
|
||||||
|
|
||||||
describe('the plugin', function () {
|
|
||||||
let identifier = {
|
|
||||||
namespace: 'couch-search',
|
|
||||||
key: "couch-search"
|
|
||||||
};
|
|
||||||
let testPath = '/test/db';
|
|
||||||
let openmct;
|
|
||||||
let composition;
|
|
||||||
|
|
||||||
beforeEach((done) => {
|
|
||||||
|
|
||||||
openmct = createOpenMct();
|
|
||||||
|
|
||||||
let couchPlugin = openmct.plugins.CouchDB(testPath);
|
|
||||||
openmct.install(couchPlugin);
|
|
||||||
|
|
||||||
openmct.install(new CouchDBSearchFolderPlugin(couchPlugin, {
|
|
||||||
"selector": {
|
|
||||||
"model": {
|
|
||||||
"type": "plan"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
|
||||||
openmct.startHeadless();
|
|
||||||
|
|
||||||
composition = openmct.composition.get({identifier});
|
|
||||||
|
|
||||||
spyOn(couchPlugin.couchProvider, 'getObjectsByFilter').and.returnValue(Promise.resolve([
|
|
||||||
{
|
|
||||||
identifier: {
|
|
||||||
key: "1",
|
|
||||||
namespace: "mct"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
identifier: {
|
|
||||||
key: "2",
|
|
||||||
namespace: "mct"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('provides a folder to hold plans', () => {
|
|
||||||
openmct.objects.get(identifier).then((object) => {
|
|
||||||
expect(object).toEqual({
|
|
||||||
identifier,
|
|
||||||
type: 'folder',
|
|
||||||
name: "CouchDB Documents"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('provides composition for couch search folders', () => {
|
|
||||||
composition.load().then((objects) => {
|
|
||||||
expect(objects.length).toEqual(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -44,15 +44,11 @@ export default function LADTableViewProvider(openmct) {
|
|||||||
LadTableComponent: LadTable
|
LadTableComponent: LadTable
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct
|
openmct,
|
||||||
},
|
|
||||||
data: () => {
|
|
||||||
return {
|
|
||||||
domainObject,
|
domainObject,
|
||||||
objectPath
|
objectPath
|
||||||
};
|
|
||||||
},
|
},
|
||||||
template: '<lad-table-component :domain-object="domainObject" :object-path="objectPath"></lad-table-component>'
|
template: '<lad-table-component></lad-table-component>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
destroy: function (element) {
|
destroy: function (element) {
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
class="js-lad-table__body__row"
|
class="js-lad-table__body__row"
|
||||||
@contextmenu.prevent="showContextMenu"
|
@contextmenu.prevent="showContextMenu"
|
||||||
>
|
>
|
||||||
<td class="js-first-data">{{ domainObject.name }}</td>
|
<td class="js-first-data">{{ name }}</td>
|
||||||
<td class="js-second-data">{{ formattedTimestamp }}</td>
|
<td class="js-second-data">{{ formattedTimestamp }}</td>
|
||||||
<td
|
<td
|
||||||
class="js-third-data"
|
class="js-third-data"
|
||||||
@ -50,16 +50,12 @@ const CONTEXT_MENU_ACTIONS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct', 'objectPath'],
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
objectPath: {
|
|
||||||
type: Array,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
hasUnits: {
|
hasUnits: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
requred: true
|
requred: true
|
||||||
@ -70,6 +66,7 @@ export default {
|
|||||||
currentObjectPath.unshift(this.domainObject);
|
currentObjectPath.unshift(this.domainObject);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
name: this.domainObject.name,
|
||||||
timestamp: undefined,
|
timestamp: undefined,
|
||||||
value: '---',
|
value: '---',
|
||||||
valueClass: '',
|
valueClass: '',
|
||||||
@ -92,6 +89,14 @@ export default {
|
|||||||
.telemetry
|
.telemetry
|
||||||
.limitEvaluator(this.domainObject);
|
.limitEvaluator(this.domainObject);
|
||||||
|
|
||||||
|
this.stopWatchingMutation = this.openmct
|
||||||
|
.objects
|
||||||
|
.observe(
|
||||||
|
this.domainObject,
|
||||||
|
'*',
|
||||||
|
this.updateName
|
||||||
|
);
|
||||||
|
|
||||||
this.openmct.time.on('timeSystem', this.updateTimeSystem);
|
this.openmct.time.on('timeSystem', this.updateTimeSystem);
|
||||||
this.openmct.time.on('bounds', this.updateBounds);
|
this.openmct.time.on('bounds', this.updateBounds);
|
||||||
|
|
||||||
@ -114,6 +119,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
|
this.stopWatchingMutation();
|
||||||
this.unsubscribe();
|
this.unsubscribe();
|
||||||
this.openmct.time.off('timeSystem', this.updateTimeSystem);
|
this.openmct.time.off('timeSystem', this.updateTimeSystem);
|
||||||
this.openmct.time.off('bounds', this.updateBounds);
|
this.openmct.time.off('bounds', this.updateBounds);
|
||||||
@ -154,6 +160,9 @@ export default {
|
|||||||
})
|
})
|
||||||
.then((array) => this.updateValues(array[array.length - 1]));
|
.then((array) => this.updateValues(array[array.length - 1]));
|
||||||
},
|
},
|
||||||
|
updateName(name) {
|
||||||
|
this.name = name;
|
||||||
|
},
|
||||||
updateBounds(bounds, isTick) {
|
updateBounds(bounds, isTick) {
|
||||||
this.bounds = bounds;
|
this.bounds = bounds;
|
||||||
if (!isTick) {
|
if (!isTick) {
|
||||||
|
@ -36,7 +36,6 @@
|
|||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
:domain-object="item.domainObject"
|
:domain-object="item.domainObject"
|
||||||
:object-path="objectPath"
|
|
||||||
:has-units="hasUnits"
|
:has-units="hasUnits"
|
||||||
/>
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -48,20 +47,10 @@
|
|||||||
import LadRow from './LADRow.vue';
|
import LadRow from './LADRow.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||||
components: {
|
components: {
|
||||||
LadRow
|
LadRow
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
|
||||||
domainObject: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
objectPath: {
|
|
||||||
type: Array,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
items: []
|
items: []
|
||||||
|
@ -57,10 +57,10 @@
|
|||||||
import LadRow from './LADRow.vue';
|
import LadRow from './LADRow.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
components: {
|
components: {
|
||||||
LadRow
|
LadRow
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ladTableObjects: [],
|
ladTableObjects: [],
|
||||||
|
@ -37,12 +37,12 @@ define([
|
|||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
if (installIndicator) {
|
if (installIndicator) {
|
||||||
let component = new Vue ({
|
let component = new Vue ({
|
||||||
components: {
|
|
||||||
GlobalClearIndicator: GlobaClearIndicator.default
|
|
||||||
},
|
|
||||||
provide: {
|
provide: {
|
||||||
openmct
|
openmct
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
GlobalClearIndicator: GlobaClearIndicator.default
|
||||||
|
},
|
||||||
template: '<GlobalClearIndicator></GlobalClearIndicator>'
|
template: '<GlobalClearIndicator></GlobalClearIndicator>'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,9 +34,6 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
this.composition = this.openmct.composition.get(conditionSetDomainObject);
|
this.composition = this.openmct.composition.get(conditionSetDomainObject);
|
||||||
this.composition.on('add', this.subscribeToTelemetry, this);
|
this.composition.on('add', this.subscribeToTelemetry, this);
|
||||||
this.composition.on('remove', this.unsubscribeFromTelemetry, this);
|
this.composition.on('remove', this.unsubscribeFromTelemetry, this);
|
||||||
|
|
||||||
this.shouldEvaluateNewTelemetry = this.shouldEvaluateNewTelemetry.bind(this);
|
|
||||||
|
|
||||||
this.compositionLoad = this.composition.load();
|
this.compositionLoad = this.composition.load();
|
||||||
this.subscriptions = {};
|
this.subscriptions = {};
|
||||||
this.telemetryObjects = {};
|
this.telemetryObjects = {};
|
||||||
@ -340,10 +337,6 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldEvaluateNewTelemetry(currentTimestamp) {
|
|
||||||
return this.openmct.time.bounds().end >= currentTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
telemetryReceived(endpoint, datum) {
|
telemetryReceived(endpoint, datum) {
|
||||||
if (!this.isTelemetryUsed(endpoint)) {
|
if (!this.isTelemetryUsed(endpoint)) {
|
||||||
return;
|
return;
|
||||||
@ -352,13 +345,11 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
|
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
|
||||||
const timeSystemKey = this.openmct.time.timeSystem().key;
|
const timeSystemKey = this.openmct.time.timeSystem().key;
|
||||||
let timestamp = {};
|
let timestamp = {};
|
||||||
const currentTimestamp = normalizedDatum[timeSystemKey];
|
timestamp[timeSystemKey] = normalizedDatum[timeSystemKey];
|
||||||
timestamp[timeSystemKey] = currentTimestamp;
|
|
||||||
if (this.shouldEvaluateNewTelemetry(currentTimestamp)) {
|
|
||||||
this.updateConditionResults(normalizedDatum);
|
this.updateConditionResults(normalizedDatum);
|
||||||
this.updateCurrentCondition(timestamp);
|
this.updateCurrentCondition(timestamp);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
updateConditionResults(normalizedDatum) {
|
updateConditionResults(normalizedDatum) {
|
||||||
//We want to stop when the first condition evaluates to true.
|
//We want to stop when the first condition evaluates to true.
|
||||||
|
@ -195,11 +195,11 @@ import { TRIGGER, TRIGGER_LABEL } from "@/plugins/condition/utils/constants";
|
|||||||
import uuid from 'uuid';
|
import uuid from 'uuid';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
Criterion,
|
Criterion,
|
||||||
ConditionDescription
|
ConditionDescription
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
currentConditionId: {
|
currentConditionId: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -81,10 +81,10 @@ import Condition from './Condition.vue';
|
|||||||
import ConditionManager from '../ConditionManager';
|
import ConditionManager from '../ConditionManager';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
components: {
|
components: {
|
||||||
Condition
|
Condition
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
props: {
|
props: {
|
||||||
isEditing: Boolean,
|
isEditing: Boolean,
|
||||||
testData: {
|
testData: {
|
||||||
|
@ -58,11 +58,11 @@ import TestData from './TestData.vue';
|
|||||||
import ConditionCollection from './ConditionCollection.vue';
|
import ConditionCollection from './ConditionCollection.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ["openmct", "domainObject"],
|
||||||
components: {
|
components: {
|
||||||
TestData,
|
TestData,
|
||||||
ConditionCollection
|
ConditionCollection
|
||||||
},
|
},
|
||||||
inject: ["openmct", "domainObject"],
|
|
||||||
props: {
|
props: {
|
||||||
isEditing: Boolean
|
isEditing: Boolean
|
||||||
},
|
},
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
v-model="expanded"
|
v-model="expanded"
|
||||||
class="c-tree__item__view-control"
|
class="c-tree__item__view-control"
|
||||||
:enabled="hasChildren"
|
:enabled="hasChildren"
|
||||||
|
:propagate="false"
|
||||||
/>
|
/>
|
||||||
<div class="c-tree__item__label c-object-label">
|
<div class="c-tree__item__label c-object-label">
|
||||||
<div
|
<div
|
||||||
@ -41,7 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
v-if="expanded && !isLoading"
|
v-if="expanded"
|
||||||
class="c-tree"
|
class="c-tree"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
@ -68,10 +69,10 @@ import viewControl from '@/ui/components/viewControl.vue';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ConditionSetDialogTreeItem',
|
name: 'ConditionSetDialogTreeItem',
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
viewControl
|
viewControl
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
node: {
|
node: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
></div>
|
></div>
|
||||||
<!-- end loading -->
|
<!-- end loading -->
|
||||||
|
|
||||||
<div v-if="shouldDisplayNoResultsText"
|
<div v-if="(allTreeItems.length === 0) || (searchValue && filteredTreeItems.length === 0)"
|
||||||
class="c-tree-and-search__no-results"
|
class="c-tree-and-search__no-results"
|
||||||
>
|
>
|
||||||
No results found
|
No results found
|
||||||
@ -63,7 +63,7 @@
|
|||||||
<!-- end main tree -->
|
<!-- end main tree -->
|
||||||
|
|
||||||
<!-- search tree -->
|
<!-- search tree -->
|
||||||
<ul v-if="searchValue && !isLoading"
|
<ul v-if="searchValue"
|
||||||
class="c-tree-and-search__tree c-tree"
|
class="c-tree-and-search__tree c-tree"
|
||||||
>
|
>
|
||||||
<condition-set-dialog-tree-item
|
<condition-set-dialog-tree-item
|
||||||
@ -80,17 +80,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import debounce from 'lodash/debounce';
|
|
||||||
import search from '@/ui/components/search.vue';
|
import search from '@/ui/components/search.vue';
|
||||||
import ConditionSetDialogTreeItem from './ConditionSetDialogTreeItem.vue';
|
import ConditionSetDialogTreeItem from './ConditionSetDialogTreeItem.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
name: 'ConditionSetSelectorDialog',
|
name: 'ConditionSetSelectorDialog',
|
||||||
components: {
|
components: {
|
||||||
search,
|
search,
|
||||||
ConditionSetDialogTreeItem
|
ConditionSetDialogTreeItem
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
@ -101,20 +100,8 @@ export default {
|
|||||||
selectedItem: undefined
|
selectedItem: undefined
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
shouldDisplayNoResultsText() {
|
|
||||||
if (this.isLoading) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.allTreeItems.length === 0
|
|
||||||
|| (this.searchValue && this.filteredTreeItems.length === 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getDebouncedFilteredChildren = debounce(this.getFilteredChildren, 400);
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.searchService = this.openmct.$injector.get('searchService');
|
||||||
this.getAllChildren();
|
this.getAllChildren();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -137,44 +124,37 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
getFilteredChildren() {
|
getFilteredChildren() {
|
||||||
// clear any previous search results
|
this.searchService.query(this.searchValue).then(children => {
|
||||||
this.filteredTreeItems = [];
|
this.filteredTreeItems = children.hits.map(child => {
|
||||||
|
|
||||||
const promises = this.openmct.objects.search(this.searchValue)
|
let context = child.object.getCapability('context');
|
||||||
.map(promise => promise
|
let object = child.object.useCapability('adapter');
|
||||||
.then(results => this.aggregateFilteredChildren(results)));
|
let objectPath = [];
|
||||||
|
let navigateToParent;
|
||||||
|
|
||||||
Promise.all(promises).then(() => {
|
if (context) {
|
||||||
this.isLoading = false;
|
objectPath = context.getPath().slice(1)
|
||||||
});
|
.map(oldObject => oldObject.useCapability('adapter'))
|
||||||
},
|
.reverse();
|
||||||
async aggregateFilteredChildren(results) {
|
navigateToParent = '/browse/' + objectPath.slice(1)
|
||||||
for (const object of results) {
|
.map((parent) => this.openmct.objects.makeKeyString(parent.identifier))
|
||||||
const objectPath = await this.openmct.objects.getOriginalPath(object.identifier);
|
|
||||||
|
|
||||||
const navigateToParent = '/browse/'
|
|
||||||
+ objectPath.slice(1)
|
|
||||||
.map(parent => this.openmct.objects.makeKeyString(parent.identifier))
|
|
||||||
.join('/');
|
.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
const filteredChild = {
|
return {
|
||||||
id: this.openmct.objects.makeKeyString(object.identifier),
|
id: this.openmct.objects.makeKeyString(object.identifier),
|
||||||
object,
|
object,
|
||||||
objectPath,
|
objectPath,
|
||||||
navigateToParent
|
navigateToParent
|
||||||
};
|
};
|
||||||
|
});
|
||||||
this.filteredTreeItems.push(filteredChild);
|
});
|
||||||
}
|
|
||||||
},
|
},
|
||||||
searchTree(value) {
|
searchTree(value) {
|
||||||
this.searchValue = value;
|
this.searchValue = value;
|
||||||
this.isLoading = true;
|
|
||||||
|
|
||||||
if (this.searchValue !== '') {
|
if (this.searchValue !== '') {
|
||||||
this.getDebouncedFilteredChildren();
|
this.getFilteredChildren();
|
||||||
} else {
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleItemSelection(item, node) {
|
handleItemSelection(item, node) {
|
||||||
|
@ -401,15 +401,15 @@ describe('the plugin', function () {
|
|||||||
let viewContainer = document.createElement('div');
|
let viewContainer = document.createElement('div');
|
||||||
child.append(viewContainer);
|
child.append(viewContainer);
|
||||||
component = new Vue({
|
component = new Vue({
|
||||||
el: viewContainer,
|
|
||||||
components: {
|
|
||||||
StylesView
|
|
||||||
},
|
|
||||||
provide: {
|
provide: {
|
||||||
openmct: openmct,
|
openmct: openmct,
|
||||||
selection: selection,
|
selection: selection,
|
||||||
stylesManager
|
stylesManager
|
||||||
},
|
},
|
||||||
|
el: viewContainer,
|
||||||
|
components: {
|
||||||
|
StylesView
|
||||||
|
},
|
||||||
template: '<styles-view/>'
|
template: '<styles-view/>'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -543,6 +543,7 @@ describe('the plugin', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should evaluate as stale when telemetry is not received in the allotted time', (done) => {
|
it('should evaluate as stale when telemetry is not received in the allotted time', (done) => {
|
||||||
|
|
||||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||||
conditionMgr.telemetryObjects = {
|
conditionMgr.telemetryObjects = {
|
||||||
@ -564,7 +565,7 @@ describe('the plugin', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not evaluate as stale when telemetry is received in the allotted time', (done) => {
|
it('should not evaluate as stale when telemetry is received in the allotted time', (done) => {
|
||||||
const date = 1;
|
const date = Date.now();
|
||||||
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input = ["0.4"];
|
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input = ["0.4"];
|
||||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||||
|
@ -56,14 +56,14 @@ define([
|
|||||||
return {
|
return {
|
||||||
show: function (element) {
|
show: function (element) {
|
||||||
component = new Vue({
|
component = new Vue({
|
||||||
el: element,
|
|
||||||
components: {
|
|
||||||
AlphanumericFormatView: AlphanumericFormatView.default
|
|
||||||
},
|
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
objectPath
|
objectPath
|
||||||
},
|
},
|
||||||
|
el: element,
|
||||||
|
components: {
|
||||||
|
AlphanumericFormatView: AlphanumericFormatView.default
|
||||||
|
},
|
||||||
template: '<alphanumeric-format-view ref="alphanumericFormatView"></alphanumeric-format-view>'
|
template: '<alphanumeric-format-view ref="alphanumericFormatView"></alphanumeric-format-view>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -51,11 +51,11 @@ export default {
|
|||||||
height: 5
|
height: 5
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
LayoutFrame
|
LayoutFrame
|
||||||
},
|
},
|
||||||
mixins: [conditionalStylesMixin],
|
mixins: [conditionalStylesMixin],
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -152,7 +152,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
internalDomainObject: domainObject,
|
||||||
initSelectIndex: undefined,
|
initSelectIndex: undefined,
|
||||||
selection: [],
|
selection: [],
|
||||||
showGrid: true
|
showGrid: true
|
||||||
@ -160,10 +163,10 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
gridSize() {
|
gridSize() {
|
||||||
return this.domainObject.configuration.layoutGrid;
|
return this.internalDomainObject.configuration.layoutGrid;
|
||||||
},
|
},
|
||||||
layoutItems() {
|
layoutItems() {
|
||||||
return this.domainObject.configuration.items;
|
return this.internalDomainObject.configuration.items;
|
||||||
},
|
},
|
||||||
selectedLayoutItems() {
|
selectedLayoutItems() {
|
||||||
return this.layoutItems.filter(item => {
|
return this.layoutItems.filter(item => {
|
||||||
@ -171,7 +174,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
layoutDimensions() {
|
layoutDimensions() {
|
||||||
return this.domainObject.configuration.layoutDimensions;
|
return this.internalDomainObject.configuration.layoutDimensions;
|
||||||
},
|
},
|
||||||
shouldDisplayLayoutDimensions() {
|
shouldDisplayLayoutDimensions() {
|
||||||
return this.layoutDimensions
|
return this.layoutDimensions
|
||||||
@ -203,9 +206,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', function (obj) {
|
||||||
|
this.internalDomainObject = JSON.parse(JSON.stringify(obj));
|
||||||
|
}.bind(this));
|
||||||
this.openmct.selection.on('change', this.setSelection);
|
this.openmct.selection.on('change', this.setSelection);
|
||||||
this.initializeItems();
|
this.initializeItems();
|
||||||
this.composition = this.openmct.composition.get(this.domainObject);
|
this.composition = this.openmct.composition.get(this.internalDomainObject);
|
||||||
this.composition.on('add', this.addChild);
|
this.composition.on('add', this.addChild);
|
||||||
this.composition.on('remove', this.removeChild);
|
this.composition.on('remove', this.removeChild);
|
||||||
this.composition.load();
|
this.composition.load();
|
||||||
@ -214,6 +220,7 @@ export default {
|
|||||||
this.openmct.selection.off('change', this.setSelection);
|
this.openmct.selection.off('change', this.setSelection);
|
||||||
this.composition.off('add', this.addChild);
|
this.composition.off('add', this.addChild);
|
||||||
this.composition.off('remove', this.removeChild);
|
this.composition.off('remove', this.removeChild);
|
||||||
|
this.unlisten();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addElement(itemType, element) {
|
addElement(itemType, element) {
|
||||||
@ -340,7 +347,7 @@ export default {
|
|||||||
this.startingMinY2 = undefined;
|
this.startingMinY2 = undefined;
|
||||||
},
|
},
|
||||||
mutate(path, value) {
|
mutate(path, value) {
|
||||||
this.openmct.objects.mutate(this.domainObject, path, value);
|
this.openmct.objects.mutate(this.internalDomainObject, path, value);
|
||||||
},
|
},
|
||||||
handleDrop($event) {
|
handleDrop($event) {
|
||||||
if (!$event.dataTransfer.types.includes('openmct/domain-object-path')) {
|
if (!$event.dataTransfer.types.includes('openmct/domain-object-path')) {
|
||||||
@ -380,11 +387,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
containsObject(identifier) {
|
containsObject(identifier) {
|
||||||
return _.get(this.domainObject, 'composition')
|
return _.get(this.internalDomainObject, 'composition')
|
||||||
.some(childId => this.openmct.objects.areIdsEqual(childId, identifier));
|
.some(childId => this.openmct.objects.areIdsEqual(childId, identifier));
|
||||||
},
|
},
|
||||||
handleDragOver($event) {
|
handleDragOver($event) {
|
||||||
if (this.domainObject.locked) {
|
if (this.internalDomainObject.locked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,7 +420,7 @@ export default {
|
|||||||
item.id = uuid();
|
item.id = uuid();
|
||||||
this.trackItem(item);
|
this.trackItem(item);
|
||||||
this.layoutItems.push(item);
|
this.layoutItems.push(item);
|
||||||
this.openmct.objects.mutate(this.domainObject, "configuration.items", this.layoutItems);
|
this.openmct.objects.mutate(this.internalDomainObject, "configuration.items", this.layoutItems);
|
||||||
this.initSelectIndex = this.layoutItems.length - 1;
|
this.initSelectIndex = this.layoutItems.length - 1;
|
||||||
},
|
},
|
||||||
trackItem(item) {
|
trackItem(item) {
|
||||||
@ -470,7 +477,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeFromComposition(keyString) {
|
removeFromComposition(keyString) {
|
||||||
let composition = _.get(this.domainObject, 'composition');
|
let composition = _.get(this.internalDomainObject, 'composition');
|
||||||
composition = composition.filter(identifier => {
|
composition = composition.filter(identifier => {
|
||||||
return this.openmct.objects.makeKeyString(identifier) !== keyString;
|
return this.openmct.objects.makeKeyString(identifier) !== keyString;
|
||||||
});
|
});
|
||||||
@ -622,10 +629,10 @@ export default {
|
|||||||
createNewDomainObject(domainObject, composition, viewType, nameExtension, model) {
|
createNewDomainObject(domainObject, composition, viewType, nameExtension, model) {
|
||||||
let identifier = {
|
let identifier = {
|
||||||
key: uuid(),
|
key: uuid(),
|
||||||
namespace: this.domainObject.identifier.namespace
|
namespace: this.internalDomainObject.identifier.namespace
|
||||||
};
|
};
|
||||||
let type = this.openmct.types.get(viewType);
|
let type = this.openmct.types.get(viewType);
|
||||||
let parentKeyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
let parentKeyString = this.openmct.objects.makeKeyString(this.internalDomainObject.identifier);
|
||||||
let objectName = nameExtension ? `${domainObject.name}-${nameExtension}` : domainObject.name;
|
let objectName = nameExtension ? `${domainObject.name}-${nameExtension}` : domainObject.name;
|
||||||
let object = {};
|
let object = {};
|
||||||
|
|
||||||
@ -682,7 +689,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
duplicateItem(selectedItems) {
|
duplicateItem(selectedItems) {
|
||||||
let objectStyles = this.domainObject.configuration.objectStyles || {};
|
let objectStyles = this.internalDomainObject.configuration.objectStyles || {};
|
||||||
let selectItemsArray = [];
|
let selectItemsArray = [];
|
||||||
let newDomainObjectsArray = [];
|
let newDomainObjectsArray = [];
|
||||||
|
|
||||||
@ -721,8 +728,8 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.openmct.objects.mutate(this.domainObject, "configuration.items", this.layoutItems);
|
this.openmct.objects.mutate(this.internalDomainObject, "configuration.items", this.layoutItems);
|
||||||
this.openmct.objects.mutate(this.domainObject, "configuration.objectStyles", objectStyles);
|
this.openmct.objects.mutate(this.internalDomainObject, "configuration.objectStyles", objectStyles);
|
||||||
this.$el.click(); //clear selection;
|
this.$el.click(); //clear selection;
|
||||||
|
|
||||||
newDomainObjectsArray.forEach(domainObject => {
|
newDomainObjectsArray.forEach(domainObject => {
|
||||||
@ -761,13 +768,13 @@ export default {
|
|||||||
};
|
};
|
||||||
this.createNewDomainObject(mockDomainObject, overlayPlotIdentifiers, viewType).then((newDomainObject) => {
|
this.createNewDomainObject(mockDomainObject, overlayPlotIdentifiers, viewType).then((newDomainObject) => {
|
||||||
let newDomainObjectKeyString = this.openmct.objects.makeKeyString(newDomainObject.identifier);
|
let newDomainObjectKeyString = this.openmct.objects.makeKeyString(newDomainObject.identifier);
|
||||||
let domainObjectKeyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
let internalDomainObjectKeyString = this.openmct.objects.makeKeyString(this.internalDomainObject.identifier);
|
||||||
|
|
||||||
this.composition.add(newDomainObject);
|
this.composition.add(newDomainObject);
|
||||||
this.addItem('subobject-view', newDomainObject, position);
|
this.addItem('subobject-view', newDomainObject, position);
|
||||||
|
|
||||||
overlayPlots.forEach(overlayPlot => {
|
overlayPlots.forEach(overlayPlot => {
|
||||||
if (overlayPlot.location === domainObjectKeyString) {
|
if (overlayPlot.location === internalDomainObjectKeyString) {
|
||||||
this.openmct.objects.mutate(overlayPlot, 'location', newDomainObjectKeyString);
|
this.openmct.objects.mutate(overlayPlot, 'location', newDomainObjectKeyString);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -51,11 +51,11 @@ export default {
|
|||||||
url: element.url
|
url: element.url
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
LayoutFrame
|
LayoutFrame
|
||||||
},
|
},
|
||||||
mixins: [conditionalStylesMixin],
|
mixins: [conditionalStylesMixin],
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -99,8 +99,8 @@ export default {
|
|||||||
stroke: '#717171'
|
stroke: '#717171'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mixins: [conditionalStylesMixin],
|
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
|
mixins: [conditionalStylesMixin],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -80,11 +80,11 @@ export default {
|
|||||||
viewKey
|
viewKey
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
inject: ['openmct', 'objectPath'],
|
||||||
components: {
|
components: {
|
||||||
ObjectFrame,
|
ObjectFrame,
|
||||||
LayoutFrame
|
LayoutFrame
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'objectPath'],
|
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -129,22 +129,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.openmct.objects.supportsMutation(this.item.identifier)) {
|
|
||||||
this.openmct.objects.getMutable(this.item.identifier)
|
|
||||||
.then(this.setObject);
|
|
||||||
} else {
|
|
||||||
this.openmct.objects.get(this.item.identifier)
|
this.openmct.objects.get(this.item.identifier)
|
||||||
.then(this.setObject);
|
.then(this.setObject);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
destroyed() {
|
||||||
if (this.removeSelectable) {
|
if (this.removeSelectable) {
|
||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.domainObject.isMutable) {
|
|
||||||
this.openmct.objects.destroyMutable(this.domainObject);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setObject(domainObject) {
|
setObject(domainObject) {
|
||||||
|
@ -98,11 +98,11 @@ export default {
|
|||||||
font: 'default'
|
font: 'default'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
inject: ['openmct', 'objectPath'],
|
||||||
components: {
|
components: {
|
||||||
LayoutFrame
|
LayoutFrame
|
||||||
},
|
},
|
||||||
mixins: [conditionalStylesMixin],
|
mixins: [conditionalStylesMixin],
|
||||||
inject: ['openmct', 'objectPath'],
|
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -212,20 +212,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.openmct.objects.supportsMutation(this.item.identifier)) {
|
|
||||||
this.openmct.objects.getMutable(this.item.identifier)
|
|
||||||
.then(this.setObject);
|
|
||||||
} else {
|
|
||||||
this.openmct.objects.get(this.item.identifier)
|
this.openmct.objects.get(this.item.identifier)
|
||||||
.then(this.setObject);
|
.then(this.setObject);
|
||||||
}
|
|
||||||
|
|
||||||
this.openmct.time.on("bounds", this.refreshData);
|
this.openmct.time.on("bounds", this.refreshData);
|
||||||
|
|
||||||
this.status = this.openmct.status.get(this.item.identifier);
|
this.status = this.openmct.status.get(this.item.identifier);
|
||||||
this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus);
|
this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
destroyed() {
|
||||||
this.removeSubscription();
|
this.removeSubscription();
|
||||||
this.removeStatusListener();
|
this.removeStatusListener();
|
||||||
|
|
||||||
@ -234,10 +228,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.openmct.time.off("bounds", this.refreshData);
|
this.openmct.time.off("bounds", this.refreshData);
|
||||||
|
|
||||||
if (this.domainObject.isMutable) {
|
|
||||||
this.openmct.objects.destroyMutable(this.domainObject);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formattedValueForCopy() {
|
formattedValueForCopy() {
|
||||||
|
@ -59,11 +59,11 @@ export default {
|
|||||||
font: 'default'
|
font: 'default'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
LayoutFrame
|
LayoutFrame
|
||||||
},
|
},
|
||||||
mixins: [conditionalStylesMixin],
|
mixins: [conditionalStylesMixin],
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -57,7 +57,6 @@ describe("The Duplicate Action plugin", () => {
|
|||||||
overwrite: {
|
overwrite: {
|
||||||
folder: {
|
folder: {
|
||||||
name: "Parent Folder",
|
name: "Parent Folder",
|
||||||
type: "folder",
|
|
||||||
composition: [childObject.identifier]
|
composition: [childObject.identifier]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +104,6 @@ describe("The Duplicate Action plugin", () => {
|
|||||||
|
|
||||||
// already installed by default, but never hurts, just adds to context menu
|
// already installed by default, but never hurts, just adds to context menu
|
||||||
openmct.install(DuplicateActionPlugin());
|
openmct.install(DuplicateActionPlugin());
|
||||||
openmct.types.addType('folder', {creatable: true});
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless();
|
openmct.startHeadless();
|
||||||
|
@ -47,13 +47,13 @@ define([
|
|||||||
return {
|
return {
|
||||||
show: function (element) {
|
show: function (element) {
|
||||||
component = new Vue({
|
component = new Vue({
|
||||||
|
provide: {
|
||||||
|
openmct
|
||||||
|
},
|
||||||
el: element,
|
el: element,
|
||||||
components: {
|
components: {
|
||||||
FiltersView: FiltersView.default
|
FiltersView: FiltersView.default
|
||||||
},
|
},
|
||||||
provide: {
|
|
||||||
openmct
|
|
||||||
},
|
|
||||||
template: '<filters-view></filters-view>'
|
template: '<filters-view></filters-view>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -65,11 +65,11 @@ import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
|||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
FilterField,
|
FilterField,
|
||||||
ToggleSwitch
|
ToggleSwitch
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
filterObject: {
|
filterObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -41,10 +41,10 @@
|
|||||||
import FilterField from './FilterField.vue';
|
import FilterField from './FilterField.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
FilterField
|
FilterField
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
globalMetadata: {
|
globalMetadata: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -87,12 +87,12 @@ import DropHint from './dropHint.vue';
|
|||||||
const MIN_FRAME_SIZE = 5;
|
const MIN_FRAME_SIZE = 5;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
FrameComponent,
|
FrameComponent,
|
||||||
ResizeHandle,
|
ResizeHandle,
|
||||||
DropHint
|
DropHint
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
container: {
|
container: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="allContainersAreEmpty"
|
v-if="areAllContainersEmpty()"
|
||||||
class="c-fl__empty"
|
class="c-fl__empty"
|
||||||
>
|
>
|
||||||
<span class="c-fl__empty-message">This Flexible Layout is currently empty</span>
|
<span class="c-fl__empty-message">This Flexible Layout is currently empty</span>
|
||||||
@ -94,6 +94,7 @@ import Container from '../utils/container';
|
|||||||
import Frame from '../utils/frame';
|
import Frame from '../utils/frame';
|
||||||
import ResizeHandle from './resizeHandle.vue';
|
import ResizeHandle from './resizeHandle.vue';
|
||||||
import DropHint from './dropHint.vue';
|
import DropHint from './dropHint.vue';
|
||||||
|
import RemoveAction from '../../remove/RemoveAction.js';
|
||||||
|
|
||||||
const MIN_CONTAINER_SIZE = 5;
|
const MIN_CONTAINER_SIZE = 5;
|
||||||
|
|
||||||
@ -139,20 +140,19 @@ function sizeToFill(items) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct', 'objectPath', 'layoutObject'],
|
||||||
components: {
|
components: {
|
||||||
ContainerComponent,
|
ContainerComponent,
|
||||||
ResizeHandle,
|
ResizeHandle,
|
||||||
DropHint
|
DropHint
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'objectPath', 'layoutObject'],
|
|
||||||
props: {
|
props: {
|
||||||
isEditing: Boolean
|
isEditing: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
domainObject: this.layoutObject,
|
domainObject: this.layoutObject,
|
||||||
newFrameLocation: [],
|
newFrameLocation: []
|
||||||
identifierMap: {}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -168,30 +168,26 @@ export default {
|
|||||||
},
|
},
|
||||||
rowsLayout() {
|
rowsLayout() {
|
||||||
return this.domainObject.configuration.rowsLayout;
|
return this.domainObject.configuration.rowsLayout;
|
||||||
},
|
|
||||||
allContainersAreEmpty() {
|
|
||||||
return this.containers.every(container => container.frames.length === 0);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.buildIdentifierMap();
|
|
||||||
this.composition = this.openmct.composition.get(this.domainObject);
|
this.composition = this.openmct.composition.get(this.domainObject);
|
||||||
this.composition.on('remove', this.removeChildObject);
|
this.composition.on('remove', this.removeChildObject);
|
||||||
this.composition.on('add', this.addFrame);
|
this.composition.on('add', this.addFrame);
|
||||||
this.composition.load();
|
|
||||||
|
this.RemoveAction = new RemoveAction(this.openmct);
|
||||||
|
|
||||||
|
this.unobserve = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.composition.off('remove', this.removeChildObject);
|
this.composition.off('remove', this.removeChildObject);
|
||||||
this.composition.off('add', this.addFrame);
|
this.composition.off('add', this.addFrame);
|
||||||
|
|
||||||
|
this.unobserve();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
buildIdentifierMap() {
|
areAllContainersEmpty() {
|
||||||
this.containers.forEach(container => {
|
return !this.containers.filter(container => container.frames.length).length;
|
||||||
container.frames.forEach(frame => {
|
|
||||||
let keystring = this.openmct.objects.makeKeyString(frame.domainObjectIdentifier);
|
|
||||||
this.identifierMap[keystring] = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
addContainer() {
|
addContainer() {
|
||||||
let container = new Container();
|
let container = new Container();
|
||||||
@ -240,9 +236,6 @@ export default {
|
|||||||
this.newFrameLocation = [containerIndex, insertFrameIndex];
|
this.newFrameLocation = [containerIndex, insertFrameIndex];
|
||||||
},
|
},
|
||||||
addFrame(domainObject) {
|
addFrame(domainObject) {
|
||||||
let keystring = this.openmct.objects.makeKeyString(domainObject.identifier);
|
|
||||||
|
|
||||||
if (!this.identifierMap[keystring]) {
|
|
||||||
let containerIndex = this.newFrameLocation.length ? this.newFrameLocation[0] : 0;
|
let containerIndex = this.newFrameLocation.length ? this.newFrameLocation[0] : 0;
|
||||||
let container = this.containers[containerIndex];
|
let container = this.containers[containerIndex];
|
||||||
let frameIndex = this.newFrameLocation.length ? this.newFrameLocation[1] : container.frames.length;
|
let frameIndex = this.newFrameLocation.length ? this.newFrameLocation[1] : container.frames.length;
|
||||||
@ -253,8 +246,6 @@ export default {
|
|||||||
|
|
||||||
this.newFrameLocation = [];
|
this.newFrameLocation = [];
|
||||||
this.persist(containerIndex);
|
this.persist(containerIndex);
|
||||||
this.identifierMap[keystring] = true;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
deleteFrame(frameId) {
|
deleteFrame(frameId) {
|
||||||
let container = this.containers
|
let container = this.containers
|
||||||
@ -263,20 +254,16 @@ export default {
|
|||||||
.frames
|
.frames
|
||||||
.filter((f => f.id === frameId))[0];
|
.filter((f => f.id === frameId))[0];
|
||||||
|
|
||||||
this.removeFromComposition(frame.domainObjectIdentifier);
|
this.removeFromComposition(frame.domainObjectIdentifier)
|
||||||
|
.then(() => {
|
||||||
this.$nextTick().then(() => {
|
|
||||||
sizeToFill(container.frames);
|
sizeToFill(container.frames);
|
||||||
this.setSelectionToParent();
|
this.setSelectionToParent();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
removeFromComposition(identifier) {
|
removeFromComposition(identifier) {
|
||||||
let keystring = this.openmct.objects.makeKeyString(identifier);
|
return this.openmct.objects.get(identifier).then((childDomainObject) => {
|
||||||
|
this.RemoveAction.removeFromComposition(this.domainObject, childDomainObject);
|
||||||
this.identifierMap[keystring] = undefined;
|
});
|
||||||
delete this.identifierMap[keystring];
|
|
||||||
|
|
||||||
this.composition.remove({identifier});
|
|
||||||
},
|
},
|
||||||
setSelectionToParent() {
|
setSelectionToParent() {
|
||||||
this.$el.click();
|
this.$el.click();
|
||||||
|
@ -58,10 +58,10 @@
|
|||||||
import ObjectFrame from '../../../ui/components/ObjectFrame.vue';
|
import ObjectFrame from '../../../ui/components/ObjectFrame.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
ObjectFrame
|
ObjectFrame
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
frame: {
|
frame: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -44,15 +44,15 @@ define([
|
|||||||
return {
|
return {
|
||||||
show: function (element, isEditing) {
|
show: function (element, isEditing) {
|
||||||
component = new Vue({
|
component = new Vue({
|
||||||
el: element,
|
|
||||||
components: {
|
|
||||||
FlexibleLayoutComponent: FlexibleLayoutComponent.default
|
|
||||||
},
|
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
objectPath,
|
objectPath,
|
||||||
layoutObject: domainObject
|
layoutObject: domainObject
|
||||||
},
|
},
|
||||||
|
el: element,
|
||||||
|
components: {
|
||||||
|
FlexibleLayoutComponent: FlexibleLayoutComponent.default
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isEditing: isEditing
|
isEditing: isEditing
|
||||||
|
@ -22,22 +22,18 @@
|
|||||||
|
|
||||||
define([
|
define([
|
||||||
'./components/GridView.vue',
|
'./components/GridView.vue',
|
||||||
'./constants.js',
|
|
||||||
'vue'
|
'vue'
|
||||||
], function (
|
], function (
|
||||||
GridViewComponent,
|
GridViewComponent,
|
||||||
constants,
|
|
||||||
Vue
|
Vue
|
||||||
) {
|
) {
|
||||||
function FolderGridView(openmct) {
|
function FolderGridView(openmct) {
|
||||||
const ALLOWED_FOLDER_TYPES = constants.ALLOWED_FOLDER_TYPES;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: 'grid',
|
key: 'grid',
|
||||||
name: 'Grid View',
|
name: 'Grid View',
|
||||||
cssClass: 'icon-thumbs-strip',
|
cssClass: 'icon-thumbs-strip',
|
||||||
canView: function (domainObject) {
|
canView: function (domainObject) {
|
||||||
return ALLOWED_FOLDER_TYPES.includes(domainObject.type);
|
return domainObject.type === 'folder';
|
||||||
},
|
},
|
||||||
view: function (domainObject) {
|
view: function (domainObject) {
|
||||||
let component;
|
let component;
|
||||||
|
@ -22,24 +22,20 @@
|
|||||||
|
|
||||||
define([
|
define([
|
||||||
'./components/ListView.vue',
|
'./components/ListView.vue',
|
||||||
'./constants.js',
|
|
||||||
'vue',
|
'vue',
|
||||||
'moment'
|
'moment'
|
||||||
], function (
|
], function (
|
||||||
ListViewComponent,
|
ListViewComponent,
|
||||||
constants,
|
|
||||||
Vue,
|
Vue,
|
||||||
Moment
|
Moment
|
||||||
) {
|
) {
|
||||||
function FolderListView(openmct) {
|
function FolderListView(openmct) {
|
||||||
const ALLOWED_FOLDER_TYPES = constants.ALLOWED_FOLDER_TYPES;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: 'list-view',
|
key: 'list-view',
|
||||||
name: 'List View',
|
name: 'List View',
|
||||||
cssClass: 'icon-list-view',
|
cssClass: 'icon-list-view',
|
||||||
canView: function (domainObject) {
|
canView: function (domainObject) {
|
||||||
return ALLOWED_FOLDER_TYPES.includes(domainObject.type);
|
return domainObject.type === 'folder';
|
||||||
},
|
},
|
||||||
view: function (domainObject) {
|
view: function (domainObject) {
|
||||||
let component;
|
let component;
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 {
|
|
||||||
createOpenMct,
|
|
||||||
resetApplicationState
|
|
||||||
} from 'utils/testing';
|
|
||||||
|
|
||||||
describe("the plugin", () => {
|
|
||||||
let openmct;
|
|
||||||
let goToFolderAction;
|
|
||||||
let mockObjectPath;
|
|
||||||
|
|
||||||
beforeEach((done) => {
|
|
||||||
openmct = createOpenMct();
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
|
||||||
openmct.startHeadless();
|
|
||||||
|
|
||||||
goToFolderAction = openmct.actions._allActions.goToOriginal;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('installs the go to folder action', () => {
|
|
||||||
expect(goToFolderAction).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when invoked', () => {
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockObjectPath = [{
|
|
||||||
name: 'mock folder',
|
|
||||||
type: 'folder',
|
|
||||||
identifier: {
|
|
||||||
key: 'mock-folder',
|
|
||||||
namespace: ''
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({
|
|
||||||
identifier: {
|
|
||||||
namespace: '',
|
|
||||||
key: 'test'
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
goToFolderAction.invoke(mockObjectPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('goes to the original location', () => {
|
|
||||||
expect(window.location.href).toContain('context.html#/browse/?tc.mode=fixed&tc.startBound=0&tc.endBound=1&tc.timeSystem=utc');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,99 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 { createOpenMct, resetApplicationState } from "utils/testing";
|
|
||||||
import InterceptorPlugin from "./plugin";
|
|
||||||
|
|
||||||
describe('the plugin', function () {
|
|
||||||
let element;
|
|
||||||
let child;
|
|
||||||
let openmct;
|
|
||||||
const TEST_NAMESPACE = 'test';
|
|
||||||
|
|
||||||
beforeEach((done) => {
|
|
||||||
const appHolder = document.createElement('div');
|
|
||||||
appHolder.style.width = '640px';
|
|
||||||
appHolder.style.height = '480px';
|
|
||||||
|
|
||||||
openmct = createOpenMct();
|
|
||||||
openmct.install(new InterceptorPlugin(openmct));
|
|
||||||
|
|
||||||
element = document.createElement('div');
|
|
||||||
element.style.width = '640px';
|
|
||||||
element.style.height = '480px';
|
|
||||||
child = document.createElement('div');
|
|
||||||
child.style.width = '640px';
|
|
||||||
child.style.height = '480px';
|
|
||||||
element.appendChild(child);
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
|
||||||
openmct.startHeadless(appHolder);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('the missingObjectInterceptor', () => {
|
|
||||||
let mockProvider;
|
|
||||||
beforeEach(() => {
|
|
||||||
mockProvider = jasmine.createSpyObj("mock provider", [
|
|
||||||
"get"
|
|
||||||
]);
|
|
||||||
mockProvider.get.and.returnValue(Promise.resolve(undefined));
|
|
||||||
openmct.objects.addProvider(TEST_NAMESPACE, mockProvider);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns missing objects', (done) => {
|
|
||||||
const identifier = {
|
|
||||||
namespace: TEST_NAMESPACE,
|
|
||||||
key: 'hello'
|
|
||||||
};
|
|
||||||
openmct.objects.get(identifier).then((testObject) => {
|
|
||||||
expect(testObject).toEqual({
|
|
||||||
identifier,
|
|
||||||
type: 'unknown',
|
|
||||||
name: 'Missing: test:hello'
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the My items object if not found', (done) => {
|
|
||||||
const identifier = {
|
|
||||||
namespace: TEST_NAMESPACE,
|
|
||||||
key: 'mine'
|
|
||||||
};
|
|
||||||
openmct.objects.get(identifier).then((testObject) => {
|
|
||||||
expect(testObject).toEqual({
|
|
||||||
identifier,
|
|
||||||
"name": "My Items",
|
|
||||||
"type": "folder",
|
|
||||||
"composition": [],
|
|
||||||
"location": "ROOT"
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,33 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
return function (openmct) {
|
|
||||||
openmct.types.addType("noneditable.folder", {
|
|
||||||
name: "Non-Editable Folder",
|
|
||||||
key: "noneditable.folder",
|
|
||||||
description: "Create folders to organize other objects or links to objects without the ability to edit it's properties.",
|
|
||||||
cssClass: "icon-folder",
|
|
||||||
creatable: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 {
|
|
||||||
createOpenMct,
|
|
||||||
resetApplicationState
|
|
||||||
} from 'utils/testing';
|
|
||||||
|
|
||||||
describe("the plugin", () => {
|
|
||||||
const NON_EDITABLE_FOLDER_KEY = 'noneditable.folder';
|
|
||||||
let openmct;
|
|
||||||
|
|
||||||
beforeEach((done) => {
|
|
||||||
openmct = createOpenMct();
|
|
||||||
openmct.install(openmct.plugins.NonEditableFolder());
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
|
||||||
openmct.startHeadless();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds the new non-editable folder type', () => {
|
|
||||||
const type = openmct.types.get(NON_EDITABLE_FOLDER_KEY);
|
|
||||||
|
|
||||||
expect(type).toBeDefined();
|
|
||||||
expect(type.definition.creatable).toBeFalse();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -97,8 +97,7 @@
|
|||||||
:selected-page="getSelectedPage()"
|
:selected-page="getSelectedPage()"
|
||||||
:selected-section="getSelectedSection()"
|
:selected-section="getSelectedSection()"
|
||||||
:read-only="false"
|
:read-only="false"
|
||||||
@deleteEntry="deleteEntry"
|
@updateEntries="updateEntries"
|
||||||
@updateEntry="updateEntry"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -112,20 +111,19 @@ import Search from '@/ui/components/search.vue';
|
|||||||
import SearchResults from './SearchResults.vue';
|
import SearchResults from './SearchResults.vue';
|
||||||
import Sidebar from './Sidebar.vue';
|
import Sidebar from './Sidebar.vue';
|
||||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
||||||
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
import { addNotebookEntry, createNewEmbed, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
||||||
import objectUtils from 'objectUtils';
|
import objectUtils from 'objectUtils';
|
||||||
|
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import objectLink from '../../../ui/mixins/object-link';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject', 'snapshotContainer'],
|
||||||
components: {
|
components: {
|
||||||
NotebookEntry,
|
NotebookEntry,
|
||||||
Search,
|
Search,
|
||||||
SearchResults,
|
SearchResults,
|
||||||
Sidebar
|
Sidebar
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject', 'snapshotContainer'],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
defaultPageId: getDefaultNotebook() ? getDefaultNotebook().page.id : '',
|
defaultPageId: getDefaultNotebook() ? getDefaultNotebook().page.id : '',
|
||||||
@ -184,9 +182,7 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
|
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
|
||||||
this.formatSidebar();
|
this.formatSidebar();
|
||||||
|
|
||||||
window.addEventListener('orientationchange', this.formatSidebar);
|
window.addEventListener('orientationchange', this.formatSidebar);
|
||||||
window.addEventListener("hashchange", this.navigateToSectionPage, false);
|
|
||||||
|
|
||||||
this.navigateToSectionPage();
|
this.navigateToSectionPage();
|
||||||
},
|
},
|
||||||
@ -194,9 +190,6 @@ export default {
|
|||||||
if (this.unlisten) {
|
if (this.unlisten) {
|
||||||
this.unlisten();
|
this.unlisten();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.removeEventListener('orientationchange', this.formatSidebar);
|
|
||||||
window.removeEventListener("hashchange", this.navigateToSectionPage);
|
|
||||||
},
|
},
|
||||||
updated: function () {
|
updated: function () {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@ -232,50 +225,17 @@ export default {
|
|||||||
},
|
},
|
||||||
createNotebookStorageObject() {
|
createNotebookStorageObject() {
|
||||||
const notebookMeta = {
|
const notebookMeta = {
|
||||||
name: this.internalDomainObject.name,
|
identifier: this.internalDomainObject.identifier
|
||||||
identifier: this.internalDomainObject.identifier,
|
|
||||||
link: this.getLinktoNotebook()
|
|
||||||
};
|
};
|
||||||
const page = this.getSelectedPage();
|
const page = this.getSelectedPage();
|
||||||
const section = this.getSelectedSection();
|
const section = this.getSelectedSection();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
notebookMeta,
|
notebookMeta,
|
||||||
page,
|
section,
|
||||||
section
|
page
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
deleteEntry(entryId) {
|
|
||||||
const self = this;
|
|
||||||
const entryPos = getEntryPosById(entryId, this.internalDomainObject, this.selectedSection, this.selectedPage);
|
|
||||||
if (entryPos === -1) {
|
|
||||||
this.openmct.notifications.alert('Warning: unable to delete entry');
|
|
||||||
console.error(`unable to delete entry ${entryId} from section ${this.selectedSection}, page ${this.selectedPage}`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dialog = this.openmct.overlays.dialog({
|
|
||||||
iconClass: 'alert',
|
|
||||||
message: 'This action will permanently delete this entry. Do you wish to continue?',
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
label: "Ok",
|
|
||||||
emphasis: true,
|
|
||||||
callback: () => {
|
|
||||||
const entries = getNotebookEntries(self.internalDomainObject, self.selectedSection, self.selectedPage);
|
|
||||||
entries.splice(entryPos, 1);
|
|
||||||
self.updateEntries(entries);
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Cancel",
|
|
||||||
callback: () => dialog.dismiss()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
},
|
|
||||||
dragOver(event) {
|
dragOver(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.dataTransfer.dropEffect = "copy";
|
event.dataTransfer.dropEffect = "copy";
|
||||||
@ -349,20 +309,6 @@ export default {
|
|||||||
|
|
||||||
return this.openmct.objects.get(oldNotebookStorage.notebookMeta.identifier);
|
return this.openmct.objects.get(oldNotebookStorage.notebookMeta.identifier);
|
||||||
},
|
},
|
||||||
getLinktoNotebook() {
|
|
||||||
const objectPath = this.openmct.router.path;
|
|
||||||
const link = objectLink.computed.objectLink.call({
|
|
||||||
objectPath,
|
|
||||||
openmct: this.openmct
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedSection = this.selectedSection;
|
|
||||||
const selectedPage = this.selectedPage;
|
|
||||||
const sectionId = selectedSection ? selectedSection.id : '';
|
|
||||||
const pageId = selectedPage ? selectedPage.id : '';
|
|
||||||
|
|
||||||
return `${link}?sectionId=${sectionId}&pageId=${pageId}`;
|
|
||||||
},
|
|
||||||
getPage(section, id) {
|
getPage(section, id) {
|
||||||
return section.pages.find(p => p.id === id);
|
return section.pages.find(p => p.id === id);
|
||||||
},
|
},
|
||||||
@ -447,12 +393,6 @@ export default {
|
|||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedSectionId = this.selectedSection && this.selectedSection.id;
|
|
||||||
const selectedPageId = this.selectedPage && this.selectedPage.id;
|
|
||||||
if (selectedPageId === pageId && selectedSectionId === sectionId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sectionsChanged({ sections });
|
this.sectionsChanged({ sections });
|
||||||
},
|
},
|
||||||
newEntry(embed = null) {
|
newEntry(embed = null) {
|
||||||
@ -572,13 +512,6 @@ export default {
|
|||||||
|
|
||||||
setDefaultNotebookSection(section);
|
setDefaultNotebookSection(section);
|
||||||
},
|
},
|
||||||
updateEntry(entry) {
|
|
||||||
const entries = getNotebookEntries(this.internalDomainObject, this.selectedSection, this.selectedPage);
|
|
||||||
const entryPos = getEntryPosById(entry.id, this.internalDomainObject, this.selectedSection, this.selectedPage);
|
|
||||||
entries[entryPos] = entry;
|
|
||||||
|
|
||||||
this.updateEntries(entries);
|
|
||||||
},
|
|
||||||
updateEntries(entries) {
|
updateEntries(entries) {
|
||||||
const configuration = this.internalDomainObject.configuration;
|
const configuration = this.internalDomainObject.configuration;
|
||||||
const notebookEntries = configuration.entries || {};
|
const notebookEntries = configuration.entries || {};
|
||||||
|
@ -33,10 +33,10 @@ import SnapshotTemplate from './snapshot-template.html';
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
PopupMenu
|
PopupMenu
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
embed: {
|
embed: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -12,15 +12,11 @@
|
|||||||
<div class="c-ne__content">
|
<div class="c-ne__content">
|
||||||
<div :id="entry.id"
|
<div :id="entry.id"
|
||||||
class="c-ne__text"
|
class="c-ne__text"
|
||||||
tabindex="0"
|
:class="{'c-ne__input' : !readOnly }"
|
||||||
:class="{ 'c-ne__input' : !readOnly }"
|
|
||||||
:contenteditable="!readOnly"
|
:contenteditable="!readOnly"
|
||||||
@blur="updateEntryValue($event)"
|
@blur="updateEntryValue($event, entry.id)"
|
||||||
@keydown.enter.exact.prevent
|
@focus="updateCurrentEntryValue($event, entry.id)"
|
||||||
@keyup.enter.exact.prevent="forceBlur($event)"
|
>{{ entry.text }}</div>
|
||||||
v-text="entry.text"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="c-snapshots c-ne__embeds">
|
<div class="c-snapshots c-ne__embeds">
|
||||||
<NotebookEmbed v-for="embed in entry.embeds"
|
<NotebookEmbed v-for="embed in entry.embeds"
|
||||||
:key="embed.id"
|
:key="embed.id"
|
||||||
@ -37,7 +33,6 @@
|
|||||||
>
|
>
|
||||||
<button class="c-icon-button c-icon-button--major icon-trash"
|
<button class="c-icon-button c-icon-button--major icon-trash"
|
||||||
title="Delete this entry"
|
title="Delete this entry"
|
||||||
tabindex="-1"
|
|
||||||
@click="deleteEntry"
|
@click="deleteEntry"
|
||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
@ -62,14 +57,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NotebookEmbed from './NotebookEmbed.vue';
|
import NotebookEmbed from './NotebookEmbed.vue';
|
||||||
import { createNewEmbed } from '../utils/notebook-entries';
|
import { createNewEmbed, getEntryPosById, getNotebookEntries } from '../utils/notebook-entries';
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct', 'snapshotContainer'],
|
||||||
components: {
|
components: {
|
||||||
NotebookEmbed
|
NotebookEmbed
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'snapshotContainer'],
|
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -108,6 +103,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentEntryValue: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
createdOnDate() {
|
createdOnDate() {
|
||||||
return this.formatTime(this.entry.createdOn, 'YYYY-MM-DD');
|
return this.formatTime(this.entry.createdOn, 'YYYY-MM-DD');
|
||||||
@ -117,20 +117,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.updateEntries = this.updateEntries.bind(this);
|
||||||
this.dropOnEntry = this.dropOnEntry.bind(this);
|
this.dropOnEntry = this.dropOnEntry.bind(this);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addNewEmbed(objectPath) {
|
|
||||||
const bounds = this.openmct.time.bounds();
|
|
||||||
const snapshotMeta = {
|
|
||||||
bounds,
|
|
||||||
link: null,
|
|
||||||
objectPath,
|
|
||||||
openmct: this.openmct
|
|
||||||
};
|
|
||||||
const newEmbed = createNewEmbed(snapshotMeta);
|
|
||||||
this.entry.embeds.push(newEmbed);
|
|
||||||
},
|
|
||||||
cancelEditMode(event) {
|
cancelEditMode(event) {
|
||||||
const isEditing = this.openmct.editor.isEditing();
|
const isEditing = this.openmct.editor.isEditing();
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
@ -142,23 +132,63 @@ export default {
|
|||||||
event.dataTransfer.dropEffect = "copy";
|
event.dataTransfer.dropEffect = "copy";
|
||||||
},
|
},
|
||||||
deleteEntry() {
|
deleteEntry() {
|
||||||
this.$emit('deleteEntry', this.entry.id);
|
const self = this;
|
||||||
|
const entryPosById = self.entryPosById(self.entry.id);
|
||||||
|
if (entryPosById === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialog = this.openmct.overlays.dialog({
|
||||||
|
iconClass: 'alert',
|
||||||
|
message: 'This action will permanently delete this entry. Do you wish to continue?',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: "Ok",
|
||||||
|
emphasis: true,
|
||||||
|
callback: () => {
|
||||||
|
const entries = getNotebookEntries(self.domainObject, self.selectedSection, self.selectedPage);
|
||||||
|
entries.splice(entryPosById, 1);
|
||||||
|
self.updateEntries(entries);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Cancel",
|
||||||
|
callback: () => {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
},
|
},
|
||||||
dropOnEntry($event) {
|
dropOnEntry($event) {
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
const snapshotId = $event.dataTransfer.getData('openmct/snapshot/id');
|
const snapshotId = $event.dataTransfer.getData('openmct/snapshot/id');
|
||||||
if (snapshotId.length) {
|
if (snapshotId.length) {
|
||||||
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
|
this.moveSnapshot(snapshotId);
|
||||||
this.snapshotContainer.removeSnapshot(snapshotId);
|
|
||||||
this.entry.embeds.push(snapshot);
|
return;
|
||||||
} else {
|
|
||||||
const data = $event.dataTransfer.getData('openmct/domain-object-path');
|
|
||||||
const objectPath = JSON.parse(data);
|
|
||||||
this.addNewEmbed(objectPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('updateEntry', this.entry);
|
const data = $event.dataTransfer.getData('openmct/domain-object-path');
|
||||||
|
const objectPath = JSON.parse(data);
|
||||||
|
const entryPos = this.entryPosById(this.entry.id);
|
||||||
|
const bounds = this.openmct.time.bounds();
|
||||||
|
const snapshotMeta = {
|
||||||
|
bounds,
|
||||||
|
link: null,
|
||||||
|
objectPath,
|
||||||
|
openmct: this.openmct
|
||||||
|
};
|
||||||
|
const newEmbed = createNewEmbed(snapshotMeta);
|
||||||
|
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||||
|
const currentEntryEmbeds = entries[entryPos].embeds;
|
||||||
|
currentEntryEmbeds.push(newEmbed);
|
||||||
|
this.updateEntries(entries);
|
||||||
|
},
|
||||||
|
entryPosById(entryId) {
|
||||||
|
return getEntryPosById(entryId, this.domainObject, this.selectedSection, this.selectedPage);
|
||||||
},
|
},
|
||||||
findPositionInArray(array, id) {
|
findPositionInArray(array, id) {
|
||||||
let position = -1;
|
let position = -1;
|
||||||
@ -173,12 +203,15 @@ export default {
|
|||||||
|
|
||||||
return position;
|
return position;
|
||||||
},
|
},
|
||||||
forceBlur(event) {
|
|
||||||
event.target.blur();
|
|
||||||
},
|
|
||||||
formatTime(unixTime, timeFormat) {
|
formatTime(unixTime, timeFormat) {
|
||||||
return Moment.utc(unixTime).format(timeFormat);
|
return Moment.utc(unixTime).format(timeFormat);
|
||||||
},
|
},
|
||||||
|
moveSnapshot(snapshotId) {
|
||||||
|
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
|
||||||
|
this.entry.embeds.push(snapshot);
|
||||||
|
this.updateEntry(this.entry);
|
||||||
|
this.snapshotContainer.removeSnapshot(snapshotId);
|
||||||
|
},
|
||||||
navigateToPage() {
|
navigateToPage() {
|
||||||
this.$emit('changeSectionPage', {
|
this.$emit('changeSectionPage', {
|
||||||
sectionId: this.result.section.id,
|
sectionId: this.result.section.id,
|
||||||
@ -194,8 +227,15 @@ export default {
|
|||||||
removeEmbed(id) {
|
removeEmbed(id) {
|
||||||
const embedPosition = this.findPositionInArray(this.entry.embeds, id);
|
const embedPosition = this.findPositionInArray(this.entry.embeds, id);
|
||||||
this.entry.embeds.splice(embedPosition, 1);
|
this.entry.embeds.splice(embedPosition, 1);
|
||||||
|
this.updateEntry(this.entry);
|
||||||
|
},
|
||||||
|
updateCurrentEntryValue($event) {
|
||||||
|
if (this.readOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('updateEntry', this.entry);
|
const target = $event.target;
|
||||||
|
this.currentEntryValue = target ? target.textContent : '';
|
||||||
},
|
},
|
||||||
updateEmbed(newEmbed) {
|
updateEmbed(newEmbed) {
|
||||||
this.entry.embeds.some(e => {
|
this.entry.embeds.some(e => {
|
||||||
@ -207,14 +247,44 @@ export default {
|
|||||||
return found;
|
return found;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$emit('updateEntry', this.entry);
|
this.updateEntry(this.entry);
|
||||||
},
|
},
|
||||||
updateEntryValue($event) {
|
updateEntry(newEntry) {
|
||||||
const value = $event.target.innerText;
|
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||||
if (value !== this.entry.text && value.match(/\S/)) {
|
entries.some(entry => {
|
||||||
this.entry.text = value;
|
const found = (entry.id === newEntry.id);
|
||||||
this.$emit('updateEntry', this.entry);
|
if (found) {
|
||||||
|
entry = newEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateEntries(entries);
|
||||||
|
},
|
||||||
|
updateEntryValue($event, entryId) {
|
||||||
|
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = $event.target;
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entryPos = this.entryPosById(entryId);
|
||||||
|
const value = target.textContent.trim();
|
||||||
|
if (this.currentEntryValue !== value) {
|
||||||
|
target.textContent = value;
|
||||||
|
|
||||||
|
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||||
|
entries[entryPos].text = value;
|
||||||
|
|
||||||
|
this.updateEntries(entries);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateEntries(entries) {
|
||||||
|
this.$emit('updateEntries', entries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -56,11 +56,11 @@ import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container';
|
|||||||
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
|
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct', 'snapshotContainer'],
|
||||||
components: {
|
components: {
|
||||||
NotebookEmbed,
|
NotebookEmbed,
|
||||||
PopupMenu
|
PopupMenu
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'snapshotContainer'],
|
|
||||||
props: {
|
props: {
|
||||||
toggleSnapshot: {
|
toggleSnapshot: {
|
||||||
type: Function,
|
type: Function,
|
||||||
|
@ -69,14 +69,14 @@ export default {
|
|||||||
const divElement = document.querySelector('.l-shell__drawer div');
|
const divElement = document.querySelector('.l-shell__drawer div');
|
||||||
|
|
||||||
this.component = new Vue({
|
this.component = new Vue({
|
||||||
el: divElement,
|
|
||||||
components: {
|
|
||||||
SnapshotContainerComponent
|
|
||||||
},
|
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
snapshotContainer
|
snapshotContainer
|
||||||
},
|
},
|
||||||
|
el: divElement,
|
||||||
|
components: {
|
||||||
|
SnapshotContainerComponent
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
toggleSnapshot
|
toggleSnapshot
|
||||||
|
@ -22,10 +22,10 @@ import { getDefaultNotebook } from '../utils/notebook-storage';
|
|||||||
import Page from './PageComponent.vue';
|
import Page from './PageComponent.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
Page
|
Page
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
defaultPageId: {
|
defaultPageId: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -18,10 +18,10 @@ import PopupMenu from './PopupMenu.vue';
|
|||||||
import RemoveDialog from '../utils/removeDialog';
|
import RemoveDialog from '../utils/removeDialog';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
PopupMenu
|
PopupMenu
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
defaultPageId: {
|
defaultPageId: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -21,10 +21,10 @@
|
|||||||
import NotebookEntry from './NotebookEntry.vue';
|
import NotebookEntry from './NotebookEntry.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct', 'snapshotContainer'],
|
||||||
components: {
|
components: {
|
||||||
NotebookEntry
|
NotebookEntry
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'snapshotContainer'],
|
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -22,10 +22,10 @@ import { getDefaultNotebook } from '../utils/notebook-storage';
|
|||||||
import sectionComponent from './SectionComponent.vue';
|
import sectionComponent from './SectionComponent.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
sectionComponent
|
sectionComponent
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
defaultSectionId: {
|
defaultSectionId: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -21,10 +21,10 @@ import PopupMenu from './PopupMenu.vue';
|
|||||||
import RemoveDialog from '../utils/removeDialog';
|
import RemoveDialog from '../utils/removeDialog';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
PopupMenu
|
PopupMenu
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
defaultSectionId: {
|
defaultSectionId: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -61,11 +61,11 @@ import PageCollection from './PageCollection.vue';
|
|||||||
import uuid from 'uuid';
|
import uuid from 'uuid';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
SectionCollection,
|
SectionCollection,
|
||||||
PageCollection
|
PageCollection
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
defaultPageId: {
|
defaultPageId: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -88,13 +88,13 @@ export default function NotebookPlugin() {
|
|||||||
|
|
||||||
const snapshotContainer = new SnapshotContainer(openmct);
|
const snapshotContainer = new SnapshotContainer(openmct);
|
||||||
const notebookSnapshotIndicator = new Vue ({
|
const notebookSnapshotIndicator = new Vue ({
|
||||||
components: {
|
|
||||||
NotebookSnapshotIndicator
|
|
||||||
},
|
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
snapshotContainer
|
snapshotContainer
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
NotebookSnapshotIndicator
|
||||||
|
},
|
||||||
template: '<NotebookSnapshotIndicator></NotebookSnapshotIndicator>'
|
template: '<NotebookSnapshotIndicator></NotebookSnapshotIndicator>'
|
||||||
});
|
});
|
||||||
const indicator = {
|
const indicator = {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { addNotebookEntry, createNewEmbed } from './utils/notebook-entries';
|
import { addNotebookEntry, createNewEmbed } from './utils/notebook-entries';
|
||||||
import { getDefaultNotebook, getDefaultNotebookLink, setDefaultNotebook } from './utils/notebook-storage';
|
import { getDefaultNotebook } from './utils/notebook-storage';
|
||||||
import { NOTEBOOK_DEFAULT } from '@/plugins/notebook/notebook-constants';
|
import { NOTEBOOK_DEFAULT } from '@/plugins/notebook/notebook-constants';
|
||||||
import SnapshotContainer from './snapshot-container';
|
import SnapshotContainer from './snapshot-container';
|
||||||
|
|
||||||
@ -45,21 +45,12 @@ export default class Snapshot {
|
|||||||
_saveToDefaultNoteBook(embed) {
|
_saveToDefaultNoteBook(embed) {
|
||||||
const notebookStorage = getDefaultNotebook();
|
const notebookStorage = getDefaultNotebook();
|
||||||
this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
|
this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
|
||||||
.then(async (domainObject) => {
|
.then(domainObject => {
|
||||||
addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
|
addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
|
||||||
|
|
||||||
let link = notebookStorage.notebookMeta.link;
|
|
||||||
|
|
||||||
// Backwards compatibility fix (old notebook model without link)
|
|
||||||
if (!link) {
|
|
||||||
link = await getDefaultNotebookLink(this.openmct, domainObject);
|
|
||||||
notebookStorage.notebookMeta.link = link;
|
|
||||||
setDefaultNotebook(this.openmct, notebookStorage);
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
|
const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
|
||||||
const msg = `Saved to Notebook ${defaultPath}`;
|
const msg = `Saved to Notebook ${defaultPath}`;
|
||||||
this._showNotification(msg, link);
|
this._showNotification(msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,29 +58,16 @@ export default class Snapshot {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_saveToNotebookSnapshots(embed) {
|
_saveToNotebookSnapshots(embed) {
|
||||||
this.snapshotContainer.addSnapshot(embed);
|
const saved = this.snapshotContainer.addSnapshot(embed);
|
||||||
|
if (!saved) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_showNotification(msg, url) {
|
const msg = 'Saved to Notebook Snapshots - click to view.';
|
||||||
const options = {
|
this._showNotification(msg);
|
||||||
autoDismissTimeout: 30000,
|
|
||||||
link: {
|
|
||||||
cssClass: '',
|
|
||||||
text: 'click to view',
|
|
||||||
onClick: this._navigateToNotebook(url)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.openmct.notifications.info(msg, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_navigateToNotebook(url = null) {
|
_showNotification(msg) {
|
||||||
if (!url) {
|
this.openmct.notifications.info(msg);
|
||||||
return () => {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.location.href = window.location.origin + url;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,30 +109,10 @@ const selectedPage = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let openmct;
|
let openmct;
|
||||||
let mockIdentifierService;
|
|
||||||
|
|
||||||
describe('Notebook Entries:', () => {
|
describe('Notebook Entries:', () => {
|
||||||
beforeEach(done => {
|
beforeEach(done => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
|
||||||
mockIdentifierService = jasmine.createSpyObj(
|
|
||||||
'identifierService',
|
|
||||||
['parse']
|
|
||||||
);
|
|
||||||
mockIdentifierService.parse.and.returnValue({
|
|
||||||
getSpace: () => {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.$injector.get.and.returnValue(mockIdentifierService);
|
|
||||||
openmct.types.addType('notebook', {
|
|
||||||
creatable: true
|
|
||||||
});
|
|
||||||
openmct.objects.addProvider('', jasmine.createSpyObj('mockNotebookProvider', [
|
|
||||||
'create',
|
|
||||||
'update'
|
|
||||||
]));
|
|
||||||
window.localStorage.setItem('notebook-storage', null);
|
window.localStorage.setItem('notebook-storage', null);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
@ -48,29 +48,14 @@ export function getDefaultNotebook() {
|
|||||||
return JSON.parse(notebookStorage);
|
return JSON.parse(notebookStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDefaultNotebookLink(openmct, domainObject = null) {
|
|
||||||
if (!domainObject) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = await openmct.objects.getOriginalPath(domainObject.identifier)
|
|
||||||
.then(objectPath => objectPath
|
|
||||||
.map(o => o && openmct.objects.makeKeyString(o.identifier))
|
|
||||||
.reverse()
|
|
||||||
.join('/')
|
|
||||||
);
|
|
||||||
const { page, section } = getDefaultNotebook();
|
|
||||||
|
|
||||||
return `#/browse/${path}?sectionId=${section.id}&pageId=${page.id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setDefaultNotebook(openmct, notebookStorage, domainObject) {
|
export function setDefaultNotebook(openmct, notebookStorage, domainObject) {
|
||||||
observeDefaultNotebookObject(openmct, notebookStorage, domainObject);
|
observeDefaultNotebookObject(openmct, notebookStorage.notebookMeta, domainObject);
|
||||||
saveDefaultNotebook(notebookStorage);
|
saveDefaultNotebook(notebookStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setDefaultNotebookSection(section) {
|
export function setDefaultNotebookSection(section) {
|
||||||
const notebookStorage = getDefaultNotebook();
|
const notebookStorage = getDefaultNotebook();
|
||||||
|
|
||||||
notebookStorage.section = section;
|
notebookStorage.section = section;
|
||||||
saveDefaultNotebook(notebookStorage);
|
saveDefaultNotebook(notebookStorage);
|
||||||
}
|
}
|
||||||
|
@ -62,10 +62,7 @@ describe('Notebook Storage:', () => {
|
|||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
window.localStorage.setItem('notebook-storage', null);
|
window.localStorage.setItem('notebook-storage', null);
|
||||||
openmct.objects.addProvider('', jasmine.createSpyObj('mockNotebookProvider', [
|
|
||||||
'create',
|
|
||||||
'update'
|
|
||||||
]));
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -27,10 +27,10 @@
|
|||||||
import NotificationsList from './NotificationsList.vue';
|
import NotificationsList from './NotificationsList.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
NotificationsList
|
NotificationsList
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
notifications: this.openmct.notifications.notifications,
|
notifications: this.openmct.notifications.notifications,
|
||||||
|
@ -25,12 +25,12 @@ import NotificationIndicator from './components/NotificationIndicator.vue';
|
|||||||
export default function plugin() {
|
export default function plugin() {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
let component = new Vue ({
|
let component = new Vue ({
|
||||||
components: {
|
|
||||||
NotificationIndicator: NotificationIndicator
|
|
||||||
},
|
|
||||||
provide: {
|
provide: {
|
||||||
openmct
|
openmct
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
NotificationIndicator: NotificationIndicator
|
||||||
|
},
|
||||||
template: '<NotificationIndicator></NotificationIndicator>'
|
template: '<NotificationIndicator></NotificationIndicator>'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,36 +25,13 @@ import CouchObjectQueue from "./CouchObjectQueue";
|
|||||||
|
|
||||||
const REV = "_rev";
|
const REV = "_rev";
|
||||||
const ID = "_id";
|
const ID = "_id";
|
||||||
const HEARTBEAT = 50000;
|
|
||||||
|
|
||||||
export default class CouchObjectProvider {
|
export default class CouchObjectProvider {
|
||||||
// options {
|
constructor(openmct, url, namespace) {
|
||||||
// url: couchdb url,
|
|
||||||
// disableObserve: disable auto feed from couchdb to keep objects in sync,
|
|
||||||
// filter: selector to find objects to sync in couchdb
|
|
||||||
// }
|
|
||||||
constructor(openmct, options, namespace) {
|
|
||||||
options = this._normalize(options);
|
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.url = options.url;
|
this.url = url;
|
||||||
this.namespace = namespace;
|
this.namespace = namespace;
|
||||||
this.objectQueue = {};
|
this.objectQueue = {};
|
||||||
this.observeEnabled = options.disableObserve !== true;
|
|
||||||
this.observers = {};
|
|
||||||
if (this.observeEnabled) {
|
|
||||||
this.observeObjectChanges(options.filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//backwards compatibility, options used to be a url. Now it's an object
|
|
||||||
_normalize(options) {
|
|
||||||
if (typeof options === 'string') {
|
|
||||||
return {
|
|
||||||
url: options
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request(subPath, method, value) {
|
request(subPath, method, value) {
|
||||||
@ -125,162 +102,6 @@ export default class CouchObjectProvider {
|
|||||||
return this.request(identifier.key, "GET").then(this.getModel.bind(this));
|
return this.request(identifier.key, "GET").then(this.getModel.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getObjectsByFilter(filter) {
|
|
||||||
let objects = [];
|
|
||||||
|
|
||||||
let url = `${this.url}/_find`;
|
|
||||||
let body = {};
|
|
||||||
|
|
||||||
if (filter) {
|
|
||||||
body = JSON.stringify(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body
|
|
||||||
});
|
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
|
||||||
let completed = false;
|
|
||||||
|
|
||||||
while (!completed) {
|
|
||||||
const {done, value} = await reader.read();
|
|
||||||
//done is true when we lose connection with the provider
|
|
||||||
if (done) {
|
|
||||||
completed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
let chunk = new Uint8Array(value.length);
|
|
||||||
chunk.set(value, 0);
|
|
||||||
const decodedChunk = new TextDecoder("utf-8").decode(chunk);
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(decodedChunk);
|
|
||||||
if (json) {
|
|
||||||
let docs = json.docs;
|
|
||||||
docs.forEach(doc => {
|
|
||||||
let object = this.getModel(doc);
|
|
||||||
if (object) {
|
|
||||||
objects.push(object);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
//do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
observe(identifier, callback) {
|
|
||||||
if (!this.observeEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyString = this.openmct.objects.makeKeyString(identifier);
|
|
||||||
this.observers[keyString] = this.observers[keyString] || [];
|
|
||||||
this.observers[keyString].push(callback);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
this.observers[keyString] = this.observers[keyString].filter(observer => observer !== callback);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
abortGetChanges() {
|
|
||||||
if (this.controller) {
|
|
||||||
this.controller.abort();
|
|
||||||
this.controller = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async observeObjectChanges(filter) {
|
|
||||||
let intermediateResponse = this.getIntermediateResponse();
|
|
||||||
|
|
||||||
if (!this.observeEnabled) {
|
|
||||||
intermediateResponse.reject('Observe for changes is disabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
const signal = controller.signal;
|
|
||||||
|
|
||||||
if (this.controller) {
|
|
||||||
this.abortGetChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.controller = controller;
|
|
||||||
// feed=continuous maintains an indefinitely open connection with a keep-alive of HEARTBEAT milliseconds until this client closes the connection
|
|
||||||
// style=main_only returns only the current winning revision of the document
|
|
||||||
let url = `${this.url}/_changes?feed=continuous&style=main_only&heartbeat=${HEARTBEAT}`;
|
|
||||||
|
|
||||||
let body = {};
|
|
||||||
if (filter) {
|
|
||||||
url = `${url}&filter=_selector`;
|
|
||||||
body = JSON.stringify(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
signal,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": 'application/json'
|
|
||||||
},
|
|
||||||
body
|
|
||||||
});
|
|
||||||
const reader = response.body.getReader();
|
|
||||||
let completed = false;
|
|
||||||
|
|
||||||
while (!completed) {
|
|
||||||
const {done, value} = await reader.read();
|
|
||||||
//done is true when we lose connection with the provider
|
|
||||||
if (done) {
|
|
||||||
completed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
let chunk = new Uint8Array(value.length);
|
|
||||||
chunk.set(value, 0);
|
|
||||||
const decodedChunk = new TextDecoder("utf-8").decode(chunk).split('\n');
|
|
||||||
if (decodedChunk.length && decodedChunk[decodedChunk.length - 1] === '') {
|
|
||||||
decodedChunk.forEach((doc, index) => {
|
|
||||||
try {
|
|
||||||
const object = JSON.parse(doc);
|
|
||||||
object.identifier = {
|
|
||||||
namespace: this.namespace,
|
|
||||||
key: object.id
|
|
||||||
};
|
|
||||||
let keyString = this.openmct.objects.makeKeyString(object.identifier);
|
|
||||||
let observersForObject = this.observers[keyString];
|
|
||||||
|
|
||||||
if (observersForObject) {
|
|
||||||
observersForObject.forEach(async (observer) => {
|
|
||||||
const updatedObject = await this.get(object.identifier);
|
|
||||||
observer(updatedObject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
//do nothing;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//We're done receiving from the provider. No more chunks.
|
|
||||||
intermediateResponse.resolve(true);
|
|
||||||
|
|
||||||
return intermediateResponse.promise;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
getIntermediateResponse() {
|
getIntermediateResponse() {
|
||||||
let intermediateResponse = {};
|
let intermediateResponse = {};
|
||||||
intermediateResponse.promise = new Promise(function (resolve, reject) {
|
intermediateResponse.promise = new Promise(function (resolve, reject) {
|
||||||
|
@ -24,9 +24,8 @@ import CouchObjectProvider from './CouchObjectProvider';
|
|||||||
const NAMESPACE = '';
|
const NAMESPACE = '';
|
||||||
const PERSISTENCE_SPACE = 'mct';
|
const PERSISTENCE_SPACE = 'mct';
|
||||||
|
|
||||||
export default function CouchPlugin(options) {
|
export default function CouchPlugin(url) {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
install.couchProvider = new CouchObjectProvider(openmct, options, NAMESPACE);
|
openmct.objects.addProvider(PERSISTENCE_SPACE, new CouchObjectProvider(openmct, url, NAMESPACE));
|
||||||
openmct.objects.addProvider(PERSISTENCE_SPACE, install.couchProvider);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -32,43 +32,17 @@ describe('the plugin', () => {
|
|||||||
let child;
|
let child;
|
||||||
let provider;
|
let provider;
|
||||||
let testPath = '/test/db';
|
let testPath = '/test/db';
|
||||||
let options;
|
|
||||||
let mockIdentifierService;
|
|
||||||
let mockDomainObject;
|
let mockDomainObject;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
mockDomainObject = {
|
mockDomainObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
namespace: '',
|
namespace: 'mct',
|
||||||
key: 'some-value'
|
key: 'some-value'
|
||||||
},
|
}
|
||||||
type: 'mock-type'
|
|
||||||
};
|
|
||||||
options = {
|
|
||||||
url: testPath,
|
|
||||||
filter: {},
|
|
||||||
disableObserve: true
|
|
||||||
};
|
};
|
||||||
openmct = createOpenMct(false);
|
openmct = createOpenMct(false);
|
||||||
|
openmct.install(new CouchPlugin(testPath));
|
||||||
spyOnBuiltins(['fetch'], window);
|
|
||||||
|
|
||||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
|
||||||
mockIdentifierService = jasmine.createSpyObj(
|
|
||||||
'identifierService',
|
|
||||||
['parse']
|
|
||||||
);
|
|
||||||
mockIdentifierService.parse.and.returnValue({
|
|
||||||
getSpace: () => {
|
|
||||||
return 'mct';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.$injector.get.and.returnValue(mockIdentifierService);
|
|
||||||
|
|
||||||
openmct.install(new CouchPlugin(options));
|
|
||||||
|
|
||||||
openmct.types.addType('mock-type', {creatable: true});
|
|
||||||
|
|
||||||
element = document.createElement('div');
|
element = document.createElement('div');
|
||||||
child = document.createElement('div');
|
child = document.createElement('div');
|
||||||
@ -81,16 +55,9 @@ describe('the plugin', () => {
|
|||||||
spyOn(provider, 'get').and.callThrough();
|
spyOn(provider, 'get').and.callThrough();
|
||||||
spyOn(provider, 'create').and.callThrough();
|
spyOn(provider, 'create').and.callThrough();
|
||||||
spyOn(provider, 'update').and.callThrough();
|
spyOn(provider, 'update').and.callThrough();
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
spyOnBuiltins(['fetch'], window);
|
||||||
return resetApplicationState(openmct);
|
fetch.and.returnValue(Promise.resolve({
|
||||||
});
|
|
||||||
|
|
||||||
describe('the provider', () => {
|
|
||||||
let mockPromise;
|
|
||||||
beforeEach(() => {
|
|
||||||
mockPromise = Promise.resolve({
|
|
||||||
json: () => {
|
json: () => {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -99,8 +66,11 @@ describe('the plugin', () => {
|
|||||||
model: {}
|
model: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
fetch.and.returnValue(mockPromise);
|
|
||||||
|
afterEach(() => {
|
||||||
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets an object', () => {
|
it('gets an object', () => {
|
||||||
@ -128,7 +98,7 @@ describe('the plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates queued objects', () => {
|
it('updates queued objects', () => {
|
||||||
let couchProvider = new CouchObjectProvider(openmct, options, '');
|
let couchProvider = new CouchObjectProvider(openmct, 'http://localhost', '');
|
||||||
let intermediateResponse = couchProvider.getIntermediateResponse();
|
let intermediateResponse = couchProvider.getIntermediateResponse();
|
||||||
spyOn(couchProvider, 'updateQueued');
|
spyOn(couchProvider, 'updateQueued');
|
||||||
couchProvider.enqueueObject(mockDomainObject.identifier.key, mockDomainObject, intermediateResponse);
|
couchProvider.enqueueObject(mockDomainObject.identifier.key, mockDomainObject, intermediateResponse);
|
||||||
@ -143,5 +113,4 @@ describe('the plugin', () => {
|
|||||||
|
|
||||||
expect(couchProvider.updateQueued).toHaveBeenCalledTimes(2);
|
expect(couchProvider.updateQueued).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -136,7 +136,7 @@ define([
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (objectPath && (typeof objectPath !== "function")) {
|
if (objectPath && !_.isFunction(objectPath)) {
|
||||||
const staticObjectPath = objectPath;
|
const staticObjectPath = objectPath;
|
||||||
objectPath = function () {
|
objectPath = function () {
|
||||||
return staticObjectPath;
|
return staticObjectPath;
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 Plot from '../single/Plot.vue';
|
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
export default function OverlayPlotViewProvider(openmct) {
|
|
||||||
return {
|
|
||||||
key: 'plot-overlay',
|
|
||||||
name: 'Overlay Plot',
|
|
||||||
cssClass: 'icon-telemetry',
|
|
||||||
canView(domainObject) {
|
|
||||||
return domainObject.type === 'telemetry.plot.overlay';
|
|
||||||
},
|
|
||||||
|
|
||||||
canEdit(domainObject) {
|
|
||||||
return domainObject.type === 'telemetry.plot.overlay';
|
|
||||||
},
|
|
||||||
|
|
||||||
view: function (domainObject) {
|
|
||||||
let component;
|
|
||||||
|
|
||||||
return {
|
|
||||||
show: function (element) {
|
|
||||||
component = new Vue({
|
|
||||||
el: element,
|
|
||||||
components: {
|
|
||||||
Plot
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
openmct,
|
|
||||||
domainObject
|
|
||||||
},
|
|
||||||
template: '<plot></plot>'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroy: function () {
|
|
||||||
component.$destroy();
|
|
||||||
component = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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.
|
|
||||||
*****************************************************************************/
|
|
||||||
/*jscs:disable disallowDanglingUnderscores */
|
|
||||||
/**
|
|
||||||
* A scale has an input domain and an output range. It provides functions
|
|
||||||
* `scale` return the range value associated with a domain value.
|
|
||||||
* `invert` return the domain value associated with range value.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class LinearScale {
|
|
||||||
constructor(domain) {
|
|
||||||
this.domain(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
domain(newDomain) {
|
|
||||||
if (newDomain) {
|
|
||||||
this._domain = newDomain;
|
|
||||||
this._domainDenominator = newDomain.max - newDomain.min;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
range(newRange) {
|
|
||||||
if (newRange) {
|
|
||||||
this._range = newRange;
|
|
||||||
this._rangeDenominator = newRange.max - newRange.min;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._range;
|
|
||||||
}
|
|
||||||
|
|
||||||
scale(domainValue) {
|
|
||||||
if (!this._domain || !this._range) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const domainOffset = domainValue - this._domain.min;
|
|
||||||
const rangeFraction = domainOffset - this._domainDenominator;
|
|
||||||
const rangeOffset = rangeFraction * this._rangeDenominator;
|
|
||||||
const rangeValue = rangeOffset + this._range.min;
|
|
||||||
|
|
||||||
return rangeValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
invert(rangeValue) {
|
|
||||||
if (!this._domain || !this._range) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rangeOffset = rangeValue - this._range.min;
|
|
||||||
const domainFraction = rangeOffset / this._rangeDenominator;
|
|
||||||
const domainOffset = domainFraction * this._domainDenominator;
|
|
||||||
const domainValue = domainOffset + this._domain.min;
|
|
||||||
|
|
||||||
return domainValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LinearScale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
@ -1,892 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2020, 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.
|
|
||||||
-->
|
|
||||||
<template>
|
|
||||||
<div v-if="loaded"
|
|
||||||
class="gl-plot"
|
|
||||||
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
|
|
||||||
>
|
|
||||||
<plot-legend :cursor-locked="!!lockHighlightPoint"
|
|
||||||
:series="config.series.models"
|
|
||||||
:highlights="highlights"
|
|
||||||
:legend="config.legend"
|
|
||||||
/>
|
|
||||||
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
|
||||||
<y-axis v-if="config.series.models.length > 0"
|
|
||||||
:tick-width="tickWidth"
|
|
||||||
:single-series="config.series.models.length === 1"
|
|
||||||
:series-model="config.series.models[0]"
|
|
||||||
@yKeyChanged="setYAxisKey"
|
|
||||||
@tickWidthChanged="onTickWidthChange"
|
|
||||||
/>
|
|
||||||
<div class="gl-plot-wrapper-display-area-and-x-axis"
|
|
||||||
:style="{
|
|
||||||
left: (tickWidth + 20) + 'px'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
|
|
||||||
<div class="gl-plot-display-area has-local-controls has-cursor-guides">
|
|
||||||
<div class="l-state-indicators">
|
|
||||||
<span class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
|
|
||||||
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mct-ticks v-show="gridLines"
|
|
||||||
:axis-type="'xAxis'"
|
|
||||||
:position="'right'"
|
|
||||||
@plotTickWidth="onTickWidthChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<mct-ticks v-show="gridLines"
|
|
||||||
:axis-type="'yAxis'"
|
|
||||||
:position="'bottom'"
|
|
||||||
@plotTickWidth="onTickWidthChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div ref="chartContainer"
|
|
||||||
class="gl-plot-chart-wrapper"
|
|
||||||
>
|
|
||||||
<mct-chart :series-config="config"
|
|
||||||
:rectangles="rectangles"
|
|
||||||
:highlights="highlights"
|
|
||||||
@plotReinitializeCanvas="initCanvas"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover">
|
|
||||||
<div class="c-button-set c-button-set--strip-h">
|
|
||||||
<button class="c-button icon-minus"
|
|
||||||
title="Zoom out"
|
|
||||||
@click="zoom('out', 0.2)"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
<button class="c-button icon-plus"
|
|
||||||
title="Zoom in"
|
|
||||||
@click="zoom('in', 0.2)"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="c-button-set c-button-set--strip-h"
|
|
||||||
:disabled="!plotHistory.length"
|
|
||||||
>
|
|
||||||
<button class="c-button icon-arrow-left"
|
|
||||||
title="Restore previous pan/zoom"
|
|
||||||
@click="back()"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
<button class="c-button icon-reset"
|
|
||||||
title="Reset pan/zoom"
|
|
||||||
@click="clear()"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--Cursor guides-->
|
|
||||||
<div v-show="cursorGuide"
|
|
||||||
ref="cursorGuideVertical"
|
|
||||||
class="c-cursor-guide--v js-cursor-guide--v"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div v-show="cursorGuide"
|
|
||||||
ref="cursorGuideHorizontal"
|
|
||||||
class="c-cursor-guide--h js-cursor-guide--h"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<x-axis v-if="config.series.models.length > 0"
|
|
||||||
:series-model="config.series.models[0]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import eventHelpers from './lib/eventHelpers';
|
|
||||||
import LinearScale from "./LinearScale";
|
|
||||||
import PlotConfigurationModel from './configuration/PlotConfigurationModel';
|
|
||||||
import configStore from './configuration/configStore';
|
|
||||||
|
|
||||||
import PlotLegend from "./legend/PlotLegend.vue";
|
|
||||||
import MctTicks from "./MctTicks.vue";
|
|
||||||
import MctChart from "./chart/MctChart.vue";
|
|
||||||
import XAxis from "./axis/XAxis.vue";
|
|
||||||
import YAxis from "./axis/YAxis.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
XAxis,
|
|
||||||
YAxis,
|
|
||||||
PlotLegend,
|
|
||||||
MctTicks,
|
|
||||||
MctChart
|
|
||||||
},
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
props: {
|
|
||||||
gridLines: {
|
|
||||||
type: Boolean,
|
|
||||||
default() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cursorGuide: {
|
|
||||||
type: Boolean,
|
|
||||||
default() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plotTickWidth: {
|
|
||||||
type: Number,
|
|
||||||
default() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
highlights: [],
|
|
||||||
lockHighlightPoint: false,
|
|
||||||
tickWidth: 0,
|
|
||||||
yKeyOptions: [],
|
|
||||||
yAxisLabel: '',
|
|
||||||
rectangles: [],
|
|
||||||
plotHistory: [],
|
|
||||||
selectedXKeyOption: {},
|
|
||||||
xKeyOptions: [],
|
|
||||||
config: {},
|
|
||||||
pending: 0,
|
|
||||||
loaded: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
plotLegendPositionClass() {
|
|
||||||
return `plot-legend-${this.config.legend.get('position')}`;
|
|
||||||
},
|
|
||||||
plotLegendExpandedStateClass() {
|
|
||||||
if (this.config.legend.get('expanded')) {
|
|
||||||
return 'plot-legend-expanded';
|
|
||||||
} else {
|
|
||||||
return 'plot-legend-collapsed';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
plotTickWidth(newTickWidth) {
|
|
||||||
this.onTickWidthChange(newTickWidth, true);
|
|
||||||
},
|
|
||||||
gridLines(newGridLines) {
|
|
||||||
this.setGridLinesVisibility(newGridLines);
|
|
||||||
},
|
|
||||||
cursorGuide(newCursorGuide) {
|
|
||||||
this.setCursorGuideVisibility(newCursorGuide);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
eventHelpers.extend(this);
|
|
||||||
|
|
||||||
this.config = this.getConfig();
|
|
||||||
|
|
||||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
|
||||||
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
|
|
||||||
|
|
||||||
this.config.series.models.forEach(this.addSeries, this);
|
|
||||||
|
|
||||||
this.filterObserver = this.openmct.objects.observe(
|
|
||||||
this.domainObject,
|
|
||||||
'configuration.filters',
|
|
||||||
this.updateFiltersAndResubscribe
|
|
||||||
);
|
|
||||||
|
|
||||||
this.openmct.objectViews.on('clearData', this.clearData);
|
|
||||||
this.followTimeConductor();
|
|
||||||
|
|
||||||
this.loaded = true;
|
|
||||||
|
|
||||||
//We're referencing the canvas elements from the mct-chart in the initialize method.
|
|
||||||
// So we need $nextTick to ensure the component is fully mounted before we can initialize stuff.
|
|
||||||
this.$nextTick(this.initialize);
|
|
||||||
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.destroy();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
followTimeConductor() {
|
|
||||||
this.openmct.time.on('bounds', this.updateDisplayBounds);
|
|
||||||
this.synchronized(true);
|
|
||||||
},
|
|
||||||
getConfig() {
|
|
||||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
|
||||||
let config = configStore.get(configId);
|
|
||||||
if (!config) {
|
|
||||||
config = new PlotConfigurationModel({
|
|
||||||
id: configId,
|
|
||||||
domainObject: this.domainObject,
|
|
||||||
openmct: this.openmct
|
|
||||||
});
|
|
||||||
configStore.add(configId, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
addSeries(series) {
|
|
||||||
this.listenTo(series, 'change:xKey', (xKey) => {
|
|
||||||
this.setDisplayRange(series, xKey);
|
|
||||||
}, this);
|
|
||||||
this.listenTo(series, 'change:yKey', () => {
|
|
||||||
this.loadSeriesData(series);
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this.listenTo(series, 'change:interpolate', () => {
|
|
||||||
this.loadSeriesData(series);
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this.loadSeriesData(series);
|
|
||||||
},
|
|
||||||
|
|
||||||
removeSeries(plotSeries) {
|
|
||||||
this.stopListening(plotSeries);
|
|
||||||
},
|
|
||||||
|
|
||||||
loadSeriesData(series) {
|
|
||||||
if (this.$parent.$refs.plotWrapper.offsetWidth === 0) {
|
|
||||||
this.scheduleLoad(series);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.startLoading();
|
|
||||||
const options = {
|
|
||||||
size: this.$parent.$refs.plotWrapper.offsetWidth,
|
|
||||||
domain: this.config.xAxis.get('key')
|
|
||||||
};
|
|
||||||
|
|
||||||
series.load(options)
|
|
||||||
.then(this.stopLoading.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
loadMoreData(range, purge) {
|
|
||||||
this.config.series.forEach(plotSeries => {
|
|
||||||
this.startLoading();
|
|
||||||
plotSeries.load({
|
|
||||||
size: this.$parent.$refs.plotWrapper.offsetWidth,
|
|
||||||
start: range.min,
|
|
||||||
end: range.max,
|
|
||||||
domain: this.config.xAxis.get('key')
|
|
||||||
})
|
|
||||||
.then(this.stopLoading());
|
|
||||||
if (purge) {
|
|
||||||
plotSeries.purgeRecordsOutsideRange(range);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
scheduleLoad(series) {
|
|
||||||
if (!this.scheduledLoads) {
|
|
||||||
this.startLoading();
|
|
||||||
this.scheduledLoads = [];
|
|
||||||
this.checkForSize = setInterval(function () {
|
|
||||||
if (this.$parent.$refs.plotWrapper.offsetWidth === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stopLoading();
|
|
||||||
this.scheduledLoads.forEach(this.loadSeriesData, this);
|
|
||||||
delete this.scheduledLoads;
|
|
||||||
clearInterval(this.checkForSize);
|
|
||||||
delete this.checkForSize;
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.scheduledLoads.indexOf(series) === -1) {
|
|
||||||
this.scheduledLoads.push(series);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
startLoading() {
|
|
||||||
this.pending += 1;
|
|
||||||
this.updateLoading();
|
|
||||||
},
|
|
||||||
|
|
||||||
stopLoading() {
|
|
||||||
//TODO: Is Vue.$nextTick ok to replace $scope.$evalAsync?
|
|
||||||
this.$nextTick().then(() => {
|
|
||||||
this.pending -= 1;
|
|
||||||
this.updateLoading();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateLoading() {
|
|
||||||
this.$emit('loadingUpdated', this.pending > 0);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFiltersAndResubscribe(updatedFilters) {
|
|
||||||
this.config.series.forEach(function (series) {
|
|
||||||
series.updateFiltersAndRefresh(updatedFilters[series.keyString]);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
clearData() {
|
|
||||||
this.config.series.forEach(function (series) {
|
|
||||||
series.reset();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
setDisplayRange(series, xKey) {
|
|
||||||
if (this.config.series.length !== 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayRange = series.getDisplayRange(xKey);
|
|
||||||
this.config.xAxis.set('range', displayRange);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track latest display bounds. Forces update when not receiving ticks.
|
|
||||||
*/
|
|
||||||
updateDisplayBounds(bounds, isTick) {
|
|
||||||
const newRange = {
|
|
||||||
min: bounds.start,
|
|
||||||
max: bounds.end
|
|
||||||
};
|
|
||||||
this.config.xAxis.set('range', newRange);
|
|
||||||
if (!isTick) {
|
|
||||||
this.skipReloadOnInteraction = true;
|
|
||||||
this.clear();
|
|
||||||
this.skipReloadOnInteraction = false;
|
|
||||||
this.loadMoreData(newRange, true);
|
|
||||||
} else {
|
|
||||||
// Drop any data that is more than 1x (max-min) before min.
|
|
||||||
// Limit these purges to once a second.
|
|
||||||
if (!this.nextPurge || this.nextPurge < Date.now()) {
|
|
||||||
const keepRange = {
|
|
||||||
min: newRange.min - (newRange.max - newRange.min),
|
|
||||||
max: newRange.max
|
|
||||||
};
|
|
||||||
this.config.series.forEach(function (series) {
|
|
||||||
series.purgeRecordsOutsideRange(keepRange);
|
|
||||||
});
|
|
||||||
this.nextPurge = Date.now() + 1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle end of user viewport change: load more data for current display
|
|
||||||
* bounds, and mark view as synchronized if bounds match configured bounds.
|
|
||||||
*/
|
|
||||||
userViewportChangeEnd() {
|
|
||||||
const xDisplayRange = this.config.xAxis.get('displayRange');
|
|
||||||
const xRange = this.config.xAxis.get('range');
|
|
||||||
|
|
||||||
if (!this.skipReloadOnInteraction) {
|
|
||||||
this.loadMoreData(xDisplayRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.synchronized(xRange.min === xDisplayRange.min
|
|
||||||
&& xRange.max === xDisplayRange.max);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter/setter for "synchronized" value. If not synchronized and
|
|
||||||
* time conductor is in clock mode, will mark objects as unsynced so that
|
|
||||||
* displays can update accordingly.
|
|
||||||
*/
|
|
||||||
synchronized(value) {
|
|
||||||
if (typeof value !== 'undefined') {
|
|
||||||
this._synchronized = value;
|
|
||||||
const isUnsynced = !value && this.openmct.time.clock();
|
|
||||||
const domainObject = this.openmct.legacyObject(this.domainObject);
|
|
||||||
if (domainObject.getCapability('status')) {
|
|
||||||
domainObject.getCapability('status')
|
|
||||||
.set('timeconductor-unsynced', isUnsynced);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._synchronized;
|
|
||||||
},
|
|
||||||
|
|
||||||
initCanvas() {
|
|
||||||
if (this.canvas) {
|
|
||||||
this.stopListening(this.canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.canvas = this.$refs.chartContainer.querySelectorAll('canvas')[1];
|
|
||||||
|
|
||||||
this.listenTo(this.canvas, 'mousemove', this.trackMousePosition, this);
|
|
||||||
this.listenTo(this.canvas, 'mouseleave', this.untrackMousePosition, this);
|
|
||||||
this.listenTo(this.canvas, 'mousedown', this.onMouseDown, this);
|
|
||||||
this.listenTo(this.canvas, 'wheel', this.wheelZoom, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
// Setup canvas etc.
|
|
||||||
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
|
|
||||||
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
|
|
||||||
|
|
||||||
this.pan = undefined;
|
|
||||||
this.marquee = undefined;
|
|
||||||
|
|
||||||
this.chartElementBounds = undefined;
|
|
||||||
this.tickUpdate = false;
|
|
||||||
|
|
||||||
this.canvas = this.$refs.chartContainer.querySelectorAll('canvas')[1];
|
|
||||||
|
|
||||||
this.listenTo(this.canvas, 'mousemove', this.trackMousePosition, this);
|
|
||||||
this.listenTo(this.canvas, 'mouseleave', this.untrackMousePosition, this);
|
|
||||||
this.listenTo(this.canvas, 'mousedown', this.onMouseDown, this);
|
|
||||||
this.listenTo(this.canvas, 'wheel', this.wheelZoom, this);
|
|
||||||
|
|
||||||
this.config.yAxisLabel = this.config.yAxis.get('label');
|
|
||||||
|
|
||||||
this.cursorGuideVertical = this.$refs.cursorGuideVertical;
|
|
||||||
this.cursorGuideHorizontal = this.$refs.cursorGuideHorizontal;
|
|
||||||
|
|
||||||
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
|
|
||||||
this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
onXAxisChange(displayBounds) {
|
|
||||||
if (displayBounds) {
|
|
||||||
this.xScale.domain(displayBounds);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onYAxisChange(displayBounds) {
|
|
||||||
if (displayBounds) {
|
|
||||||
this.yScale.domain(displayBounds);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onTickWidthChange(width, fromDifferentObject) {
|
|
||||||
if (fromDifferentObject) {
|
|
||||||
// Always accept tick width if it comes from a different object.
|
|
||||||
this.tickWidth = width;
|
|
||||||
} else {
|
|
||||||
// Otherwise, only accept tick with if it's larger.
|
|
||||||
const newWidth = Math.max(width, this.tickWidth);
|
|
||||||
if (newWidth !== this.tickWidth) {
|
|
||||||
this.tickWidth = newWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit('plotTickWidth', this.tickWidth);
|
|
||||||
},
|
|
||||||
|
|
||||||
trackMousePosition(event) {
|
|
||||||
this.trackChartElementBounds(event);
|
|
||||||
this.xScale.range({
|
|
||||||
min: 0,
|
|
||||||
max: this.chartElementBounds.width
|
|
||||||
});
|
|
||||||
this.yScale.range({
|
|
||||||
min: 0,
|
|
||||||
max: this.chartElementBounds.height
|
|
||||||
});
|
|
||||||
|
|
||||||
this.positionOverElement = {
|
|
||||||
x: event.clientX - this.chartElementBounds.left,
|
|
||||||
y: this.chartElementBounds.height
|
|
||||||
- (event.clientY - this.chartElementBounds.top)
|
|
||||||
};
|
|
||||||
|
|
||||||
this.positionOverPlot = {
|
|
||||||
x: this.xScale.invert(this.positionOverElement.x),
|
|
||||||
y: this.yScale.invert(this.positionOverElement.y)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.cursorGuide) {
|
|
||||||
this.updateCrosshairs(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.highlightValues(this.positionOverPlot.x);
|
|
||||||
this.updateMarquee();
|
|
||||||
this.updatePan();
|
|
||||||
event.preventDefault();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateCrosshairs(event) {
|
|
||||||
this.cursorGuideVertical.style.left = (event.clientX - this.chartElementBounds.x) + 'px';
|
|
||||||
this.cursorGuideHorizontal.style.top = (event.clientY - this.chartElementBounds.y) + 'px';
|
|
||||||
},
|
|
||||||
|
|
||||||
trackChartElementBounds(event) {
|
|
||||||
if (event.target === this.canvas) {
|
|
||||||
this.chartElementBounds = event.target.getBoundingClientRect();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onPlotHighlightSet($e, point) {
|
|
||||||
if (point === this.highlightPoint) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.highlightValues(point);
|
|
||||||
},
|
|
||||||
|
|
||||||
highlightValues(point) {
|
|
||||||
this.highlightPoint = point;
|
|
||||||
// TODO: used in StackedPlotController
|
|
||||||
this.$emit('plotHighlightUpdate', point);
|
|
||||||
if (this.lockHighlightPoint) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!point) {
|
|
||||||
this.highlights = [];
|
|
||||||
this.config.series.models.forEach(series => delete series.closest);
|
|
||||||
} else {
|
|
||||||
this.highlights = this.config.series.models
|
|
||||||
.filter(series => series.data.length > 0)
|
|
||||||
.map(series => {
|
|
||||||
series.closest = series.nearestPoint(point);
|
|
||||||
|
|
||||||
return {
|
|
||||||
series: series,
|
|
||||||
point: series.closest
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
untrackMousePosition() {
|
|
||||||
this.positionOverElement = undefined;
|
|
||||||
this.positionOverPlot = undefined;
|
|
||||||
this.highlightValues();
|
|
||||||
},
|
|
||||||
|
|
||||||
onMouseDown(event) {
|
|
||||||
// do not monitor drag events on browser context click
|
|
||||||
if (event.ctrlKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listenTo(window, 'mouseup', this.onMouseUp, this);
|
|
||||||
this.listenTo(window, 'mousemove', this.trackMousePosition, this);
|
|
||||||
if (event.altKey) {
|
|
||||||
return this.startPan(event);
|
|
||||||
} else {
|
|
||||||
return this.startMarquee(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onMouseUp(event) {
|
|
||||||
this.stopListening(window, 'mouseup', this.onMouseUp, this);
|
|
||||||
this.stopListening(window, 'mousemove', this.trackMousePosition, this);
|
|
||||||
|
|
||||||
if (this.isMouseClick()) {
|
|
||||||
this.lockHighlightPoint = !this.lockHighlightPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pan) {
|
|
||||||
return this.endPan(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.marquee) {
|
|
||||||
return this.endMarquee(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isMouseClick() {
|
|
||||||
if (!this.marquee) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { start, end } = this.marquee;
|
|
||||||
|
|
||||||
return start.x === end.x && start.y === end.y;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateMarquee() {
|
|
||||||
if (!this.marquee) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.marquee.end = this.positionOverPlot;
|
|
||||||
this.marquee.endPixels = this.positionOverElement;
|
|
||||||
},
|
|
||||||
|
|
||||||
startMarquee(event) {
|
|
||||||
this.canvas.classList.remove('plot-drag');
|
|
||||||
this.canvas.classList.add('plot-marquee');
|
|
||||||
|
|
||||||
this.trackMousePosition(event);
|
|
||||||
if (this.positionOverPlot) {
|
|
||||||
this.freeze();
|
|
||||||
this.marquee = {
|
|
||||||
startPixels: this.positionOverElement,
|
|
||||||
endPixels: this.positionOverElement,
|
|
||||||
start: this.positionOverPlot,
|
|
||||||
end: this.positionOverPlot,
|
|
||||||
color: [1, 1, 1, 0.5]
|
|
||||||
};
|
|
||||||
this.rectangles.push(this.marquee);
|
|
||||||
this.trackHistory();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
endMarquee() {
|
|
||||||
const startPixels = this.marquee.startPixels;
|
|
||||||
const endPixels = this.marquee.endPixels;
|
|
||||||
const marqueeDistance = Math.sqrt(
|
|
||||||
Math.pow(startPixels.x - endPixels.x, 2)
|
|
||||||
+ Math.pow(startPixels.y - endPixels.y, 2)
|
|
||||||
);
|
|
||||||
// Don't zoom if mouse moved less than 7.5 pixels.
|
|
||||||
if (marqueeDistance > 7.5) {
|
|
||||||
this.config.xAxis.set('displayRange', {
|
|
||||||
min: Math.min(this.marquee.start.x, this.marquee.end.x),
|
|
||||||
max: Math.max(this.marquee.start.x, this.marquee.end.x)
|
|
||||||
});
|
|
||||||
this.config.yAxis.set('displayRange', {
|
|
||||||
min: Math.min(this.marquee.start.y, this.marquee.end.y),
|
|
||||||
max: Math.max(this.marquee.start.y, this.marquee.end.y)
|
|
||||||
});
|
|
||||||
this.userViewportChangeEnd();
|
|
||||||
} else {
|
|
||||||
// A history entry is created by startMarquee, need to remove
|
|
||||||
// if marquee zoom doesn't occur.
|
|
||||||
this.plotHistory.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.rectangles = [];
|
|
||||||
this.marquee = undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
zoom(zoomDirection, zoomFactor) {
|
|
||||||
const currentXaxis = this.config.xAxis.get('displayRange');
|
|
||||||
const currentYaxis = this.config.yAxis.get('displayRange');
|
|
||||||
|
|
||||||
// when there is no plot data, the ranges can be undefined
|
|
||||||
// in which case we should not perform zoom
|
|
||||||
if (!currentXaxis || !currentYaxis) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.freeze();
|
|
||||||
this.trackHistory();
|
|
||||||
|
|
||||||
const xAxisDist = (currentXaxis.max - currentXaxis.min) * zoomFactor;
|
|
||||||
const yAxisDist = (currentYaxis.max - currentYaxis.min) * zoomFactor;
|
|
||||||
|
|
||||||
if (zoomDirection === 'in') {
|
|
||||||
this.config.xAxis.set('displayRange', {
|
|
||||||
min: currentXaxis.min + xAxisDist,
|
|
||||||
max: currentXaxis.max - xAxisDist
|
|
||||||
});
|
|
||||||
|
|
||||||
this.config.yAxis.set('displayRange', {
|
|
||||||
min: currentYaxis.min + yAxisDist,
|
|
||||||
max: currentYaxis.max - yAxisDist
|
|
||||||
});
|
|
||||||
} else if (zoomDirection === 'out') {
|
|
||||||
this.config.xAxis.set('displayRange', {
|
|
||||||
min: currentXaxis.min - xAxisDist,
|
|
||||||
max: currentXaxis.max + xAxisDist
|
|
||||||
});
|
|
||||||
|
|
||||||
this.config.yAxis.set('displayRange', {
|
|
||||||
min: currentYaxis.min - yAxisDist,
|
|
||||||
max: currentYaxis.max + yAxisDist
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.userViewportChangeEnd();
|
|
||||||
},
|
|
||||||
|
|
||||||
wheelZoom(event) {
|
|
||||||
const ZOOM_AMT = 0.1;
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (!this.positionOverPlot) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let xDisplayRange = this.config.xAxis.get('displayRange');
|
|
||||||
let yDisplayRange = this.config.yAxis.get('displayRange');
|
|
||||||
|
|
||||||
// when there is no plot data, the ranges can be undefined
|
|
||||||
// in which case we should not perform zoom
|
|
||||||
if (!xDisplayRange || !yDisplayRange) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.freeze();
|
|
||||||
window.clearTimeout(this.stillZooming);
|
|
||||||
|
|
||||||
let xAxisDist = (xDisplayRange.max - xDisplayRange.min);
|
|
||||||
let yAxisDist = (yDisplayRange.max - yDisplayRange.min);
|
|
||||||
let xDistMouseToMax = xDisplayRange.max - this.positionOverPlot.x;
|
|
||||||
let xDistMouseToMin = this.positionOverPlot.x - xDisplayRange.min;
|
|
||||||
let yDistMouseToMax = yDisplayRange.max - this.positionOverPlot.y;
|
|
||||||
let yDistMouseToMin = this.positionOverPlot.y - yDisplayRange.min;
|
|
||||||
let xAxisMaxDist = xDistMouseToMax / xAxisDist;
|
|
||||||
let xAxisMinDist = xDistMouseToMin / xAxisDist;
|
|
||||||
let yAxisMaxDist = yDistMouseToMax / yAxisDist;
|
|
||||||
let yAxisMinDist = yDistMouseToMin / yAxisDist;
|
|
||||||
|
|
||||||
let plotHistoryStep;
|
|
||||||
|
|
||||||
if (!plotHistoryStep) {
|
|
||||||
plotHistoryStep = {
|
|
||||||
x: xDisplayRange,
|
|
||||||
y: yDisplayRange
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.wheelDelta < 0) {
|
|
||||||
|
|
||||||
this.config.xAxis.set('displayRange', {
|
|
||||||
min: xDisplayRange.min + ((xAxisDist * ZOOM_AMT) * xAxisMinDist),
|
|
||||||
max: xDisplayRange.max - ((xAxisDist * ZOOM_AMT) * xAxisMaxDist)
|
|
||||||
});
|
|
||||||
|
|
||||||
this.config.yAxis.set('displayRange', {
|
|
||||||
min: yDisplayRange.min + ((yAxisDist * ZOOM_AMT) * yAxisMinDist),
|
|
||||||
max: yDisplayRange.max - ((yAxisDist * ZOOM_AMT) * yAxisMaxDist)
|
|
||||||
});
|
|
||||||
} else if (event.wheelDelta >= 0) {
|
|
||||||
|
|
||||||
this.config.xAxis.set('displayRange', {
|
|
||||||
min: xDisplayRange.min - ((xAxisDist * ZOOM_AMT) * xAxisMinDist),
|
|
||||||
max: xDisplayRange.max + ((xAxisDist * ZOOM_AMT) * xAxisMaxDist)
|
|
||||||
});
|
|
||||||
|
|
||||||
this.config.yAxis.set('displayRange', {
|
|
||||||
min: yDisplayRange.min - ((yAxisDist * ZOOM_AMT) * yAxisMinDist),
|
|
||||||
max: yDisplayRange.max + ((yAxisDist * ZOOM_AMT) * yAxisMaxDist)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stillZooming = window.setTimeout(function () {
|
|
||||||
this.plotHistory.push(plotHistoryStep);
|
|
||||||
plotHistoryStep = undefined;
|
|
||||||
this.userViewportChangeEnd();
|
|
||||||
}.bind(this), 250);
|
|
||||||
},
|
|
||||||
|
|
||||||
startPan(event) {
|
|
||||||
this.canvas.classList.add('plot-drag');
|
|
||||||
this.canvas.classList.remove('plot-marquee');
|
|
||||||
|
|
||||||
this.trackMousePosition(event);
|
|
||||||
this.freeze();
|
|
||||||
this.pan = {
|
|
||||||
start: this.positionOverPlot
|
|
||||||
};
|
|
||||||
event.preventDefault();
|
|
||||||
this.trackHistory();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
updatePan() {
|
|
||||||
// calculate offset between points. Apply that offset to viewport.
|
|
||||||
if (!this.pan) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dX = this.pan.start.x - this.positionOverPlot.x;
|
|
||||||
const dY = this.pan.start.y - this.positionOverPlot.y;
|
|
||||||
const xRange = this.config.xAxis.get('displayRange');
|
|
||||||
const yRange = this.config.yAxis.get('displayRange');
|
|
||||||
|
|
||||||
this.config.xAxis.set('displayRange', {
|
|
||||||
min: xRange.min + dX,
|
|
||||||
max: xRange.max + dX
|
|
||||||
});
|
|
||||||
this.config.yAxis.set('displayRange', {
|
|
||||||
min: yRange.min + dY,
|
|
||||||
max: yRange.max + dY
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
trackHistory() {
|
|
||||||
this.plotHistory.push({
|
|
||||||
x: this.config.xAxis.get('displayRange'),
|
|
||||||
y: this.config.yAxis.get('displayRange')
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
endPan() {
|
|
||||||
this.pan = undefined;
|
|
||||||
this.userViewportChangeEnd();
|
|
||||||
},
|
|
||||||
|
|
||||||
freeze() {
|
|
||||||
this.config.yAxis.set('frozen', true);
|
|
||||||
this.config.xAxis.set('frozen', true);
|
|
||||||
},
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.config.yAxis.set('frozen', false);
|
|
||||||
this.config.xAxis.set('frozen', false);
|
|
||||||
this.plotHistory = [];
|
|
||||||
this.userViewportChangeEnd();
|
|
||||||
},
|
|
||||||
|
|
||||||
back() {
|
|
||||||
const previousAxisRanges = this.plotHistory.pop();
|
|
||||||
if (this.plotHistory.length === 0) {
|
|
||||||
this.clear();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config.xAxis.set('displayRange', previousAxisRanges.x);
|
|
||||||
this.config.yAxis.set('displayRange', previousAxisRanges.y);
|
|
||||||
this.userViewportChangeEnd();
|
|
||||||
},
|
|
||||||
|
|
||||||
setCursorGuideVisibility(cursorGuide) {
|
|
||||||
this.cursorGuide = cursorGuide === true;
|
|
||||||
},
|
|
||||||
|
|
||||||
setGridLinesVisibility(gridLines) {
|
|
||||||
this.gridLines = gridLines === true;
|
|
||||||
},
|
|
||||||
|
|
||||||
setYAxisKey(yKey) {
|
|
||||||
this.config.series.models[0].emit('change:yKey', yKey);
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
configStore.deleteStore(this.config.id);
|
|
||||||
|
|
||||||
this.stopListening();
|
|
||||||
if (this.checkForSize) {
|
|
||||||
clearInterval(this.checkForSize);
|
|
||||||
delete this.checkForSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.filterObserver) {
|
|
||||||
this.filterObserver();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
@ -1,269 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2020, 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div ref="tickContainer"
|
|
||||||
class="u-contents js-ticks"
|
|
||||||
>
|
|
||||||
<div v-if="position === 'left'"
|
|
||||||
class="gl-plot-tick-wrapper"
|
|
||||||
>
|
|
||||||
<div v-for="tick in ticks"
|
|
||||||
:key="tick.value"
|
|
||||||
class="gl-plot-tick gl-plot-x-tick-label"
|
|
||||||
:style="{
|
|
||||||
left: (100 * (tick.value - min) / interval) + '%'
|
|
||||||
}"
|
|
||||||
:title="tick.fullText || tick.text"
|
|
||||||
>
|
|
||||||
{{ tick.text }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="position === 'top'"
|
|
||||||
class="gl-plot-tick-wrapper"
|
|
||||||
>
|
|
||||||
<div v-for="tick in ticks"
|
|
||||||
:key="tick.value"
|
|
||||||
class="gl-plot-tick gl-plot-y-tick-label"
|
|
||||||
:style="{ top: (100 * (max - tick.value) / interval) + '%' }"
|
|
||||||
:title="tick.fullText || tick.text"
|
|
||||||
style="margin-top: -0.50em; direction: ltr;"
|
|
||||||
>
|
|
||||||
<span>{{ tick.text }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- grid lines follow -->
|
|
||||||
<template v-if="position === 'right'">
|
|
||||||
<div v-for="tick in ticks"
|
|
||||||
:key="tick.value"
|
|
||||||
class="gl-plot-hash hash-v"
|
|
||||||
:style="{
|
|
||||||
right: (100 * (max - tick.value) / interval) + '%',
|
|
||||||
height: '100%'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-if="position === 'bottom'">
|
|
||||||
<div v-for="tick in ticks"
|
|
||||||
:key="tick.value"
|
|
||||||
class="gl-plot-hash hash-h"
|
|
||||||
:style="{ bottom: (100 * (tick.value - min) / interval) + '%', width: '100%' }"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import eventHelpers from "./lib/eventHelpers";
|
|
||||||
import { ticks, commonPrefix, commonSuffix } from "./tickUtils";
|
|
||||||
import configStore from "./configuration/configStore";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
props: {
|
|
||||||
axisType: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
ticks: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
eventHelpers.extend(this);
|
|
||||||
|
|
||||||
this.axis = this.getAxisFromConfig();
|
|
||||||
|
|
||||||
this.tickCount = 4;
|
|
||||||
this.tickUpdate = false;
|
|
||||||
this.listenTo(this.axis, 'change:displayRange', this.updateTicks, this);
|
|
||||||
this.listenTo(this.axis, 'change:format', this.updateTicks, this);
|
|
||||||
this.listenTo(this.axis, 'change:key', this.updateTicksForceRegeneration, this);
|
|
||||||
this.updateTicks();
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.stopListening();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getAxisFromConfig() {
|
|
||||||
if (!this.axisType) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
|
||||||
let config = configStore.get(configId);
|
|
||||||
if (config) {
|
|
||||||
return config[this.axisType];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Determine whether ticks should be regenerated for a given range.
|
|
||||||
* Ticks are updated
|
|
||||||
* a) if they don't exist,
|
|
||||||
* b) if existing ticks are outside of given range,
|
|
||||||
* c) if range exceeds size of tick range by more than one tick step,
|
|
||||||
* d) if forced to regenerate (ex. changing x-axis metadata).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
shouldRegenerateTicks(range, forceRegeneration) {
|
|
||||||
if (forceRegeneration) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.tickRange || !this.ticks || !this.ticks.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.tickRange.max > range.max || this.tickRange.min < range.min) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(range.max - this.tickRange.max) > this.tickRange.step) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(this.tickRange.min - range.min) > this.tickRange.step) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getTicks() {
|
|
||||||
const number = this.tickCount;
|
|
||||||
const clampRange = this.axis.get('values');
|
|
||||||
const range = this.axis.get('displayRange');
|
|
||||||
if (clampRange) {
|
|
||||||
return clampRange.filter(function (value) {
|
|
||||||
return value <= range.max && value >= range.min;
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ticks(range.min, range.max, number);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateTicksForceRegeneration() {
|
|
||||||
this.updateTicks(true);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateTicks(forceRegeneration = false) {
|
|
||||||
const range = this.axis.get('displayRange');
|
|
||||||
if (!range) {
|
|
||||||
delete this.min;
|
|
||||||
delete this.max;
|
|
||||||
delete this.interval;
|
|
||||||
delete this.tickRange;
|
|
||||||
this.ticks = [];
|
|
||||||
delete this.shouldCheckWidth;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const format = this.axis.get('format');
|
|
||||||
if (!format) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.min = range.min;
|
|
||||||
this.max = range.max;
|
|
||||||
this.interval = Math.abs(range.min - range.max);
|
|
||||||
if (this.shouldRegenerateTicks(range, forceRegeneration)) {
|
|
||||||
let newTicks = this.getTicks();
|
|
||||||
this.tickRange = {
|
|
||||||
min: Math.min.apply(Math, newTicks),
|
|
||||||
max: Math.max.apply(Math, newTicks),
|
|
||||||
step: newTicks[1] - newTicks[0]
|
|
||||||
};
|
|
||||||
|
|
||||||
newTicks = newTicks
|
|
||||||
.map(function (tickValue) {
|
|
||||||
return {
|
|
||||||
value: tickValue,
|
|
||||||
text: format(tickValue)
|
|
||||||
};
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
if (newTicks.length && typeof newTicks[0].text === 'string') {
|
|
||||||
const tickText = newTicks.map(function (t) {
|
|
||||||
return t.text;
|
|
||||||
});
|
|
||||||
const prefix = tickText.reduce(commonPrefix);
|
|
||||||
const suffix = tickText.reduce(commonSuffix);
|
|
||||||
newTicks.forEach(function (t) {
|
|
||||||
t.fullText = t.text;
|
|
||||||
if (suffix.length) {
|
|
||||||
t.text = t.text.slice(prefix.length, -suffix.length);
|
|
||||||
} else {
|
|
||||||
t.text = t.text.slice(prefix.length);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ticks = newTicks;
|
|
||||||
this.shouldCheckWidth = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scheduleTickUpdate();
|
|
||||||
},
|
|
||||||
|
|
||||||
scheduleTickUpdate() {
|
|
||||||
if (this.tickUpdate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tickUpdate = true;
|
|
||||||
setTimeout(this.doTickUpdate.bind(this), 0);
|
|
||||||
},
|
|
||||||
|
|
||||||
doTickUpdate() {
|
|
||||||
if (this.shouldCheckWidth) {
|
|
||||||
const tickElements = this.$refs.tickContainer.querySelectorAll('.gl-plot-tick > span');
|
|
||||||
|
|
||||||
const tickWidth = Number([].reduce.call(tickElements, function (memo, first) {
|
|
||||||
return Math.max(memo, first.offsetWidth);
|
|
||||||
}, 0));
|
|
||||||
|
|
||||||
this.tickWidth = tickWidth;
|
|
||||||
this.$emit('plotTickWidth', tickWidth);
|
|
||||||
this.shouldCheckWidth = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tickUpdate = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
@ -1,125 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2020, 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.
|
|
||||||
-->
|
|
||||||
<template>
|
|
||||||
<div ref="plotWrapper"
|
|
||||||
class="c-plot holder holder-plot has-control-bar"
|
|
||||||
>
|
|
||||||
<div class="c-control-bar">
|
|
||||||
<span class="c-button-set c-button-set--strip-h">
|
|
||||||
<button class="c-button icon-download"
|
|
||||||
title="Export This View's Data as PNG"
|
|
||||||
@click="exportPNG()"
|
|
||||||
>
|
|
||||||
<span class="c-button__label">PNG</span>
|
|
||||||
</button>
|
|
||||||
<button class="c-button"
|
|
||||||
title="Export This View's Data as JPG"
|
|
||||||
@click="exportJPG()"
|
|
||||||
>
|
|
||||||
<span class="c-button__label">JPG</span>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
<button class="c-button icon-crosshair"
|
|
||||||
:class="{ 'is-active': cursorGuide }"
|
|
||||||
title="Toggle cursor guides"
|
|
||||||
@click="toggleCursorGuide"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
<button class="c-button"
|
|
||||||
:class="{ 'icon-grid-on': gridLines, 'icon-grid-off': !gridLines }"
|
|
||||||
title="Toggle grid lines"
|
|
||||||
@click="toggleGridLines"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ref="plotContainer"
|
|
||||||
class="l-view-section u-style-receiver js-style-receiver"
|
|
||||||
>
|
|
||||||
<div v-show="!!loading"
|
|
||||||
class="c-loading--overlay loading"
|
|
||||||
></div>
|
|
||||||
<mct-plot :grid-lines="gridLines"
|
|
||||||
:cursor-guide="cursorGuide"
|
|
||||||
@loadingUpdated="loadingUpdated"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import eventHelpers from "./lib/eventHelpers";
|
|
||||||
import MctPlot from './MctPlot.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
MctPlot
|
|
||||||
},
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
//Don't think we need this as it appears to be stacked plot specific
|
|
||||||
// hideExportButtons: false
|
|
||||||
cursorGuide: false,
|
|
||||||
gridLines: true,
|
|
||||||
loading: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
eventHelpers.extend(this);
|
|
||||||
|
|
||||||
this.exportImageService = this.openmct.$injector.get('exportImageService');
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.destroy();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadingUpdated(loading) {
|
|
||||||
this.loading = loading;
|
|
||||||
},
|
|
||||||
destroy() {
|
|
||||||
this.stopListening();
|
|
||||||
},
|
|
||||||
|
|
||||||
exportJPG() {
|
|
||||||
const plotElement = this.$refs.plotContainer;
|
|
||||||
|
|
||||||
this.exportImageService.exportJPG(plotElement, 'plot.jpg', 'export-plot');
|
|
||||||
},
|
|
||||||
|
|
||||||
exportPNG() {
|
|
||||||
const plotElement = this.$refs.plotContainer;
|
|
||||||
|
|
||||||
this.exportImageService.exportPNG(plotElement, 'plot.png', 'export-plot');
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleCursorGuide() {
|
|
||||||
this.cursorGuide = !this.cursorGuide;
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleGridLines() {
|
|
||||||
this.gridLines = !this.gridLines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
@ -1,74 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 Plot from './Plot.vue';
|
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
export default function PlotViewProvider(openmct) {
|
|
||||||
function hasTelemetry(domainObject) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadata = openmct.telemetry.getMetadata(domainObject);
|
|
||||||
|
|
||||||
return metadata.values().length > 0 && hasDomainAndRange(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasDomainAndRange(metadata) {
|
|
||||||
return (metadata.valuesForHints(['range']).length > 0
|
|
||||||
&& metadata.valuesForHints(['domain']).length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: 'plot-single',
|
|
||||||
name: 'Plot',
|
|
||||||
cssClass: 'icon-telemetry',
|
|
||||||
canView(domainObject) {
|
|
||||||
return domainObject.type === 'plot-single' || hasTelemetry(domainObject);
|
|
||||||
},
|
|
||||||
|
|
||||||
view: function (domainObject) {
|
|
||||||
let component;
|
|
||||||
|
|
||||||
return {
|
|
||||||
show: function (element) {
|
|
||||||
component = new Vue({
|
|
||||||
el: element,
|
|
||||||
components: {
|
|
||||||
Plot
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
openmct,
|
|
||||||
domainObject
|
|
||||||
},
|
|
||||||
template: '<plot></plot>'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroy: function () {
|
|
||||||
component.$destroy();
|
|
||||||
component = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2020, 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="loaded"
|
|
||||||
class="gl-plot-axis-area gl-plot-x has-local-controls"
|
|
||||||
>
|
|
||||||
<mct-ticks :axis-type="'xAxis'"
|
|
||||||
:position="'left'"
|
|
||||||
@plotTickWidth="onTickWidthChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="gl-plot-label gl-plot-x-label"
|
|
||||||
:class="{'icon-gear': isEnabledXKeyToggle()}"
|
|
||||||
>
|
|
||||||
{{ xAxisLabel }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<select
|
|
||||||
v-show="isEnabledXKeyToggle()"
|
|
||||||
v-model="selectedXKeyOptionKey"
|
|
||||||
class="gl-plot-x-label__select local-controls--hidden"
|
|
||||||
@change="toggleXKeyOption()"
|
|
||||||
>
|
|
||||||
<option v-for="option in xKeyOptions"
|
|
||||||
:key="option.key"
|
|
||||||
:value="option.key"
|
|
||||||
>{{ option.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MctTicks from "../MctTicks.vue";
|
|
||||||
import eventHelpers from '../lib/eventHelpers';
|
|
||||||
import configStore from "../configuration/configStore";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
MctTicks
|
|
||||||
},
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
props: {
|
|
||||||
seriesModel: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
selectedXKeyOptionKey: '',
|
|
||||||
xKeyOptions: [],
|
|
||||||
xAxis: {},
|
|
||||||
loaded: false,
|
|
||||||
xAxisLabel: ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
eventHelpers.extend(this);
|
|
||||||
this.xAxis = this.getXAxisFromConfig();
|
|
||||||
this.loaded = true;
|
|
||||||
this.setUpXAxisOptions();
|
|
||||||
this.openmct.time.on('timeSystem', this.syncXAxisToTimeSystem);
|
|
||||||
this.listenTo(this.xAxis, 'change', this.setUpXAxisOptions);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.openmct.time.off('timeSystem', this.syncXAxisToTimeSystem);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
isEnabledXKeyToggle() {
|
|
||||||
const isSinglePlot = this.xKeyOptions && this.xKeyOptions.length > 1 && this.seriesModel;
|
|
||||||
const isFrozen = this.xAxis.get('frozen');
|
|
||||||
const inRealTimeMode = this.openmct.time.clock();
|
|
||||||
|
|
||||||
return isSinglePlot && !isFrozen && !inRealTimeMode;
|
|
||||||
},
|
|
||||||
getXAxisFromConfig() {
|
|
||||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
|
||||||
let config = configStore.get(configId);
|
|
||||||
if (config) {
|
|
||||||
return config.xAxis;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
toggleXKeyOption() {
|
|
||||||
const selectedXKey = this.selectedXKeyOptionKey;
|
|
||||||
const dataForSelectedXKey = this.seriesModel.data
|
|
||||||
? this.seriesModel.data[0][selectedXKey]
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (dataForSelectedXKey !== undefined) {
|
|
||||||
this.xAxis.set('key', selectedXKey);
|
|
||||||
} else {
|
|
||||||
this.openmct.notifications.error('Cannot change x-axis view as no data exists for this view type.');
|
|
||||||
const xAxisKey = this.xAxis.get('key');
|
|
||||||
this.selectedXKeyOptionKey = this.getXKeyOption(xAxisKey).key;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getXKeyOption(key) {
|
|
||||||
return this.xKeyOptions.find(option => option.key === key);
|
|
||||||
},
|
|
||||||
syncXAxisToTimeSystem(timeSystem) {
|
|
||||||
const xAxisKey = this.xAxis.get('key');
|
|
||||||
if (xAxisKey !== timeSystem.key) {
|
|
||||||
this.xAxis.set('key', timeSystem.key);
|
|
||||||
this.xAxis.resetSeries();
|
|
||||||
this.setUpXAxisOptions();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setUpXAxisOptions() {
|
|
||||||
const xAxisKey = this.xAxis.get('key');
|
|
||||||
|
|
||||||
this.xKeyOptions = this.seriesModel.metadata
|
|
||||||
.valuesForHints(['domain'])
|
|
||||||
.map(function (o) {
|
|
||||||
return {
|
|
||||||
name: o.name,
|
|
||||||
key: o.key
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.xAxisLabel = this.xAxis.get('label');
|
|
||||||
this.selectedXKeyOptionKey = this.getXKeyOption(xAxisKey).key;
|
|
||||||
},
|
|
||||||
onTickWidthChange(width) {
|
|
||||||
this.$emit('tickWidthChanged', width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,137 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2020, 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.
|
|
||||||
-->
|
|
||||||
<template>
|
|
||||||
<div v-if="loaded"
|
|
||||||
class="gl-plot-axis-area gl-plot-y has-local-controls"
|
|
||||||
:style="{
|
|
||||||
width: (tickWidth + 20) + 'px'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
|
|
||||||
<div v-if="singleSeries"
|
|
||||||
class="gl-plot-label gl-plot-y-label"
|
|
||||||
:class="{'icon-gear': (yKeyOptions.length > 1)}"
|
|
||||||
>{{ yAxisLabel }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<select v-if="yKeyOptions.length > 1 && singleSeries"
|
|
||||||
v-model="yAxisLabel"
|
|
||||||
class="gl-plot-y-label__select local-controls--hidden"
|
|
||||||
@change="toggleYAxisLabel"
|
|
||||||
>
|
|
||||||
<option v-for="(option, index) in yKeyOptions"
|
|
||||||
:key="index"
|
|
||||||
:value="option.name"
|
|
||||||
:selected="option.name === yAxisLabel"
|
|
||||||
>
|
|
||||||
{{ option.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<mct-ticks :axis-type="'yAxis'"
|
|
||||||
class="gl-plot-ticks"
|
|
||||||
:position="'top'"
|
|
||||||
@plotTickWidth="onTickWidthChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MctTicks from "../MctTicks.vue";
|
|
||||||
import configStore from "../configuration/configStore";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
MctTicks
|
|
||||||
},
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
props: {
|
|
||||||
singleSeries: {
|
|
||||||
type: Boolean,
|
|
||||||
default() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
seriesModel: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tickWidth: {
|
|
||||||
type: Number,
|
|
||||||
default() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
yAxisLabel: 'none',
|
|
||||||
loaded: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.yAxis = this.getYAxisFromConfig();
|
|
||||||
this.loaded = true;
|
|
||||||
this.setUpYAxisOptions();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getYAxisFromConfig() {
|
|
||||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
|
||||||
let config = configStore.get(configId);
|
|
||||||
if (config) {
|
|
||||||
return config.yAxis;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setUpYAxisOptions() {
|
|
||||||
this.yKeyOptions = this.seriesModel.metadata
|
|
||||||
.valuesForHints(['range'])
|
|
||||||
.map(function (o) {
|
|
||||||
return {
|
|
||||||
name: o.name,
|
|
||||||
key: o.key
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// set yAxisLabel if none is set yet
|
|
||||||
if (this.yAxisLabel === 'none') {
|
|
||||||
let yKey = this.seriesModel.model.yKey;
|
|
||||||
let yKeyModel = this.yKeyOptions.filter(o => o.key === yKey)[0];
|
|
||||||
|
|
||||||
this.yAxisLabel = yKeyModel.name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
toggleYAxisLabel() {
|
|
||||||
let yAxisObject = this.yKeyOptions.filter(o => o.name === this.yAxisLabel)[0];
|
|
||||||
|
|
||||||
if (yAxisObject) {
|
|
||||||
this.$emit('yKeyChanged', yAxisObject.key);
|
|
||||||
this.yAxis.set('label', this.yAxisLabel);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onTickWidthChange(width) {
|
|
||||||
this.$emit('tickWidthChanged', width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,67 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 eventHelpers from '../lib/eventHelpers';
|
|
||||||
|
|
||||||
export default class MCTChartAlarmPointSet {
|
|
||||||
constructor(series, chart, offset) {
|
|
||||||
this.series = series;
|
|
||||||
this.chart = chart;
|
|
||||||
this.offset = offset;
|
|
||||||
this.points = [];
|
|
||||||
|
|
||||||
eventHelpers.extend(this);
|
|
||||||
|
|
||||||
this.listenTo(series, 'add', this.append, this);
|
|
||||||
this.listenTo(series, 'remove', this.remove, this);
|
|
||||||
this.listenTo(series, 'reset', this.reset, this);
|
|
||||||
this.listenTo(series, 'destroy', this.destroy, this);
|
|
||||||
|
|
||||||
series.data.forEach(function (point, index) {
|
|
||||||
this.append(point, index, series);
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
append(datum) {
|
|
||||||
if (datum.mctLimitState) {
|
|
||||||
this.points.push({
|
|
||||||
x: this.offset.xVal(datum, this.series),
|
|
||||||
y: this.offset.yVal(datum, this.series),
|
|
||||||
datum: datum
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(datum) {
|
|
||||||
this.points = this.points.filter(function (p) {
|
|
||||||
return p.datum !== datum;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.points = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.stopListening();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 MCTChartSeriesElement from './MCTChartSeriesElement';
|
|
||||||
|
|
||||||
export default class MCTChartLineStepAfter extends MCTChartSeriesElement {
|
|
||||||
removePoint(point, index, count) {
|
|
||||||
if (index > 0 && index / 2 < this.count) {
|
|
||||||
this.buffer[index + 1] = this.buffer[index - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vertexCountForPointAtIndex(index) {
|
|
||||||
if (index === 0 && this.count === 0) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
startIndexForPointAtIndex(index) {
|
|
||||||
if (index === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 2 + ((index - 1) * 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
addPoint(point, start, count) {
|
|
||||||
if (start === 0 && this.count === 0) {
|
|
||||||
// First point is easy.
|
|
||||||
this.buffer[start] = point.x;
|
|
||||||
this.buffer[start + 1] = point.y; // one point
|
|
||||||
} else if (start === 0 && this.count > 0) {
|
|
||||||
// Unshifting requires adding an extra point.
|
|
||||||
this.buffer[start] = point.x;
|
|
||||||
this.buffer[start + 1] = point.y;
|
|
||||||
this.buffer[start + 2] = this.buffer[start + 4];
|
|
||||||
this.buffer[start + 3] = point.y;
|
|
||||||
} else {
|
|
||||||
// Appending anywhere in line, insert standard two points.
|
|
||||||
this.buffer[start] = point.x;
|
|
||||||
this.buffer[start + 1] = this.buffer[start - 1];
|
|
||||||
this.buffer[start + 2] = point.x;
|
|
||||||
this.buffer[start + 3] = point.y;
|
|
||||||
|
|
||||||
if (start < this.count * 2) {
|
|
||||||
// Insert into the middle, need to update the following
|
|
||||||
// point.
|
|
||||||
this.buffer[start + 5] = point.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 MCTChartSeriesElement from './MCTChartSeriesElement';
|
|
||||||
|
|
||||||
// TODO: Is this needed? This is identical to MCTChartLineLinear. Why is it a different class?
|
|
||||||
export default class MCTChartPointSet extends MCTChartSeriesElement {
|
|
||||||
addPoint(point, start, count) {
|
|
||||||
this.buffer[start] = point.x;
|
|
||||||
this.buffer[start + 1] = point.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 eventHelpers from '../lib/eventHelpers';
|
|
||||||
|
|
||||||
export default class MCTChartSeriesElement {
|
|
||||||
constructor(series, chart, offset) {
|
|
||||||
this.series = series;
|
|
||||||
this.chart = chart;
|
|
||||||
this.offset = offset;
|
|
||||||
this.buffer = new Float32Array(20000);
|
|
||||||
this.count = 0;
|
|
||||||
|
|
||||||
eventHelpers.extend(this);
|
|
||||||
|
|
||||||
this.listenTo(series, 'add', this.append, this);
|
|
||||||
this.listenTo(series, 'remove', this.remove, this);
|
|
||||||
this.listenTo(series, 'reset', this.reset, this);
|
|
||||||
this.listenTo(series, 'destroy', this.destroy, this);
|
|
||||||
series.data.forEach(function (point, index) {
|
|
||||||
this.append(point, index, series);
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
getBuffer() {
|
|
||||||
if (this.isTempBuffer) {
|
|
||||||
this.buffer = new Float32Array(this.buffer);
|
|
||||||
this.isTempBuffer = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
color() {
|
|
||||||
return this.series.get('color');
|
|
||||||
}
|
|
||||||
|
|
||||||
vertexCountForPointAtIndex(index) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
startIndexForPointAtIndex(index) {
|
|
||||||
return 2 * index;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeSegments(index, count) {
|
|
||||||
const target = index;
|
|
||||||
const start = index + count;
|
|
||||||
const end = this.count * 2;
|
|
||||||
this.buffer.copyWithin(target, start, end);
|
|
||||||
for (let zero = end - count; zero < end; zero++) {
|
|
||||||
this.buffer[zero] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removePoint(point, index, count) {
|
|
||||||
// by default, do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(point, index, series) {
|
|
||||||
const vertexCount = this.vertexCountForPointAtIndex(index);
|
|
||||||
const removalPoint = this.startIndexForPointAtIndex(index);
|
|
||||||
|
|
||||||
this.removeSegments(removalPoint, vertexCount);
|
|
||||||
|
|
||||||
this.removePoint(
|
|
||||||
this.makePoint(point, series),
|
|
||||||
removalPoint,
|
|
||||||
vertexCount
|
|
||||||
);
|
|
||||||
this.count -= (vertexCount / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
makePoint(point, series) {
|
|
||||||
if (!this.offset.xVal) {
|
|
||||||
this.chart.setOffset(point, undefined, series);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: this.offset.xVal(point, series),
|
|
||||||
y: this.offset.yVal(point, series)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
append(point, index, series) {
|
|
||||||
const pointsRequired = this.vertexCountForPointAtIndex(index);
|
|
||||||
const insertionPoint = this.startIndexForPointAtIndex(index);
|
|
||||||
this.growIfNeeded(pointsRequired);
|
|
||||||
this.makeInsertionPoint(insertionPoint, pointsRequired);
|
|
||||||
this.addPoint(
|
|
||||||
this.makePoint(point, series),
|
|
||||||
insertionPoint,
|
|
||||||
pointsRequired
|
|
||||||
);
|
|
||||||
this.count += (pointsRequired / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
makeInsertionPoint(insertionPoint, pointsRequired) {
|
|
||||||
if (this.count * 2 > insertionPoint) {
|
|
||||||
if (!this.isTempBuffer) {
|
|
||||||
this.buffer = Array.prototype.slice.apply(this.buffer);
|
|
||||||
this.isTempBuffer = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = insertionPoint + pointsRequired;
|
|
||||||
let start = insertionPoint;
|
|
||||||
for (; start < target; start++) {
|
|
||||||
this.buffer.splice(start, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.buffer = new Float32Array(20000);
|
|
||||||
this.count = 0;
|
|
||||||
if (this.offset.x) {
|
|
||||||
this.series.data.forEach(function (point, index) {
|
|
||||||
this.append(point, index, this.series);
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
growIfNeeded(pointsRequired) {
|
|
||||||
const remainingPoints = this.buffer.length - this.count * 2;
|
|
||||||
let temp;
|
|
||||||
|
|
||||||
if (remainingPoints <= pointsRequired) {
|
|
||||||
temp = new Float32Array(this.buffer.length + 20000);
|
|
||||||
temp.set(this.buffer);
|
|
||||||
this.buffer = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.stopListening();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,449 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2020, 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.
|
|
||||||
-->
|
|
||||||
<template>
|
|
||||||
<div class="gl-plot-chart-area">
|
|
||||||
<span v-html="canvasTemplate"></span>
|
|
||||||
<span v-html="canvasTemplate"></span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import eventHelpers from "../lib/eventHelpers";
|
|
||||||
import { DrawLoader } from '../draw/DrawLoader';
|
|
||||||
import MCTChartLineLinear from './MCTChartLineLinear';
|
|
||||||
import MCTChartLineStepAfter from './MCTChartLineStepAfter';
|
|
||||||
import MCTChartPointSet from './MCTChartPointSet';
|
|
||||||
import MCTChartAlarmPointSet from './MCTChartAlarmPointSet';
|
|
||||||
import configStore from "../configuration/configStore";
|
|
||||||
import PlotConfigurationModel from "../configuration/PlotConfigurationModel";
|
|
||||||
|
|
||||||
const MARKER_SIZE = 6.0;
|
|
||||||
const HIGHLIGHT_SIZE = MARKER_SIZE * 2.0;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
props: {
|
|
||||||
rectangles: {
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
highlights: {
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
canvasTemplate: '<canvas style="position: absolute; background: none; width: 100%; height: 100%;"></canvas>'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
highlights() {
|
|
||||||
this.scheduleDraw();
|
|
||||||
},
|
|
||||||
rectangles() {
|
|
||||||
this.scheduleDraw();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
eventHelpers.extend(this);
|
|
||||||
|
|
||||||
this.config = this.getConfig();
|
|
||||||
this.isDestroyed = false;
|
|
||||||
this.lines = [];
|
|
||||||
this.pointSets = [];
|
|
||||||
this.alarmSets = [];
|
|
||||||
this.offset = {};
|
|
||||||
this.seriesElements = new WeakMap();
|
|
||||||
|
|
||||||
let canvasEls = this.$parent.$refs.chartContainer.querySelectorAll("canvas");
|
|
||||||
const mainCanvas = canvasEls[1];
|
|
||||||
const overlayCanvas = canvasEls[0];
|
|
||||||
if (this.initializeCanvas(mainCanvas, overlayCanvas)) {
|
|
||||||
this.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
|
||||||
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
|
||||||
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
|
|
||||||
this.listenTo(this.config.yAxis, 'change', this.scheduleDraw);
|
|
||||||
this.listenTo(this.config.xAxis, 'change', this.scheduleDraw);
|
|
||||||
this.config.series.forEach(this.onSeriesAdd, this);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.destroy();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getConfig() {
|
|
||||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
|
||||||
let config = configStore.get(configId);
|
|
||||||
if (!config) {
|
|
||||||
config = new PlotConfigurationModel({
|
|
||||||
id: configId,
|
|
||||||
domainObject: this.domainObject,
|
|
||||||
openmct: this.openmct
|
|
||||||
});
|
|
||||||
configStore.add(configId, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
reDraw(mode, o, series) {
|
|
||||||
this.changeInterpolate(mode, o, series);
|
|
||||||
this.changeMarkers(mode, o, series);
|
|
||||||
this.changeAlarmMarkers(mode, o, series);
|
|
||||||
},
|
|
||||||
onSeriesAdd(series) {
|
|
||||||
this.listenTo(series, 'change:xKey', this.reDraw, this);
|
|
||||||
this.listenTo(series, 'change:interpolate', this.changeInterpolate, this);
|
|
||||||
this.listenTo(series, 'change:markers', this.changeMarkers, this);
|
|
||||||
this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this);
|
|
||||||
this.listenTo(series, 'change', this.scheduleDraw);
|
|
||||||
this.listenTo(series, 'add', this.scheduleDraw);
|
|
||||||
this.makeChartElement(series);
|
|
||||||
},
|
|
||||||
changeInterpolate(mode, o, series) {
|
|
||||||
if (mode === o) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const elements = this.seriesElements.get(series);
|
|
||||||
elements.lines.forEach(function (line) {
|
|
||||||
this.lines.splice(this.lines.indexOf(line), 1);
|
|
||||||
line.destroy();
|
|
||||||
}, this);
|
|
||||||
elements.lines = [];
|
|
||||||
|
|
||||||
const newLine = this.lineForSeries(series);
|
|
||||||
if (newLine) {
|
|
||||||
elements.lines.push(newLine);
|
|
||||||
this.lines.push(newLine);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
changeAlarmMarkers(mode, o, series) {
|
|
||||||
if (mode === o) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const elements = this.seriesElements.get(series);
|
|
||||||
if (elements.alarmSet) {
|
|
||||||
elements.alarmSet.destroy();
|
|
||||||
this.alarmSets.splice(this.alarmSets.indexOf(elements.alarmSet), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.alarmSet = this.alarmPointSetForSeries(series);
|
|
||||||
if (elements.alarmSet) {
|
|
||||||
this.alarmSets.push(elements.alarmSet);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
changeMarkers(mode, o, series) {
|
|
||||||
if (mode === o) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const elements = this.seriesElements.get(series);
|
|
||||||
elements.pointSets.forEach(function (pointSet) {
|
|
||||||
this.pointSets.splice(this.pointSets.indexOf(pointSet), 1);
|
|
||||||
pointSet.destroy();
|
|
||||||
}, this);
|
|
||||||
elements.pointSets = [];
|
|
||||||
|
|
||||||
const pointSet = this.pointSetForSeries(series);
|
|
||||||
if (pointSet) {
|
|
||||||
elements.pointSets.push(pointSet);
|
|
||||||
this.pointSets.push(pointSet);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSeriesRemove(series) {
|
|
||||||
this.stopListening(series);
|
|
||||||
this.removeChartElement(series);
|
|
||||||
this.scheduleDraw();
|
|
||||||
},
|
|
||||||
destroy() {
|
|
||||||
this.isDestroyed = true;
|
|
||||||
this.stopListening();
|
|
||||||
this.lines.forEach(line => line.destroy());
|
|
||||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
|
||||||
},
|
|
||||||
clearOffset() {
|
|
||||||
delete this.offset.x;
|
|
||||||
delete this.offset.y;
|
|
||||||
delete this.offset.xVal;
|
|
||||||
delete this.offset.yVal;
|
|
||||||
delete this.offset.xKey;
|
|
||||||
delete this.offset.yKey;
|
|
||||||
this.lines.forEach(function (line) {
|
|
||||||
line.reset();
|
|
||||||
});
|
|
||||||
this.pointSets.forEach(function (pointSet) {
|
|
||||||
pointSet.reset();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setOffset(offsetPoint, index, series) {
|
|
||||||
if (this.offset.x && this.offset.y) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const offsets = {
|
|
||||||
x: series.getXVal(offsetPoint),
|
|
||||||
y: series.getYVal(offsetPoint)
|
|
||||||
};
|
|
||||||
|
|
||||||
this.offset.x = function (x) {
|
|
||||||
return x - offsets.x;
|
|
||||||
}.bind(this);
|
|
||||||
this.offset.y = function (y) {
|
|
||||||
return y - offsets.y;
|
|
||||||
}.bind(this);
|
|
||||||
this.offset.xVal = function (point, pSeries) {
|
|
||||||
return this.offset.x(pSeries.getXVal(point));
|
|
||||||
}.bind(this);
|
|
||||||
this.offset.yVal = function (point, pSeries) {
|
|
||||||
return this.offset.y(pSeries.getYVal(point));
|
|
||||||
}.bind(this);
|
|
||||||
},
|
|
||||||
initializeCanvas(canvas, overlay) {
|
|
||||||
this.canvas = canvas;
|
|
||||||
this.overlay = overlay;
|
|
||||||
this.drawAPI = DrawLoader.getDrawAPI(canvas, overlay);
|
|
||||||
if (this.drawAPI) {
|
|
||||||
this.listenTo(this.drawAPI, 'error', this.fallbackToCanvas, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Boolean(this.drawAPI);
|
|
||||||
},
|
|
||||||
fallbackToCanvas() {
|
|
||||||
this.stopListening(this.drawAPI);
|
|
||||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
|
||||||
// Have to throw away the old canvas elements and replace with new
|
|
||||||
// canvas elements in order to get new drawing contexts.
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.innerHTML = this.TEMPLATE;
|
|
||||||
const mainCanvas = div.querySelectorAll("canvas")[1];
|
|
||||||
const overlayCanvas = div.querySelectorAll("canvas")[0];
|
|
||||||
this.canvas.parentNode.replaceChild(mainCanvas, this.canvas);
|
|
||||||
this.canvas = mainCanvas;
|
|
||||||
this.overlay.parentNode.replaceChild(overlayCanvas, this.overlay);
|
|
||||||
this.overlay = overlayCanvas;
|
|
||||||
this.drawAPI = DrawLoader.getFallbackDrawAPI(this.canvas, this.overlay);
|
|
||||||
this.$emit('plotReinitializeCanvas');
|
|
||||||
},
|
|
||||||
removeChartElement(series) {
|
|
||||||
const elements = this.seriesElements.get(series);
|
|
||||||
|
|
||||||
elements.lines.forEach(function (line) {
|
|
||||||
this.lines.splice(this.lines.indexOf(line), 1);
|
|
||||||
line.destroy();
|
|
||||||
}, this);
|
|
||||||
elements.pointSets.forEach(function (pointSet) {
|
|
||||||
this.pointSets.splice(this.pointSets.indexOf(pointSet), 1);
|
|
||||||
pointSet.destroy();
|
|
||||||
}, this);
|
|
||||||
if (elements.alarmSet) {
|
|
||||||
elements.alarmSet.destroy();
|
|
||||||
this.alarmSets.splice(this.alarmSets.indexOf(elements.alarmSet), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.seriesElements.delete(series);
|
|
||||||
},
|
|
||||||
lineForSeries(series) {
|
|
||||||
if (series.get('interpolate') === 'linear') {
|
|
||||||
return new MCTChartLineLinear(
|
|
||||||
series,
|
|
||||||
this,
|
|
||||||
this.offset
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (series.get('interpolate') === 'stepAfter') {
|
|
||||||
return new MCTChartLineStepAfter(
|
|
||||||
series,
|
|
||||||
this,
|
|
||||||
this.offset
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pointSetForSeries(series) {
|
|
||||||
if (series.get('markers')) {
|
|
||||||
return new MCTChartPointSet(
|
|
||||||
series,
|
|
||||||
this,
|
|
||||||
this.offset
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
alarmPointSetForSeries(series) {
|
|
||||||
if (series.get('alarmMarkers')) {
|
|
||||||
return new MCTChartAlarmPointSet(
|
|
||||||
series,
|
|
||||||
this,
|
|
||||||
this.offset
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
makeChartElement(series) {
|
|
||||||
const elements = {
|
|
||||||
lines: [],
|
|
||||||
pointSets: []
|
|
||||||
};
|
|
||||||
|
|
||||||
const line = this.lineForSeries(series);
|
|
||||||
if (line) {
|
|
||||||
elements.lines.push(line);
|
|
||||||
this.lines.push(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pointSet = this.pointSetForSeries(series);
|
|
||||||
if (pointSet) {
|
|
||||||
elements.pointSets.push(pointSet);
|
|
||||||
this.pointSets.push(pointSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.alarmSet = this.alarmPointSetForSeries(series);
|
|
||||||
if (elements.alarmSet) {
|
|
||||||
this.alarmSets.push(elements.alarmSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.seriesElements.set(series, elements);
|
|
||||||
},
|
|
||||||
canDraw() {
|
|
||||||
if (!this.offset.x || !this.offset.y) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
scheduleDraw() {
|
|
||||||
if (!this.drawScheduled) {
|
|
||||||
requestAnimationFrame(this.draw);
|
|
||||||
this.drawScheduled = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
draw() {
|
|
||||||
this.drawScheduled = false;
|
|
||||||
if (this.isDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.drawAPI.clear();
|
|
||||||
if (this.canDraw()) {
|
|
||||||
this.updateViewport();
|
|
||||||
this.drawSeries();
|
|
||||||
this.drawRectangles();
|
|
||||||
this.drawHighlights();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateViewport() {
|
|
||||||
const xRange = this.config.xAxis.get('displayRange');
|
|
||||||
const yRange = this.config.yAxis.get('displayRange');
|
|
||||||
|
|
||||||
if (!xRange || !yRange) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dimensions = [
|
|
||||||
xRange.max - xRange.min,
|
|
||||||
yRange.max - yRange.min
|
|
||||||
];
|
|
||||||
|
|
||||||
const origin = [
|
|
||||||
this.offset.x(xRange.min),
|
|
||||||
this.offset.y(yRange.min)
|
|
||||||
];
|
|
||||||
|
|
||||||
this.drawAPI.setDimensions(
|
|
||||||
dimensions,
|
|
||||||
origin
|
|
||||||
);
|
|
||||||
},
|
|
||||||
drawSeries() {
|
|
||||||
this.lines.forEach(this.drawLine, this);
|
|
||||||
this.pointSets.forEach(this.drawPoints, this);
|
|
||||||
this.alarmSets.forEach(this.drawAlarmPoints, this);
|
|
||||||
},
|
|
||||||
drawAlarmPoints(alarmSet) {
|
|
||||||
this.drawAPI.drawLimitPoints(
|
|
||||||
alarmSet.points,
|
|
||||||
alarmSet.series.get('color').asRGBAArray(),
|
|
||||||
alarmSet.series.get('markerSize')
|
|
||||||
);
|
|
||||||
},
|
|
||||||
drawPoints(chartElement) {
|
|
||||||
this.drawAPI.drawPoints(
|
|
||||||
chartElement.getBuffer(),
|
|
||||||
chartElement.color().asRGBAArray(),
|
|
||||||
chartElement.count,
|
|
||||||
chartElement.series.get('markerSize'),
|
|
||||||
chartElement.series.get('markerShape')
|
|
||||||
);
|
|
||||||
},
|
|
||||||
drawLine(chartElement) {
|
|
||||||
this.drawAPI.drawLine(
|
|
||||||
chartElement.getBuffer(),
|
|
||||||
chartElement.color().asRGBAArray(),
|
|
||||||
chartElement.count
|
|
||||||
);
|
|
||||||
},
|
|
||||||
drawHighlights() {
|
|
||||||
if (this.highlights && this.highlights.length) {
|
|
||||||
this.highlights.forEach(this.drawHighlight, this);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
drawHighlight(highlight) {
|
|
||||||
const points = new Float32Array([
|
|
||||||
this.offset.xVal(highlight.point, highlight.series),
|
|
||||||
this.offset.yVal(highlight.point, highlight.series)
|
|
||||||
]);
|
|
||||||
|
|
||||||
const color = highlight.series.get('color').asRGBAArray();
|
|
||||||
const pointCount = 1;
|
|
||||||
const shape = highlight.series.get('markerShape');
|
|
||||||
|
|
||||||
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE, shape);
|
|
||||||
},
|
|
||||||
drawRectangles() {
|
|
||||||
if (this.rectangles) {
|
|
||||||
this.rectangles.forEach(this.drawRectangle, this);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
drawRectangle(rect) {
|
|
||||||
this.drawAPI.drawSquare(
|
|
||||||
[
|
|
||||||
this.offset.x(rect.start.x),
|
|
||||||
this.offset.y(rect.start.y)
|
|
||||||
],
|
|
||||||
[
|
|
||||||
this.offset.x(rect.end.x),
|
|
||||||
this.offset.y(rect.end.y)
|
|
||||||
],
|
|
||||||
rect.color
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
@ -1,109 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 Model from './Model';
|
|
||||||
|
|
||||||
export default class Collection extends Model {
|
|
||||||
|
|
||||||
initialize(options) {
|
|
||||||
super.initialize(options);
|
|
||||||
this.modelClass = Model;
|
|
||||||
if (options.models) {
|
|
||||||
this.models = options.models.map(this.modelFn, this);
|
|
||||||
} else {
|
|
||||||
this.models = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modelFn(model) {
|
|
||||||
//TODO: Come back to this - why are we doing this?
|
|
||||||
if (model instanceof this.modelClass) {
|
|
||||||
model.collection = this;
|
|
||||||
|
|
||||||
return model;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return new this.modelClass({
|
|
||||||
collection: this,
|
|
||||||
model: model
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
first() {
|
|
||||||
return this.at(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(iteree, context) {
|
|
||||||
this.models.forEach(iteree, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
map(iteree, context) {
|
|
||||||
return this.models.map(iteree, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
filter(iteree, context) {
|
|
||||||
return this.models.filter(iteree, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
size() {
|
|
||||||
return this.models.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
at(index) {
|
|
||||||
return this.models[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
add(model) {
|
|
||||||
model = this.modelFn(model);
|
|
||||||
const index = this.models.length;
|
|
||||||
this.models.push(model);
|
|
||||||
this.emit('add', model, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
insert(model, index) {
|
|
||||||
model = this.modelFn(model);
|
|
||||||
this.models.splice(index, 0, model);
|
|
||||||
this.emit('add', model, index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
indexOf(model) {
|
|
||||||
return this.models.findIndex(m => m === model);
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(model) {
|
|
||||||
const index = this.indexOf(model);
|
|
||||||
|
|
||||||
if (index === -1) {
|
|
||||||
throw new Error('model not found in collection.');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('remove', model, index);
|
|
||||||
this.models.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(model) {
|
|
||||||
this.forEach(function (m) {
|
|
||||||
m.destroy();
|
|
||||||
});
|
|
||||||
this.stopListening();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 Model from "./Model";
|
|
||||||
/**
|
|
||||||
* TODO: doc strings.
|
|
||||||
*/
|
|
||||||
export default class LegendModel extends Model {
|
|
||||||
listenToSeriesCollection(seriesCollection) {
|
|
||||||
this.seriesCollection = seriesCollection;
|
|
||||||
this.listenTo(this.seriesCollection, 'add', this.setHeight, this);
|
|
||||||
this.listenTo(this.seriesCollection, 'remove', this.setHeight, this);
|
|
||||||
this.listenTo(this, 'change:expanded', this.setHeight, this);
|
|
||||||
this.set('expanded', this.get('expandByDefault'));
|
|
||||||
}
|
|
||||||
|
|
||||||
setHeight() {
|
|
||||||
const expanded = this.get('expanded');
|
|
||||||
if (this.get('position') !== 'top') {
|
|
||||||
this.set('height', '0px');
|
|
||||||
} else {
|
|
||||||
this.set('height', expanded ? (20 * (this.seriesCollection.size() + 1) + 40) + 'px' : '21px');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defaults(options) {
|
|
||||||
return {
|
|
||||||
position: 'top',
|
|
||||||
expandByDefault: false,
|
|
||||||
hideLegendWhenSmall: false,
|
|
||||||
valueToShowWhenCollapsed: 'nearestValue',
|
|
||||||
showTimestampWhenExpanded: true,
|
|
||||||
showValueWhenExpanded: true,
|
|
||||||
showMaximumWhenExpanded: true,
|
|
||||||
showMinimumWhenExpanded: true,
|
|
||||||
showUnitsWhenExpanded: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 EventEmitter from 'EventEmitter';
|
|
||||||
import eventHelpers from "../lib/eventHelpers";
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export default class Model extends EventEmitter {
|
|
||||||
constructor(options) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
//need to do this as we're already extending EventEmitter
|
|
||||||
eventHelpers.extend(this);
|
|
||||||
|
|
||||||
if (!options) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.id = options.id;
|
|
||||||
this.model = options.model;
|
|
||||||
this.collection = options.collection;
|
|
||||||
const defaults = this.defaults(options);
|
|
||||||
if (!this.model) {
|
|
||||||
this.model = options.model = defaults;
|
|
||||||
} else {
|
|
||||||
_.defaultsDeep(this.model, defaults);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.initialize(options);
|
|
||||||
this.idAttr = 'id';
|
|
||||||
}
|
|
||||||
|
|
||||||
defaults(options) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(model) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy the model, removing all listeners and subscriptions.
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
this.emit('destroy');
|
|
||||||
this.removeAllListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
id() {
|
|
||||||
return this.get(this.idAttr);
|
|
||||||
}
|
|
||||||
|
|
||||||
get(attribute) {
|
|
||||||
return this.model[attribute];
|
|
||||||
}
|
|
||||||
|
|
||||||
has(attribute) {
|
|
||||||
return _.has(this.model, attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
set(attribute, value) {
|
|
||||||
const oldValue = this.model[attribute];
|
|
||||||
this.model[attribute] = value;
|
|
||||||
this.emit('change', attribute, value, oldValue, this);
|
|
||||||
this.emit('change:' + attribute, value, oldValue, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
unset(attribute) {
|
|
||||||
const oldValue = this.model[attribute];
|
|
||||||
delete this.model[attribute];
|
|
||||||
this.emit('change', attribute, undefined, oldValue, this);
|
|
||||||
this.emit('change:' + attribute, undefined, oldValue, this);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,136 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, 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 Model from "./Model";
|
|
||||||
import SeriesCollection from "./SeriesCollection";
|
|
||||||
import XAxisModel from "./XAxisModel";
|
|
||||||
import YAxisModel from "./YAxisModel";
|
|
||||||
import LegendModel from "./LegendModel";
|
|
||||||
/**
|
|
||||||
* PlotConfiguration model stores the configuration of a plot and some
|
|
||||||
* limited state. The indiidual parts of the plot configuration model
|
|
||||||
* handle setting defaults and updating in response to various changes.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export default class PlotConfigurationModel extends Model {
|
|
||||||
/**
|
|
||||||
* Initializes all sub models and then passes references to submodels
|
|
||||||
* to those that need it.
|
|
||||||
*/
|
|
||||||
initialize(options) {
|
|
||||||
this.openmct = options.openmct;
|
|
||||||
|
|
||||||
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
|
|
||||||
});
|
|
||||||
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
|
|
||||||
});
|
|
||||||
|
|
||||||
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.legend.listenToSeriesCollection(this.series);
|
|
||||||
|
|
||||||
this.listenTo(this, 'destroy', this.onDestroy, this);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Retrieve the persisted series config for a given identifier.
|
|
||||||
*/
|
|
||||||
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.series.destroy();
|
|
||||||
this.legend.destroy();
|
|
||||||
if (this.removeMutationListener) {
|
|
||||||
this.removeMutationListener();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Return defaults, which are extracted from the passed in domain
|
|
||||||
* object.
|
|
||||||
*/
|
|
||||||
defaults(options) {
|
|
||||||
return {
|
|
||||||
series: [],
|
|
||||||
domainObject: options.domainObject,
|
|
||||||
xAxis: {
|
|
||||||
},
|
|
||||||
yAxis: _.cloneDeep(_.get(options.domainObject, 'configuration.yAxis', {})),
|
|
||||||
legend: _.cloneDeep(_.get(options.domainObject, 'configuration.legend', {}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user