mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
2 Commits
1.7.2
...
remove-ang
Author | SHA1 | Date | |
---|---|---|---|
cf5ef9fa60 | |||
756e5d301e |
1
API.md
1
API.md
@ -430,7 +430,6 @@ Known hints:
|
||||
* `domain`: Values with a `domain` hint will be used for the x-axis of a plot, and tables will render columns for these values first.
|
||||
* `range`: Values with a `range` hint will be used as the y-axis on a plot, and tables will render columns for these values after the `domain` values.
|
||||
* `image`: Indicates that the value may be interpreted as the URL to an image file, in which case appropriate views will be made available.
|
||||
* `imageDownloadName`: Indicates that the value may be interpreted as the name of the image file.
|
||||
|
||||
##### The Time Conductor and Telemetry
|
||||
|
||||
|
@ -50,16 +50,11 @@ define([
|
||||
const IMAGE_DELAY = 20000;
|
||||
|
||||
function pointForTimestamp(timestamp, name) {
|
||||
const url = IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length];
|
||||
const urlItems = url.split('/');
|
||||
const imageDownloadName = `example.imagery.${urlItems[urlItems.length - 1]}`;
|
||||
|
||||
return {
|
||||
name,
|
||||
name: name,
|
||||
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||
url,
|
||||
imageDownloadName
|
||||
url: IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length]
|
||||
};
|
||||
}
|
||||
|
||||
@ -144,14 +139,6 @@ define([
|
||||
hints: {
|
||||
image: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Image Download Name',
|
||||
key: 'imageDownloadName',
|
||||
format: 'imageDownloadName',
|
||||
hints: {
|
||||
imageDownloadName: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "1.7.2",
|
||||
"version": "1.6.3-SNAPSHOT",
|
||||
"description": "The Open MCT core platform",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
|
@ -154,9 +154,7 @@ define(['zepto', 'objectUtils'], function ($, objectUtils) {
|
||||
tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
|
||||
|
||||
return JSON.parse(tree, (key, value) => {
|
||||
if (value !== undefined
|
||||
&& value !== null
|
||||
&& Object.prototype.hasOwnProperty.call(value, 'key')
|
||||
if (Object.prototype.hasOwnProperty.call(value, 'key')
|
||||
&& Object.prototype.hasOwnProperty.call(value, 'namespace')
|
||||
&& value.key === oldId.key
|
||||
&& value.namespace === oldId.namespace) {
|
||||
|
8
platform/persistence/couch/README.md
Normal file
8
platform/persistence/couch/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# Couch DB Persistence Plugin
|
||||
An adapter for using CouchDB for persistence of user-created objects. The plugin installation function takes the URL
|
||||
for the CouchDB database as a parameter.
|
||||
|
||||
## Installation
|
||||
```js
|
||||
openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct'))
|
||||
```
|
78
platform/persistence/couch/bundle.js
Normal file
78
platform/persistence/couch/bundle.js
Normal file
@ -0,0 +1,78 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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([
|
||||
"./src/CouchPersistenceProvider",
|
||||
"./src/CouchIndicator"
|
||||
], function (
|
||||
CouchPersistenceProvider,
|
||||
CouchIndicator
|
||||
) {
|
||||
|
||||
return {
|
||||
name: "platform/persistence/couch",
|
||||
definition: {
|
||||
"name": "Couch Persistence",
|
||||
"description": "Adapter to read and write objects using a CouchDB instance.",
|
||||
"extensions": {
|
||||
"components": [
|
||||
{
|
||||
"provides": "persistenceService",
|
||||
"type": "provider",
|
||||
"implementation": CouchPersistenceProvider,
|
||||
"depends": [
|
||||
"$http",
|
||||
"$q",
|
||||
"PERSISTENCE_SPACE",
|
||||
"COUCHDB_PATH"
|
||||
]
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "PERSISTENCE_SPACE",
|
||||
"value": "mct"
|
||||
},
|
||||
{
|
||||
"key": "COUCHDB_PATH",
|
||||
"value": "/couch/openmct"
|
||||
},
|
||||
{
|
||||
"key": "COUCHDB_INDICATOR_INTERVAL",
|
||||
"value": 15000
|
||||
}
|
||||
],
|
||||
"indicators": [
|
||||
{
|
||||
"implementation": CouchIndicator,
|
||||
"depends": [
|
||||
"$http",
|
||||
"$interval",
|
||||
"COUCHDB_PATH",
|
||||
"COUCHDB_INDICATOR_INTERVAL"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
61
platform/persistence/couch/src/CouchDocument.js
Normal file
61
platform/persistence/couch/src/CouchDocument.js
Normal file
@ -0,0 +1,61 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* A CouchDocument describes domain object model in a format
|
||||
* which is easily read-written to CouchDB. This includes
|
||||
* Couch's _id and _rev fields, as well as a separate
|
||||
* metadata field which contains a subset of information found
|
||||
* in the model itself (to support search optimization with
|
||||
* CouchDB views.)
|
||||
* @memberof platform/persistence/couch
|
||||
* @constructor
|
||||
* @param {string} id the id under which to store this mode
|
||||
* @param {object} model the model to store
|
||||
* @param {string} rev the revision to include (or undefined,
|
||||
* if no revision should be noted for couch)
|
||||
* @param {boolean} whether or not to mark this document as
|
||||
* deleted (see CouchDB docs for _deleted)
|
||||
*/
|
||||
function CouchDocument(id, model, rev, markDeleted) {
|
||||
return {
|
||||
"_id": id,
|
||||
"_rev": rev,
|
||||
"_deleted": markDeleted,
|
||||
"metadata": {
|
||||
"category": "domain object",
|
||||
"type": model.type,
|
||||
"owner": "admin",
|
||||
"name": model.name,
|
||||
"created": Date.now()
|
||||
},
|
||||
"model": model
|
||||
};
|
||||
}
|
||||
|
||||
return CouchDocument;
|
||||
}
|
||||
);
|
119
platform/persistence/couch/src/CouchIndicator.js
Normal file
119
platform/persistence/couch/src/CouchIndicator.js
Normal file
@ -0,0 +1,119 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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(
|
||||
[],
|
||||
function () {
|
||||
|
||||
// Set of connection states; changing among these states will be
|
||||
// reflected in the indicator's appearance.
|
||||
// CONNECTED: Everything nominal, expect to be able to read/write.
|
||||
// DISCONNECTED: HTTP failed; maybe misconfigured, disconnected.
|
||||
// SEMICONNECTED: Connected to the database, but it reported an error.
|
||||
// PENDING: Still trying to connect, and haven't failed yet.
|
||||
var CONNECTED = {
|
||||
text: "Connected",
|
||||
glyphClass: "ok",
|
||||
statusClass: "s-status-on",
|
||||
description: "Connected to the domain object database."
|
||||
},
|
||||
DISCONNECTED = {
|
||||
text: "Disconnected",
|
||||
glyphClass: "err",
|
||||
statusClass: "s-status-caution",
|
||||
description: "Unable to connect to the domain object database."
|
||||
},
|
||||
SEMICONNECTED = {
|
||||
text: "Unavailable",
|
||||
glyphClass: "caution",
|
||||
statusClass: "s-status-caution",
|
||||
description: "Database does not exist or is unavailable."
|
||||
},
|
||||
PENDING = {
|
||||
text: "Checking connection...",
|
||||
statusClass: "s-status-caution"
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicator for the current CouchDB connection. Polls CouchDB
|
||||
* at a regular interval (defined by bundle constants) to ensure
|
||||
* that the database is available.
|
||||
* @constructor
|
||||
* @memberof platform/persistence/couch
|
||||
* @implements {Indicator}
|
||||
* @param $http Angular's $http service
|
||||
* @param $interval Angular's $interval service
|
||||
* @param {string} path the URL to poll to check for couch availability
|
||||
* @param {number} interval the interval, in milliseconds, to poll at
|
||||
*/
|
||||
function CouchIndicator($http, $interval, path, interval) {
|
||||
var self = this;
|
||||
|
||||
// Track the current connection state
|
||||
this.state = PENDING;
|
||||
|
||||
this.$http = $http;
|
||||
this.$interval = $interval;
|
||||
this.path = path;
|
||||
this.interval = interval;
|
||||
|
||||
// Callback if the HTTP request to Couch fails
|
||||
function handleError() {
|
||||
self.state = DISCONNECTED;
|
||||
}
|
||||
|
||||
// Callback if the HTTP request succeeds. CouchDB may
|
||||
// report an error, so check for that.
|
||||
function handleResponse(response) {
|
||||
var data = response.data;
|
||||
self.state = data.error ? SEMICONNECTED : CONNECTED;
|
||||
}
|
||||
|
||||
// Try to connect to CouchDB, and update the indicator.
|
||||
function updateIndicator() {
|
||||
$http.get(path).then(handleResponse, handleError);
|
||||
}
|
||||
|
||||
// Update the indicator initially, and start polling.
|
||||
updateIndicator();
|
||||
$interval(updateIndicator, interval);
|
||||
}
|
||||
|
||||
CouchIndicator.prototype.getCssClass = function () {
|
||||
return "c-indicator--clickable icon-suitcase " + this.state.statusClass;
|
||||
};
|
||||
|
||||
CouchIndicator.prototype.getGlyphClass = function () {
|
||||
return this.state.glyphClass;
|
||||
};
|
||||
|
||||
CouchIndicator.prototype.getText = function () {
|
||||
return this.state.text;
|
||||
};
|
||||
|
||||
CouchIndicator.prototype.getDescription = function () {
|
||||
return this.state.description;
|
||||
};
|
||||
|
||||
return CouchIndicator;
|
||||
}
|
||||
);
|
145
platform/persistence/couch/src/CouchPersistenceProvider.js
Normal file
145
platform/persistence/couch/src/CouchPersistenceProvider.js
Normal file
@ -0,0 +1,145 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* This bundle implements a persistence service which uses CouchDB to
|
||||
* store documents.
|
||||
* @namespace platform/persistence/cache
|
||||
*/
|
||||
define(
|
||||
["./CouchDocument"],
|
||||
function (CouchDocument) {
|
||||
|
||||
// JSLint doesn't like dangling _'s, but CouchDB uses these, so
|
||||
// hide this behind variables.
|
||||
var REV = "_rev",
|
||||
ID = "_id";
|
||||
|
||||
/**
|
||||
* The CouchPersistenceProvider reads and writes JSON documents
|
||||
* (more specifically, domain object models) to/from a CouchDB
|
||||
* instance.
|
||||
* @memberof platform/persistence/couch
|
||||
* @constructor
|
||||
* @implements {PersistenceService}
|
||||
* @param $http Angular's $http service
|
||||
* @param $interval Angular's $interval service
|
||||
* @param {string} space the name of the persistence space being served
|
||||
* @param {string} path the path to the CouchDB instance
|
||||
*/
|
||||
function CouchPersistenceProvider($http, $q, space, path) {
|
||||
this.spaces = [space];
|
||||
this.revs = {};
|
||||
this.$q = $q;
|
||||
this.$http = $http;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
// Pull out a list of document IDs from CouchDB's
|
||||
// _all_docs response
|
||||
function getIdsFromAllDocs(allDocs) {
|
||||
return allDocs.rows.map(function (r) {
|
||||
return r.id;
|
||||
});
|
||||
}
|
||||
|
||||
// Check the response to a create/update/delete request;
|
||||
// track the rev if it's valid, otherwise return false to
|
||||
// indicate that the request failed.
|
||||
CouchPersistenceProvider.prototype.checkResponse = function (response) {
|
||||
if (response && response.ok) {
|
||||
this.revs[response.id] = response.rev;
|
||||
|
||||
return response.ok;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Get a domain object model out of CouchDB's response
|
||||
CouchPersistenceProvider.prototype.getModel = function (response) {
|
||||
if (response && response.model) {
|
||||
this.revs[response[ID]] = response[REV];
|
||||
|
||||
return response.model;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// Issue a request using $http; get back the plain JS object
|
||||
// from the expected JSON response
|
||||
CouchPersistenceProvider.prototype.request = function (subpath, method, value) {
|
||||
return this.$http({
|
||||
method: method,
|
||||
url: this.path + '/' + subpath,
|
||||
data: value
|
||||
}).then(function (response) {
|
||||
return response.data;
|
||||
}, function () {
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
// Shorthand methods for GET/PUT methods
|
||||
CouchPersistenceProvider.prototype.get = function (subpath) {
|
||||
return this.request(subpath, "GET");
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.put = function (subpath, value) {
|
||||
return this.request(subpath, "PUT", value);
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.listSpaces = function () {
|
||||
return this.$q.when(this.spaces);
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.listObjects = function () {
|
||||
return this.get("_all_docs").then(getIdsFromAllDocs.bind(this));
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.createObject = function (space, key, value) {
|
||||
return this.put(key, new CouchDocument(key, value))
|
||||
.then(this.checkResponse.bind(this));
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.readObject = function (space, key) {
|
||||
return this.get(key).then(this.getModel.bind(this));
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.updateObject = function (space, key, value) {
|
||||
var rev = this.revs[key];
|
||||
|
||||
return this.put(key, new CouchDocument(key, value, rev))
|
||||
.then(this.checkResponse.bind(this));
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.deleteObject = function (space, key, value) {
|
||||
var rev = this.revs[key];
|
||||
|
||||
return this.put(key, new CouchDocument(key, value, rev, true))
|
||||
.then(this.checkResponse.bind(this));
|
||||
};
|
||||
|
||||
return CouchPersistenceProvider;
|
||||
}
|
||||
);
|
63
platform/persistence/couch/test/CouchDocumentSpec.js
Normal file
63
platform/persistence/couch/test/CouchDocumentSpec.js
Normal file
@ -0,0 +1,63 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* DomainObjectProviderSpec. Created by vwoeltje on 11/6/14.
|
||||
*/
|
||||
define(
|
||||
["../src/CouchDocument"],
|
||||
function (CouchDocument) {
|
||||
|
||||
// JSLint doesn't like dangling _'s, but CouchDB uses these, so
|
||||
// hide this behind variables.
|
||||
var REV = "_rev",
|
||||
ID = "_id",
|
||||
DELETED = "_deleted";
|
||||
|
||||
describe("A couch document", function () {
|
||||
it("includes an id", function () {
|
||||
expect(new CouchDocument("testId", {})[ID])
|
||||
.toEqual("testId");
|
||||
});
|
||||
|
||||
it("includes a rev only when one is provided", function () {
|
||||
expect(new CouchDocument("testId", {})[REV])
|
||||
.not.toBeDefined();
|
||||
expect(new CouchDocument("testId", {}, "testRev")[REV])
|
||||
.toEqual("testRev");
|
||||
});
|
||||
|
||||
it("includes the provided model", function () {
|
||||
var model = { someKey: "some value" };
|
||||
expect(new CouchDocument("testId", model).model)
|
||||
.toEqual(model);
|
||||
});
|
||||
|
||||
it("marks documents as deleted only on request", function () {
|
||||
expect(new CouchDocument("testId", {}, "testRev")[DELETED])
|
||||
.not.toBeDefined();
|
||||
expect(new CouchDocument("testId", {}, "testRev", true)[DELETED])
|
||||
.toBe(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
129
platform/persistence/couch/test/CouchIndicatorSpec.js
Normal file
129
platform/persistence/couch/test/CouchIndicatorSpec.js
Normal file
@ -0,0 +1,129 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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(
|
||||
["../src/CouchIndicator"],
|
||||
function (CouchIndicator) {
|
||||
|
||||
xdescribe("The CouchDB status indicator", function () {
|
||||
var mockHttp,
|
||||
mockInterval,
|
||||
testPath,
|
||||
testInterval,
|
||||
mockPromise,
|
||||
indicator;
|
||||
|
||||
beforeEach(function () {
|
||||
mockHttp = jasmine.createSpyObj("$http", ["get"]);
|
||||
mockInterval = jasmine.createSpy("$interval");
|
||||
mockPromise = jasmine.createSpyObj("promise", ["then"]);
|
||||
testPath = "/test/path";
|
||||
testInterval = 12321; // Some number
|
||||
|
||||
mockHttp.get.and.returnValue(mockPromise);
|
||||
|
||||
indicator = new CouchIndicator(
|
||||
mockHttp,
|
||||
mockInterval,
|
||||
testPath,
|
||||
testInterval
|
||||
);
|
||||
});
|
||||
|
||||
it("polls for changes", function () {
|
||||
expect(mockInterval).toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
testInterval
|
||||
);
|
||||
});
|
||||
|
||||
it("has a database icon", function () {
|
||||
expect(indicator.getCssClass()).toEqual("icon-database s-status-caution");
|
||||
});
|
||||
|
||||
it("consults the database at the configured path", function () {
|
||||
expect(mockHttp.get).toHaveBeenCalledWith(testPath);
|
||||
});
|
||||
|
||||
it("changes when the database connection is nominal", function () {
|
||||
var initialText = indicator.getText(),
|
||||
initialDescrption = indicator.getDescription(),
|
||||
initialGlyphClass = indicator.getGlyphClass();
|
||||
|
||||
// Nominal just means getting back an object, without
|
||||
// an error field.
|
||||
mockPromise.then.calls.mostRecent().args[0]({ data: {} });
|
||||
|
||||
// Verify that these values changed;
|
||||
// don't test for specific text.
|
||||
expect(indicator.getText()).not.toEqual(initialText);
|
||||
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
|
||||
expect(indicator.getDescription()).not.toEqual(initialDescrption);
|
||||
|
||||
// Do check for specific class
|
||||
expect(indicator.getGlyphClass()).toEqual("ok");
|
||||
});
|
||||
|
||||
it("changes when the server reports an error", function () {
|
||||
var initialText = indicator.getText(),
|
||||
initialDescrption = indicator.getDescription(),
|
||||
initialGlyphClass = indicator.getGlyphClass();
|
||||
|
||||
// Nominal just means getting back an object, with
|
||||
// an error field.
|
||||
mockPromise.then.calls.mostRecent().args[0](
|
||||
{ data: { error: "Uh oh." } }
|
||||
);
|
||||
|
||||
// Verify that these values changed;
|
||||
// don't test for specific text.
|
||||
expect(indicator.getText()).not.toEqual(initialText);
|
||||
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
|
||||
expect(indicator.getDescription()).not.toEqual(initialDescrption);
|
||||
|
||||
// Do check for specific class
|
||||
expect(indicator.getGlyphClass()).toEqual("caution");
|
||||
|
||||
});
|
||||
|
||||
it("changes when the server cannot be reached", function () {
|
||||
var initialText = indicator.getText(),
|
||||
initialDescrption = indicator.getDescription(),
|
||||
initialGlyphClass = indicator.getGlyphClass();
|
||||
|
||||
// Nominal just means getting back an object, without
|
||||
// an error field.
|
||||
mockPromise.then.calls.mostRecent().args[1]({ data: {} });
|
||||
|
||||
// Verify that these values changed;
|
||||
// don't test for specific text.
|
||||
expect(indicator.getText()).not.toEqual(initialText);
|
||||
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
|
||||
expect(indicator.getDescription()).not.toEqual(initialDescrption);
|
||||
|
||||
// Do check for specific class
|
||||
expect(indicator.getGlyphClass()).toEqual("err");
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
223
platform/persistence/couch/test/CouchPersistenceProviderSpec.js
Normal file
223
platform/persistence/couch/test/CouchPersistenceProviderSpec.js
Normal file
@ -0,0 +1,223 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* DomainObjectProviderSpec. Created by vwoeltje on 11/6/14.
|
||||
*/
|
||||
define(
|
||||
["../src/CouchPersistenceProvider"],
|
||||
function (CouchPersistenceProvider) {
|
||||
|
||||
describe("The couch persistence provider", function () {
|
||||
var mockHttp,
|
||||
mockQ,
|
||||
testSpace = "testSpace",
|
||||
testPath = "/test/db",
|
||||
capture,
|
||||
provider;
|
||||
|
||||
function mockPromise(value) {
|
||||
return {
|
||||
then: function (callback) {
|
||||
return mockPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockHttp = jasmine.createSpy("$http");
|
||||
mockQ = jasmine.createSpyObj("$q", ["when"]);
|
||||
|
||||
mockQ.when.and.callFake(mockPromise);
|
||||
|
||||
// Capture promise results
|
||||
capture = jasmine.createSpy("capture");
|
||||
|
||||
provider = new CouchPersistenceProvider(
|
||||
mockHttp,
|
||||
mockQ,
|
||||
testSpace,
|
||||
testPath
|
||||
);
|
||||
});
|
||||
|
||||
it("reports available spaces", function () {
|
||||
provider.listSpaces().then(capture);
|
||||
expect(capture).toHaveBeenCalledWith([testSpace]);
|
||||
});
|
||||
|
||||
// General pattern of tests below is to simulate CouchDB's
|
||||
// response, verify that request looks like what CouchDB
|
||||
// would expect, and finally verify that CouchPersistenceProvider's
|
||||
// return values match what is expected.
|
||||
it("lists all available documents", function () {
|
||||
mockHttp.and.returnValue(mockPromise({
|
||||
data: { rows: [{ id: "a" }, { id: "b" }, { id: "c" }] }
|
||||
}));
|
||||
provider.listObjects().then(capture);
|
||||
expect(mockHttp).toHaveBeenCalledWith({
|
||||
url: "/test/db/_all_docs", // couch document listing
|
||||
method: "GET",
|
||||
data: undefined
|
||||
});
|
||||
expect(capture).toHaveBeenCalledWith(["a", "b", "c"]);
|
||||
});
|
||||
|
||||
it("allows object creation", function () {
|
||||
var model = { someKey: "some value" };
|
||||
mockHttp.and.returnValue(mockPromise({
|
||||
data: {
|
||||
"_id": "abc",
|
||||
"_rev": "xyz",
|
||||
"ok": true
|
||||
}
|
||||
}));
|
||||
provider.createObject("testSpace", "abc", model).then(capture);
|
||||
expect(mockHttp).toHaveBeenCalledWith({
|
||||
url: "/test/db/abc",
|
||||
method: "PUT",
|
||||
data: {
|
||||
"_id": "abc",
|
||||
"_rev": undefined,
|
||||
"_deleted": undefined,
|
||||
metadata: jasmine.any(Object),
|
||||
model: model
|
||||
}
|
||||
});
|
||||
expect(capture).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("allows object models to be read back", function () {
|
||||
var model = { someKey: "some value" };
|
||||
mockHttp.and.returnValue(mockPromise({
|
||||
data: {
|
||||
"_id": "abc",
|
||||
"_rev": "xyz",
|
||||
"model": model
|
||||
}
|
||||
}));
|
||||
provider.readObject("testSpace", "abc").then(capture);
|
||||
expect(mockHttp).toHaveBeenCalledWith({
|
||||
url: "/test/db/abc",
|
||||
method: "GET",
|
||||
data: undefined
|
||||
});
|
||||
expect(capture).toHaveBeenCalledWith(model);
|
||||
});
|
||||
|
||||
it("allows object update", function () {
|
||||
var model = { someKey: "some value" };
|
||||
|
||||
// First do a read to populate rev tags...
|
||||
mockHttp.and.returnValue(mockPromise({
|
||||
data: {
|
||||
"_id": "abc",
|
||||
"_rev": "xyz",
|
||||
"model": {}
|
||||
}
|
||||
}));
|
||||
provider.readObject("testSpace", "abc");
|
||||
|
||||
// Now perform an update
|
||||
mockHttp.and.returnValue(mockPromise({
|
||||
data: {
|
||||
"_id": "abc",
|
||||
"_rev": "uvw",
|
||||
"ok": true
|
||||
}
|
||||
}));
|
||||
provider.updateObject("testSpace", "abc", model).then(capture);
|
||||
expect(mockHttp).toHaveBeenCalledWith({
|
||||
url: "/test/db/abc",
|
||||
method: "PUT",
|
||||
data: {
|
||||
"_id": "abc",
|
||||
"_rev": "xyz",
|
||||
"_deleted": undefined,
|
||||
metadata: jasmine.any(Object),
|
||||
model: model
|
||||
}
|
||||
});
|
||||
expect(capture).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("allows object deletion", function () {
|
||||
// First do a read to populate rev tags...
|
||||
mockHttp.and.returnValue(mockPromise({
|
||||
data: {
|
||||
"_id": "abc",
|
||||
"_rev": "xyz",
|
||||
"model": {}
|
||||
}
|
||||
}));
|
||||
provider.readObject("testSpace", "abc");
|
||||
|
||||
// Now perform an update
|
||||
mockHttp.and.returnValue(mockPromise({
|
||||
data: {
|
||||
"_id": "abc",
|
||||
"_rev": "uvw",
|
||||
"ok": true
|
||||
}
|
||||
}));
|
||||
provider.deleteObject("testSpace", "abc", {}).then(capture);
|
||||
expect(mockHttp).toHaveBeenCalledWith({
|
||||
url: "/test/db/abc",
|
||||
method: "PUT",
|
||||
data: {
|
||||
"_id": "abc",
|
||||
"_rev": "xyz",
|
||||
"_deleted": true,
|
||||
metadata: jasmine.any(Object),
|
||||
model: {}
|
||||
}
|
||||
});
|
||||
expect(capture).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("reports failure to create objects", function () {
|
||||
var model = { someKey: "some value" };
|
||||
mockHttp.and.returnValue(mockPromise({
|
||||
data: {
|
||||
"_id": "abc",
|
||||
"_rev": "xyz",
|
||||
"ok": false
|
||||
}
|
||||
}));
|
||||
provider.createObject("testSpace", "abc", model).then(capture);
|
||||
expect(capture).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("returns undefined when objects are not found", function () {
|
||||
// Act like a 404
|
||||
mockHttp.and.returnValue({
|
||||
then: function (success, fail) {
|
||||
return mockPromise(fail());
|
||||
}
|
||||
});
|
||||
provider.readObject("testSpace", "abc").then(capture);
|
||||
expect(capture).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
48
src/MCT.js
48
src/MCT.js
@ -284,6 +284,7 @@ define([
|
||||
this.install(this.plugins.ViewDatumAction());
|
||||
this.install(this.plugins.ObjectInterceptors());
|
||||
this.install(this.plugins.NonEditableFolder());
|
||||
this.install(this.plugins.Devices());
|
||||
}
|
||||
|
||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||
@ -403,39 +404,24 @@ define([
|
||||
this.router.setPath('/browse/');
|
||||
});
|
||||
|
||||
/**
|
||||
* Fired by [MCT]{@link module:openmct.MCT} when the application
|
||||
* is started.
|
||||
* @event start
|
||||
* @memberof module:openmct.MCT~
|
||||
*/
|
||||
const startPromise = new Main();
|
||||
startPromise.run(this)
|
||||
.then(function (angular) {
|
||||
this.$angular = angular;
|
||||
// OpenMCT Object provider doesn't operate properly unless
|
||||
// something has depended upon objectService. Cool, right?
|
||||
this.$injector.get('objectService');
|
||||
if (!isHeadlessMode) {
|
||||
const appLayout = new Vue({
|
||||
components: {
|
||||
'Layout': Layout.default
|
||||
},
|
||||
provide: {
|
||||
openmct: this
|
||||
},
|
||||
template: '<Layout ref="layout"></Layout>'
|
||||
});
|
||||
domElement.appendChild(appLayout.$mount().$el);
|
||||
|
||||
if (!isHeadlessMode) {
|
||||
const appLayout = new Vue({
|
||||
components: {
|
||||
'Layout': Layout.default
|
||||
},
|
||||
provide: {
|
||||
openmct: this
|
||||
},
|
||||
template: '<Layout ref="layout"></Layout>'
|
||||
});
|
||||
domElement.appendChild(appLayout.$mount().$el);
|
||||
this.layout = appLayout.$refs.layout;
|
||||
Browse(this);
|
||||
}
|
||||
|
||||
this.layout = appLayout.$refs.layout;
|
||||
Browse(this);
|
||||
}
|
||||
|
||||
this.router.start();
|
||||
this.emit('start');
|
||||
}.bind(this));
|
||||
this.router.start();
|
||||
this.emit('start');
|
||||
};
|
||||
|
||||
MCT.prototype.startHeadless = function () {
|
||||
|
@ -52,7 +52,7 @@ define([
|
||||
|
||||
oldStyleObject.getCapability('mutation').mutate(function () {
|
||||
return utils.toOldFormat(newStyleObject);
|
||||
}, newStyleObject.modified);
|
||||
});
|
||||
|
||||
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
|
||||
}.bind(this);
|
||||
|
@ -24,13 +24,6 @@ define([
|
||||
return false;
|
||||
}
|
||||
|
||||
//TODO: Remove this when plots Angular implementation is deprecated
|
||||
let parent = selection[0].length > 1 && selection[0][1].context.item;
|
||||
if (parent && parent.type === 'time-strip') {
|
||||
return (selectionContext.item.type === typeDefinition.key)
|
||||
&& (typeDefinition.key !== 'telemetry.plot.overlay');
|
||||
}
|
||||
|
||||
return selectionContext.item.type === typeDefinition.key;
|
||||
},
|
||||
view: function (selection) {
|
||||
|
@ -76,10 +76,7 @@ class MutableDomainObject {
|
||||
}
|
||||
$set(path, value) {
|
||||
_.set(this, path, value);
|
||||
|
||||
if (path !== 'persisted' && path !== 'modified') {
|
||||
_.set(this, 'modified', Date.now());
|
||||
}
|
||||
_.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);
|
||||
@ -115,11 +112,9 @@ class MutableDomainObject {
|
||||
return () => this._instanceEventEmitter.off(event, callback);
|
||||
}
|
||||
$destroy() {
|
||||
while (this._observers.length > 0) {
|
||||
const observer = this._observers.pop();
|
||||
observer();
|
||||
}
|
||||
|
||||
this._observers.forEach(observer => observer());
|
||||
delete this._globalEventEmitter;
|
||||
delete this._observers;
|
||||
this._instanceEventEmitter.emit('$_destroy');
|
||||
}
|
||||
|
||||
|
@ -38,9 +38,6 @@ function ObjectAPI(typeRegistry, openmct) {
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.providers = {};
|
||||
this.rootRegistry = new RootRegistry();
|
||||
this.injectIdentifierService = function () {
|
||||
this.identifierService = openmct.$injector.get("identifierService");
|
||||
};
|
||||
|
||||
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
||||
this.cache = {};
|
||||
@ -55,33 +52,16 @@ ObjectAPI.prototype.supersecretSetFallbackProvider = function (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.
|
||||
* @private
|
||||
*/
|
||||
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') {
|
||||
return this.rootProvider;
|
||||
}
|
||||
|
||||
return this.providers[namespace] || this.fallbackProvider;
|
||||
return this.providers[identifier.namespace] || this.fallbackProvider;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -161,7 +141,6 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
||||
|
||||
ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
||||
let keystring = this.makeKeyString(identifier);
|
||||
|
||||
if (this.cache[keystring] !== undefined) {
|
||||
return this.cache[keystring];
|
||||
}
|
||||
@ -177,16 +156,15 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
||||
throw new Error('Provider does not support get!');
|
||||
}
|
||||
|
||||
let objectPromise = provider.get(identifier, abortSignal).then(result => {
|
||||
let objectPromise = provider.get(identifier, abortSignal);
|
||||
this.cache[keystring] = objectPromise;
|
||||
|
||||
return objectPromise.then(result => {
|
||||
delete this.cache[keystring];
|
||||
result = this.applyGetInterceptors(identifier, result);
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
this.cache[keystring] = objectPromise;
|
||||
|
||||
return objectPromise;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -522,10 +500,8 @@ ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) {
|
||||
*/
|
||||
|
||||
function hasAlreadyBeenPersisted(domainObject) {
|
||||
const result = domainObject.persisted !== undefined
|
||||
&& domainObject.persisted >= domainObject.modified;
|
||||
|
||||
return result;
|
||||
return domainObject.persisted !== undefined
|
||||
&& domainObject.persisted === domainObject.modified;
|
||||
}
|
||||
|
||||
export default ObjectAPI;
|
||||
|
@ -116,11 +116,9 @@ define([
|
||||
* @private
|
||||
*/
|
||||
DefaultMetadataProvider.prototype.typeHasTelemetry = function (domainObject) {
|
||||
if (!this.typeService) {
|
||||
this.typeService = this.openmct.$injector.get('typeService');
|
||||
}
|
||||
const type = this.openmct.types.get(domainObject.type);
|
||||
|
||||
return Boolean(this.typeService.getType(domainObject.type).typeDef.telemetry);
|
||||
return Boolean(type.definition.telemetry);
|
||||
};
|
||||
|
||||
return DefaultMetadataProvider;
|
||||
|
@ -142,6 +142,8 @@ define([
|
||||
this.metadataCache = new WeakMap();
|
||||
this.formatMapCache = new WeakMap();
|
||||
this.valueFormatterCache = new WeakMap();
|
||||
|
||||
this.formatters = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -413,17 +415,6 @@ define([
|
||||
return _.sortBy(options, sortKeys);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryAPI.prototype.getFormatService = function () {
|
||||
if (!this.formatService) {
|
||||
this.formatService = this.openmct.$injector.get('formatService');
|
||||
}
|
||||
|
||||
return this.formatService;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a value formatter for a given valueMetadata.
|
||||
*
|
||||
@ -433,7 +424,7 @@ define([
|
||||
if (!this.valueFormatterCache.has(valueMetadata)) {
|
||||
this.valueFormatterCache.set(
|
||||
valueMetadata,
|
||||
new TelemetryValueFormatter(valueMetadata, this.getFormatService())
|
||||
new TelemetryValueFormatter(valueMetadata, this.formatters)
|
||||
);
|
||||
}
|
||||
|
||||
@ -447,9 +438,11 @@ define([
|
||||
* @returns {Format}
|
||||
*/
|
||||
TelemetryAPI.prototype.getFormatter = function (key) {
|
||||
const formatMap = this.getFormatService().formatMap;
|
||||
|
||||
return formatMap[key];
|
||||
if (this.formatters.has(key)) {
|
||||
return this.formatters.get(key);
|
||||
} else {
|
||||
throw new Error(`Unknown type ${key}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -476,12 +469,7 @@ define([
|
||||
* @param {Format} format the
|
||||
*/
|
||||
TelemetryAPI.prototype.addFormat = function (format) {
|
||||
this.openmct.legacyExtension('formats', {
|
||||
key: format.key,
|
||||
implementation: function () {
|
||||
return format;
|
||||
}
|
||||
});
|
||||
this.formatters.set(format.key, format);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -28,8 +28,7 @@ define([
|
||||
printj
|
||||
) {
|
||||
|
||||
// TODO: needs reference to formatService;
|
||||
function TelemetryValueFormatter(valueMetadata, formatService) {
|
||||
function TelemetryValueFormatter(valueMetadata, formatters) {
|
||||
const numberFormatter = {
|
||||
parse: function (x) {
|
||||
return Number(x);
|
||||
@ -43,13 +42,7 @@ define([
|
||||
};
|
||||
|
||||
this.valueMetadata = valueMetadata;
|
||||
try {
|
||||
this.formatter = formatService
|
||||
.getFormat(valueMetadata.format, valueMetadata);
|
||||
} catch (e) {
|
||||
// TODO: Better formatting
|
||||
this.formatter = numberFormatter;
|
||||
}
|
||||
this.formatter = formatters.get(valueMetadata.format) || numberFormatter;
|
||||
|
||||
if (valueMetadata.format === 'enum') {
|
||||
this.formatter = {};
|
||||
|
@ -90,6 +90,7 @@ define([
|
||||
'../platform/framework/src/load/Bundle',
|
||||
'../platform/identity/bundle',
|
||||
'../platform/persistence/aggregator/bundle',
|
||||
'../platform/persistence/couch/bundle',
|
||||
'../platform/persistence/elastic/bundle',
|
||||
'../platform/persistence/local/bundle',
|
||||
'../platform/persistence/queue/bundle',
|
||||
|
@ -43,16 +43,12 @@ export default function LADTableSetViewProvider(openmct) {
|
||||
components: {
|
||||
LadTableSet: LadTableSet
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domainObject
|
||||
};
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
objectPath
|
||||
},
|
||||
template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
|
||||
template: '<lad-table-set></lad-table-set>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
|
@ -56,7 +56,7 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
pathToTable: {
|
||||
objectPath: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
@ -66,19 +66,20 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let currentObjectPath = this.objectPath.slice();
|
||||
currentObjectPath.unshift(this.domainObject);
|
||||
|
||||
return {
|
||||
timestamp: undefined,
|
||||
value: '---',
|
||||
valueClass: '',
|
||||
currentObjectPath,
|
||||
unit: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
formattedTimestamp() {
|
||||
return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---';
|
||||
},
|
||||
objectPath() {
|
||||
return [this.domainObject, ...this.pathToTable];
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -181,7 +182,7 @@ export default {
|
||||
};
|
||||
},
|
||||
showContextMenu(event) {
|
||||
let actionCollection = this.openmct.actions.get(this.objectPath, this.getView());
|
||||
let actionCollection = this.openmct.actions.get(this.currentObjectPath, this.getView());
|
||||
let allActions = actionCollection.getActionsObject();
|
||||
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
|
||||
|
||||
|
@ -33,10 +33,10 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<lad-row
|
||||
v-for="ladRow in items"
|
||||
:key="ladRow.key"
|
||||
:domain-object="ladRow.domainObject"
|
||||
:path-to-table="objectPath"
|
||||
v-for="item in items"
|
||||
:key="item.key"
|
||||
:domain-object="item.domainObject"
|
||||
:object-path="objectPath"
|
||||
:has-units="hasUnits"
|
||||
/>
|
||||
</tbody>
|
||||
|
@ -43,10 +43,9 @@
|
||||
</td>
|
||||
</tr>
|
||||
<lad-row
|
||||
v-for="ladRow in ladTelemetryObjects[ladTable.key]"
|
||||
:key="ladRow.key"
|
||||
:domain-object="ladRow.domainObject"
|
||||
:path-to-table="ladTable.objectPath"
|
||||
v-for="telemetryObject in ladTelemetryObjects[ladTable.key]"
|
||||
:key="telemetryObject.key"
|
||||
:domain-object="telemetryObject.domainObject"
|
||||
:has-units="hasUnits"
|
||||
/>
|
||||
</template>
|
||||
@ -61,13 +60,7 @@ export default {
|
||||
components: {
|
||||
LadRow
|
||||
},
|
||||
inject: ['openmct', 'objectPath'],
|
||||
props: {
|
||||
domainObject: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
ladTableObjects: [],
|
||||
@ -113,7 +106,6 @@ export default {
|
||||
let ladTable = {};
|
||||
ladTable.domainObject = domainObject;
|
||||
ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
ladTable.objectPath = [domainObject, ...this.objectPath];
|
||||
|
||||
this.$set(this.ladTelemetryObjects, ladTable.key, []);
|
||||
this.ladTableObjects.push(ladTable);
|
||||
|
@ -82,9 +82,7 @@ export default class Condition extends EventEmitter {
|
||||
if (this.isAnyOrAllTelemetry(criterion)) {
|
||||
criterion.updateResult(datum, this.conditionManager.telemetryObjects);
|
||||
} else {
|
||||
if (criterion.usesTelemetry(datum.id)) {
|
||||
criterion.updateResult(datum);
|
||||
}
|
||||
criterion.updateResult(datum);
|
||||
}
|
||||
});
|
||||
|
||||
@ -104,7 +102,7 @@ export default class Condition extends EventEmitter {
|
||||
|
||||
isTelemetryUsed(id) {
|
||||
return this.criteria.some(criterion => {
|
||||
return this.isAnyOrAllTelemetry(criterion) || criterion.usesTelemetry(id);
|
||||
return this.isAnyOrAllTelemetry(criterion) || criterion.telemetryObjectIdAsString === id;
|
||||
});
|
||||
}
|
||||
|
||||
@ -272,11 +270,11 @@ export default class Condition extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
requestLADConditionResult(options) {
|
||||
requestLADConditionResult() {
|
||||
let latestTimestamp;
|
||||
let criteriaResults = {};
|
||||
const criteriaRequests = this.criteria
|
||||
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects, options));
|
||||
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects));
|
||||
|
||||
return Promise.all(criteriaRequests)
|
||||
.then(results => {
|
||||
|
@ -282,7 +282,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
return currentCondition;
|
||||
}
|
||||
|
||||
requestLADConditionSetOutput(options) {
|
||||
requestLADConditionSetOutput() {
|
||||
if (!this.conditions.length) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
@ -291,7 +291,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
let latestTimestamp;
|
||||
let conditionResults = {};
|
||||
const conditionRequests = this.conditions
|
||||
.map(condition => condition.requestLADConditionResult(options));
|
||||
.map(condition => condition.requestLADConditionResult());
|
||||
|
||||
return Promise.all(conditionRequests)
|
||||
.then((results) => {
|
||||
|
@ -40,10 +40,10 @@ export default class ConditionSetTelemetryProvider {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
request(domainObject, options) {
|
||||
request(domainObject) {
|
||||
let conditionManager = this.getConditionManager(domainObject);
|
||||
|
||||
return conditionManager.requestLADConditionSetOutput(options)
|
||||
return conditionManager.requestLADConditionSetOutput()
|
||||
.then(latestOutput => {
|
||||
return latestOutput;
|
||||
});
|
||||
@ -52,9 +52,7 @@ export default class ConditionSetTelemetryProvider {
|
||||
subscribe(domainObject, callback) {
|
||||
let conditionManager = this.getConditionManager(domainObject);
|
||||
|
||||
conditionManager.on('conditionSetResultUpdated', (data) => {
|
||||
callback(data);
|
||||
});
|
||||
conditionManager.on('conditionSetResultUpdated', callback);
|
||||
|
||||
return this.destroyConditionManager.bind(this, this.openmct.objects.makeKeyString(domainObject.identifier));
|
||||
}
|
||||
|
@ -35,7 +35,6 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
if (styleConfiguration) {
|
||||
this.initialize(styleConfiguration);
|
||||
if (styleConfiguration.conditionSetIdentifier) {
|
||||
this.openmct.time.on("bounds", this.refreshData.bind(this));
|
||||
this.subscribeToConditionSet();
|
||||
} else {
|
||||
this.applyStaticStyle();
|
||||
@ -84,25 +83,6 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
refreshData(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
let options = {
|
||||
start: bounds.start,
|
||||
end: bounds.end,
|
||||
size: 1,
|
||||
strategy: 'latest'
|
||||
};
|
||||
this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
|
||||
this.openmct.telemetry.request(conditionSetDomainObject, options)
|
||||
.then(output => {
|
||||
if (output && output.length) {
|
||||
this.handleConditionSetResultUpdated(output[0]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateObjectStyleConfig(styleConfiguration) {
|
||||
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
|
||||
this.initialize(styleConfiguration || {});
|
||||
@ -180,14 +160,10 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
|
||||
destroy() {
|
||||
if (this.stopProvidingTelemetry) {
|
||||
|
||||
this.stopProvidingTelemetry();
|
||||
delete this.stopProvidingTelemetry;
|
||||
}
|
||||
|
||||
this.openmct.time.off("bounds", this.refreshData);
|
||||
this.openmct.editor.off('isEditing', this.toggleSubscription);
|
||||
|
||||
this.conditionSetIdentifier = undefined;
|
||||
}
|
||||
|
||||
|
@ -344,11 +344,6 @@ export default {
|
||||
const layoutItem = selectionItem[0].context.layoutItem;
|
||||
const isChildItem = selectionItem.length > 1;
|
||||
|
||||
if (!item && !layoutItem) {
|
||||
// cases where selection is used for table cells
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isChildItem) {
|
||||
domainObject = item;
|
||||
itemStyle = getApplicableStylesForItem(item);
|
||||
|
@ -147,16 +147,12 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
|
||||
}
|
||||
|
||||
requestLAD(telemetryObjects, requestOptions) {
|
||||
let options = {
|
||||
requestLAD(telemetryObjects) {
|
||||
const options = {
|
||||
strategy: 'latest',
|
||||
size: 1
|
||||
};
|
||||
|
||||
if (requestOptions !== undefined) {
|
||||
options = Object.assign(options, requestOptions);
|
||||
}
|
||||
|
||||
if (!this.isValid()) {
|
||||
return this.formatData({}, telemetryObjects);
|
||||
}
|
||||
|
@ -58,10 +58,6 @@ export default class TelemetryCriterion extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
usesTelemetry(id) {
|
||||
return this.telemetryObjectIdAsString && (this.telemetryObjectIdAsString === id);
|
||||
}
|
||||
|
||||
subscribeForStaleData() {
|
||||
if (this.stalenessSubscription) {
|
||||
this.stalenessSubscription.clear();
|
||||
@ -137,16 +133,12 @@ export default class TelemetryCriterion extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
requestLAD(telemetryObjects, requestOptions) {
|
||||
let options = {
|
||||
requestLAD() {
|
||||
const options = {
|
||||
strategy: 'latest',
|
||||
size: 1
|
||||
};
|
||||
|
||||
if (requestOptions !== undefined) {
|
||||
options = Object.assign(options, requestOptions);
|
||||
}
|
||||
|
||||
if (!this.isValid()) {
|
||||
return {
|
||||
id: this.id,
|
||||
|
@ -104,7 +104,7 @@ export function getConsolidatedStyleValues(multipleItemStyles) {
|
||||
const properties = Object.keys(styleProps);
|
||||
properties.forEach((property) => {
|
||||
const values = aggregatedStyleValues[property];
|
||||
if (values && values.length) {
|
||||
if (values.length) {
|
||||
if (values.every(value => value === values[0])) {
|
||||
styleValues[property] = values[0];
|
||||
} else {
|
||||
|
31
src/plugins/devices/plugin.js
Normal file
31
src/plugins/devices/plugin.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 plugin() {
|
||||
return function install(openmct) {
|
||||
openmct.on('start', () => {
|
||||
const body = document.getElementsByTagName('body')[0];
|
||||
body.classList.add('desktop');
|
||||
body.classList.add('portrait');
|
||||
});
|
||||
};
|
||||
}
|
@ -147,7 +147,7 @@ export default {
|
||||
this.mutablePromise.then(() => {
|
||||
this.openmct.objects.destroyMutable(this.domainObject);
|
||||
});
|
||||
} else if (this.domainObject.isMutable) {
|
||||
} else {
|
||||
this.openmct.objects.destroyMutable(this.domainObject);
|
||||
}
|
||||
},
|
||||
|
@ -240,7 +240,7 @@ export default {
|
||||
this.mutablePromise.then(() => {
|
||||
this.openmct.objects.destroyMutable(this.domainObject);
|
||||
});
|
||||
} else if (this.domainObject.isMutable) {
|
||||
} else {
|
||||
this.openmct.objects.destroyMutable(this.domainObject);
|
||||
}
|
||||
},
|
||||
@ -269,12 +269,7 @@ export default {
|
||||
},
|
||||
subscribeToObject() {
|
||||
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
|
||||
const key = this.openmct.time.timeSystem().key;
|
||||
const datumTimeStamp = datum[key];
|
||||
if (this.openmct.time.clock() !== undefined
|
||||
|| (datumTimeStamp
|
||||
&& (this.openmct.time.bounds().end >= datumTimeStamp))
|
||||
) {
|
||||
if (this.openmct.time.clock() !== undefined) {
|
||||
this.updateView(datum);
|
||||
}
|
||||
}.bind(this));
|
||||
|
@ -90,12 +90,14 @@ export default {
|
||||
this.composition.load();
|
||||
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
|
||||
this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters);
|
||||
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.composition.off('add', this.addChildren);
|
||||
this.composition.off('remove', this.removeChildren);
|
||||
this.unobserve();
|
||||
this.unobserveGlobalFilters();
|
||||
this.unobserveAllMutation();
|
||||
},
|
||||
methods: {
|
||||
addChildren(domainObject) {
|
||||
@ -156,28 +158,25 @@ export default {
|
||||
},
|
||||
getGlobalFiltersToRemove(keyString) {
|
||||
let filtersToRemove = new Set();
|
||||
const child = this.children[keyString];
|
||||
if (child && child.metadataWithFilters) {
|
||||
const metadataWithFilters = child.metadataWithFilters;
|
||||
metadataWithFilters.forEach(metadatum => {
|
||||
let keepFilter = false;
|
||||
Object.keys(this.children).forEach(childKeyString => {
|
||||
if (childKeyString !== keyString) {
|
||||
let filterMatched = this.children[childKeyString].metadataWithFilters.some(childMetadatum => childMetadatum.key === metadatum.key);
|
||||
|
||||
if (filterMatched) {
|
||||
keepFilter = true;
|
||||
this.children[keyString].metadataWithFilters.forEach(metadatum => {
|
||||
let keepFilter = false;
|
||||
Object.keys(this.children).forEach(childKeyString => {
|
||||
if (childKeyString !== keyString) {
|
||||
let filterMatched = this.children[childKeyString].metadataWithFilters.some(childMetadatum => childMetadatum.key === metadatum.key);
|
||||
|
||||
return;
|
||||
}
|
||||
if (filterMatched) {
|
||||
keepFilter = true;
|
||||
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (!keepFilter) {
|
||||
filtersToRemove.add(metadatum.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!keepFilter) {
|
||||
filtersToRemove.add(metadatum.key);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(filtersToRemove);
|
||||
},
|
||||
|
@ -29,6 +29,13 @@ define([
|
||||
) {
|
||||
return function plugin() {
|
||||
return function install(openmct) {
|
||||
openmct.types.addType('folder', {
|
||||
name: "Folder",
|
||||
key: "folder",
|
||||
description: "Create folders to organize other objects or links to objects without the ability to edit it's properties.",
|
||||
cssClass: "icon-folder",
|
||||
creatable: true
|
||||
});
|
||||
openmct.objectViews.addProvider(new FolderGridView(openmct));
|
||||
openmct.objectViews.addProvider(new FolderListView(openmct));
|
||||
};
|
||||
|
@ -23,7 +23,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="c-compass"
|
||||
:style="`width: ${ sizedImageDimensions.width }px; height: ${ sizedImageDimensions.height }px`"
|
||||
:style="compassDimensionsStyle"
|
||||
>
|
||||
<CompassHUD
|
||||
v-if="hasCameraFieldOfView"
|
||||
@ -34,7 +34,6 @@
|
||||
<CompassRose
|
||||
v-if="hasCameraFieldOfView"
|
||||
:heading="heading"
|
||||
:sized-image-width="sizedImageDimensions.width"
|
||||
:sun-heading="sunHeading"
|
||||
:camera-angle-of-view="cameraAngleOfView"
|
||||
:camera-pan="cameraPan"
|
||||
@ -78,20 +77,6 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sizedImageDimensions() {
|
||||
let sizedImageDimensions = {};
|
||||
if ((this.containerWidth / this.containerHeight) > this.naturalAspectRatio) {
|
||||
// container is wider than image
|
||||
sizedImageDimensions.width = this.containerHeight * this.naturalAspectRatio;
|
||||
sizedImageDimensions.height = this.containerHeight;
|
||||
} else {
|
||||
// container is taller than image
|
||||
sizedImageDimensions.width = this.containerWidth;
|
||||
sizedImageDimensions.height = this.containerWidth * this.naturalAspectRatio;
|
||||
}
|
||||
|
||||
return sizedImageDimensions;
|
||||
},
|
||||
hasCameraFieldOfView() {
|
||||
return this.cameraPan !== undefined && this.cameraAngleOfView > 0;
|
||||
},
|
||||
@ -109,6 +94,25 @@ export default {
|
||||
},
|
||||
cameraAngleOfView() {
|
||||
return CAMERA_ANGLE_OF_VIEW;
|
||||
},
|
||||
compassDimensionsStyle() {
|
||||
const containerAspectRatio = this.containerWidth / this.containerHeight;
|
||||
|
||||
let width;
|
||||
let height;
|
||||
|
||||
if (containerAspectRatio < this.naturalAspectRatio) {
|
||||
width = '100%';
|
||||
height = `${ this.containerWidth / this.naturalAspectRatio }px`;
|
||||
} else {
|
||||
width = `${ this.containerHeight * this.naturalAspectRatio }px`;
|
||||
height = '100%';
|
||||
}
|
||||
|
||||
return {
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -22,134 +22,129 @@
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="w-direction-rose"
|
||||
:class="compassRoseSizingClasses"
|
||||
class="c-direction-rose"
|
||||
@click="toggleLockCompass"
|
||||
>
|
||||
<div
|
||||
class="c-direction-rose"
|
||||
@click="toggleLockCompass"
|
||||
class="c-nsew"
|
||||
:style="compassRoseStyle"
|
||||
>
|
||||
<div
|
||||
class="c-nsew"
|
||||
:style="compassRoseStyle"
|
||||
<svg
|
||||
class="c-nsew__minor-ticks"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<svg
|
||||
class="c-nsew__minor-ticks"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-ne"
|
||||
x="49"
|
||||
y="0"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-se"
|
||||
x="95"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-sw"
|
||||
x="49"
|
||||
y="95"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-nw"
|
||||
x="0"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-ne"
|
||||
x="49"
|
||||
y="0"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-se"
|
||||
x="95"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-sw"
|
||||
x="49"
|
||||
y="95"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-nw"
|
||||
x="0"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
class="c-nsew__ticks"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<polygon
|
||||
class="c-nsew__tick c-tick-n"
|
||||
points="50,0 60,10 40,10"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-e"
|
||||
x="95"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-w"
|
||||
x="0"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-s"
|
||||
x="49"
|
||||
y="95"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
<svg
|
||||
class="c-nsew__ticks"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<polygon
|
||||
class="c-nsew__tick c-tick-n"
|
||||
points="50,0 57,5 43,5"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-e"
|
||||
x="95"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-w"
|
||||
x="0"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-s"
|
||||
x="49"
|
||||
y="95"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
|
||||
<text
|
||||
class="c-nsew__label c-label-n"
|
||||
text-anchor="middle"
|
||||
:transform="northTextTransform"
|
||||
>N</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-e"
|
||||
text-anchor="middle"
|
||||
:transform="eastTextTransform"
|
||||
>E</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-w"
|
||||
text-anchor="middle"
|
||||
:transform="southTextTransform"
|
||||
>W</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-s"
|
||||
text-anchor="middle"
|
||||
:transform="westTextTransform"
|
||||
>S</text>
|
||||
</svg>
|
||||
<text
|
||||
class="c-nsew__label c-label-n"
|
||||
text-anchor="middle"
|
||||
:transform="northTextTransform"
|
||||
>N</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-e"
|
||||
text-anchor="middle"
|
||||
:transform="eastTextTransform"
|
||||
>E</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-w"
|
||||
text-anchor="middle"
|
||||
:transform="southTextTransform"
|
||||
>W</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-s"
|
||||
text-anchor="middle"
|
||||
:transform="westTextTransform"
|
||||
>S</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasHeading"
|
||||
class="c-spacecraft-body"
|
||||
:style="headingStyle"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasSunHeading"
|
||||
class="c-sun"
|
||||
:style="sunHeadingStyle"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="c-cam-field"
|
||||
:style="cameraPanStyle"
|
||||
>
|
||||
<div class="cam-field-half cam-field-half-l">
|
||||
<div
|
||||
class="cam-field-area"
|
||||
:style="cameraFOVStyleLeftHalf"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasHeading"
|
||||
class="c-spacecraft-body"
|
||||
:style="headingStyle"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasSunHeading"
|
||||
class="c-sun"
|
||||
:style="sunHeadingStyle"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="c-cam-field"
|
||||
:style="cameraPanStyle"
|
||||
>
|
||||
<div class="cam-field-half cam-field-half-l">
|
||||
<div
|
||||
class="cam-field-area"
|
||||
:style="cameraFOVStyleLeftHalf"
|
||||
></div>
|
||||
</div>
|
||||
<div class="cam-field-half cam-field-half-r">
|
||||
<div
|
||||
class="cam-field-area"
|
||||
:style="cameraFOVStyleRightHalf"
|
||||
></div>
|
||||
</div>
|
||||
<div class="cam-field-half cam-field-half-r">
|
||||
<div
|
||||
class="cam-field-area"
|
||||
:style="cameraFOVStyleRightHalf"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -160,10 +155,6 @@ import { rotate } from './utils';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
sizedImageWidth: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
heading: {
|
||||
type: Number,
|
||||
required: true
|
||||
@ -186,24 +177,12 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
compassRoseSizingClasses() {
|
||||
let compassRoseSizingClasses = '';
|
||||
if (this.sizedImageWidth < 300) {
|
||||
compassRoseSizingClasses = '--rose-small --rose-min';
|
||||
} else if (this.sizedImageWidth < 500) {
|
||||
compassRoseSizingClasses = '--rose-small';
|
||||
} else if (this.sizedImageWidth > 1000) {
|
||||
compassRoseSizingClasses = '--rose-max';
|
||||
}
|
||||
|
||||
return compassRoseSizingClasses;
|
||||
north() {
|
||||
return this.lockCompass ? rotate(-this.cameraPan) : 0;
|
||||
},
|
||||
compassRoseStyle() {
|
||||
return { transform: `rotate(${ this.north }deg)` };
|
||||
},
|
||||
north() {
|
||||
return this.lockCompass ? rotate(-this.cameraPan) : 0;
|
||||
},
|
||||
northTextTransform() {
|
||||
return this.cardinalPointsTextTransform.north;
|
||||
},
|
||||
@ -225,10 +204,10 @@ export default {
|
||||
const rotation = `rotate(${ -this.north })`;
|
||||
|
||||
return {
|
||||
north: `translate(50,23) ${ rotation }`,
|
||||
east: `translate(82,50) ${ rotation }`,
|
||||
south: `translate(18,50) ${ rotation }`,
|
||||
west: `translate(50,82) ${ rotation }`
|
||||
north: `translate(50,15) ${ rotation }`,
|
||||
east: `translate(87,50) ${ rotation }`,
|
||||
south: `translate(13,50) ${ rotation }`,
|
||||
west: `translate(50,87) ${ rotation }`
|
||||
};
|
||||
},
|
||||
hasHeading() {
|
||||
|
@ -20,252 +20,195 @@ $elemBg: rgba(black, 0.7);
|
||||
|
||||
/***************************** COMPASS HUD */
|
||||
.c-hud {
|
||||
// To be placed within a imagery view, in the bounding box of the image
|
||||
$m: 1px;
|
||||
$padTB: 2px;
|
||||
$padLR: $padTB;
|
||||
color: $interfaceKeyColor;
|
||||
font-size: 0.8em;
|
||||
// To be placed within a imagery view, in the bounding box of the image
|
||||
$m: 1px;
|
||||
$padTB: 2px;
|
||||
$padLR: $padTB;
|
||||
color: $interfaceKeyColor;
|
||||
font-size: 0.8em;
|
||||
position: absolute;
|
||||
top: $m; right: $m; left: $m;
|
||||
height: 18px;
|
||||
|
||||
svg, div {
|
||||
position: absolute;
|
||||
top: $m;
|
||||
right: $m;
|
||||
left: $m;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
svg, div {
|
||||
position: absolute;
|
||||
}
|
||||
&__display {
|
||||
height: 30px;
|
||||
pointer-events: all;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&__display {
|
||||
height: 30px;
|
||||
pointer-events: all;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
&__range {
|
||||
border: 1px solid $interfaceKeyColor;
|
||||
border-top-color: transparent;
|
||||
position: absolute;
|
||||
top: 50%; right: $padLR; bottom: $padTB; left: $padLR;
|
||||
}
|
||||
|
||||
&__range {
|
||||
border: 1px solid $interfaceKeyColor;
|
||||
border-top-color: transparent;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: $padLR;
|
||||
bottom: $padTB;
|
||||
left: $padLR;
|
||||
}
|
||||
[class*="__dir"] {
|
||||
// NSEW
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 1px 2px black;
|
||||
top: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
[class*="__dir"] {
|
||||
// NSEW
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 1px 2px black;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 2;
|
||||
}
|
||||
[class*="__dir--sub"] {
|
||||
font-weight: normal;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
[class*="__dir--sub"] {
|
||||
font-weight: normal;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&__sun {
|
||||
$s: 10px;
|
||||
@include sun('circle farthest-side at bottom');
|
||||
bottom: $padTB + 2px;
|
||||
height: $s;
|
||||
width: $s*2;
|
||||
opacity: 0.8;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
&__sun {
|
||||
$s: 10px;
|
||||
@include sun('circle farthest-side at bottom');
|
||||
bottom: $padTB + 2px;
|
||||
height: $s; width: $s*2;
|
||||
opacity: 0.8;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/***************************** COMPASS DIRECTIONS */
|
||||
.c-nsew {
|
||||
$color: $interfaceKeyColor;
|
||||
$inset: 5%;
|
||||
$tickHeightPerc: 15%;
|
||||
text-shadow: black 0 0 10px;
|
||||
top: $inset;
|
||||
right: $inset;
|
||||
bottom: $inset;
|
||||
left: $inset;
|
||||
z-index: 3;
|
||||
$color: $interfaceKeyColor;
|
||||
$inset: 7%;
|
||||
$tickHeightPerc: 15%;
|
||||
text-shadow: black 0 0 10px;
|
||||
top: $inset; right: $inset; bottom: $inset; left: $inset;
|
||||
z-index: 3;
|
||||
|
||||
&__tick,
|
||||
&__label {
|
||||
fill: $color;
|
||||
}
|
||||
&__tick,
|
||||
&__label {
|
||||
fill: $color;
|
||||
}
|
||||
|
||||
&__minor-ticks {
|
||||
opacity: 0.5;
|
||||
transform-origin: center;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
&__minor-ticks {
|
||||
opacity: 0.5;
|
||||
transform-origin: center;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
&__label {
|
||||
dominant-baseline: central;
|
||||
font-size: 1.25em;
|
||||
font-weight: bold;
|
||||
}
|
||||
&__label {
|
||||
dominant-baseline: central;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.c-label-n {
|
||||
font-size: 2em;
|
||||
}
|
||||
.c-label-n {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
|
||||
/***************************** CAMERA FIELD ANGLE */
|
||||
.c-cam-field {
|
||||
$color: white;
|
||||
opacity: 0.3;
|
||||
$color: white;
|
||||
opacity: 0.2;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
|
||||
.cam-field-half {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
|
||||
.cam-field-half {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
.cam-field-area {
|
||||
background: $color;
|
||||
top: -30%;
|
||||
right: 0;
|
||||
bottom: -30%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
// clip-paths overlap a bit to avoid a gap between halves
|
||||
&-l {
|
||||
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
|
||||
|
||||
.cam-field-area {
|
||||
transform-origin: left center;
|
||||
}
|
||||
}
|
||||
|
||||
&-r {
|
||||
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
|
||||
|
||||
.cam-field-area {
|
||||
transform-origin: right center;
|
||||
}
|
||||
}
|
||||
.cam-field-area {
|
||||
background: $color;
|
||||
top: -30%;
|
||||
right: 0;
|
||||
bottom: -30%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
// clip-paths overlap a bit to avoid a gap between halves
|
||||
&-l {
|
||||
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
|
||||
.cam-field-area {
|
||||
transform-origin: left center;
|
||||
}
|
||||
}
|
||||
|
||||
&-r {
|
||||
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
|
||||
.cam-field-area {
|
||||
transform-origin: right center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***************************** SPACECRAFT BODY */
|
||||
.c-spacecraft-body {
|
||||
$color: $interfaceKeyColor;
|
||||
$s: 30%;
|
||||
background: $color;
|
||||
border-radius: 3px;
|
||||
height: $s;
|
||||
width: $s;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
opacity: 0.4;
|
||||
transform-origin: center top;
|
||||
transform: translateX(-50%); // center by default, overridden by CompassRose.vue / headingStyle()
|
||||
$color: $interfaceKeyColor;
|
||||
$s: 30%;
|
||||
background: $color;
|
||||
border-radius: 3px;
|
||||
height: $s; width: $s;
|
||||
left: 50%; top: 50%;
|
||||
opacity: 0.4;
|
||||
transform-origin: center top;
|
||||
|
||||
&:before {
|
||||
// Direction arrow
|
||||
$color: rgba(black, 0.5);
|
||||
$arwPointerY: 60%;
|
||||
$arwBodyOffset: 25%;
|
||||
background: $color;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
right: 20%;
|
||||
bottom: 50%;
|
||||
left: 20%;
|
||||
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
|
||||
}
|
||||
&:before {
|
||||
// Direction arrow
|
||||
$color: rgba(black, 0.5);
|
||||
$arwPointerY: 60%;
|
||||
$arwBodyOffset: 25%;
|
||||
background: $color;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 10%; right: 20%; bottom: 50%; left: 20%;
|
||||
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************** DIRECTION ROSE */
|
||||
.w-direction-rose {
|
||||
$s: 10%;
|
||||
$m: 2%;
|
||||
position: absolute;
|
||||
bottom: $m;
|
||||
left: $m;
|
||||
width: $s;
|
||||
padding-top: $s;
|
||||
|
||||
&.--rose-min {
|
||||
$s: 30px;
|
||||
width: $s;
|
||||
padding-top: $s;
|
||||
}
|
||||
|
||||
&.--rose-small {
|
||||
.c-nsew__minor-ticks,
|
||||
.c-tick-w,
|
||||
.c-tick-s,
|
||||
.c-tick-e,
|
||||
.c-label-w,
|
||||
.c-label-s,
|
||||
.c-label-e {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.c-label-n {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.--rose-max {
|
||||
$s: 100px;
|
||||
width: $s;
|
||||
padding-top: $s;
|
||||
}
|
||||
}
|
||||
|
||||
.c-direction-rose {
|
||||
$c2: rgba(white, 0.1);
|
||||
background: $elemBg;
|
||||
background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
|
||||
transform-origin: 0 0;
|
||||
$d: 100px;
|
||||
$c2: rgba(white, 0.1);
|
||||
background: $elemBg;
|
||||
background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
|
||||
width: $d;
|
||||
height: $d;
|
||||
transform-origin: 0 0;
|
||||
position: absolute;
|
||||
bottom: 10px; left: 10px;
|
||||
clip-path: circle(50% at 50% 50%);
|
||||
border-radius: 100%;
|
||||
|
||||
svg, div {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
// Sun
|
||||
.c-sun {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
clip-path: circle(50% at 50% 50%);
|
||||
border-radius: 100%;
|
||||
|
||||
svg, div {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
// Sun
|
||||
.c-sun {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
&:before {
|
||||
$s: 35%;
|
||||
@include sun();
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
opacity: 0.7;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
height: $s;
|
||||
width: $s;
|
||||
transform: translate(-50%, -60%);
|
||||
}
|
||||
&:before {
|
||||
$s: 35%;
|
||||
@include sun();
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
opacity: 0.7;
|
||||
top: 0; left: 50%;
|
||||
height:$s; width: $s;
|
||||
transform: translate(-50%, -60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,14 +135,9 @@
|
||||
:class="{ selected: focusedImageIndex === index && isPaused }"
|
||||
@click="setFocusedImage(index, thumbnailClick)"
|
||||
>
|
||||
<a href=""
|
||||
:download="image.imageDownloadName"
|
||||
@click.prevent
|
||||
<img class="c-thumb__image"
|
||||
:src="image.url"
|
||||
>
|
||||
<img class="c-thumb__image"
|
||||
:src="image.url"
|
||||
>
|
||||
</a>
|
||||
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -223,9 +218,6 @@ export default {
|
||||
canTrackDuration() {
|
||||
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
||||
},
|
||||
focusedImageDownloadName() {
|
||||
return this.getImageDownloadName(this.focusedImage);
|
||||
},
|
||||
isNextDisabled() {
|
||||
let disabled = false;
|
||||
|
||||
@ -353,7 +345,6 @@ export default {
|
||||
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
|
||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
|
||||
this.imageDownloadNameHints = { ...this.metadata.valuesForHints(['imageDownloadName'])[0]};
|
||||
|
||||
// related telemetry keys
|
||||
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
|
||||
@ -541,15 +532,6 @@ export default {
|
||||
// Replace ISO "T" with a space to allow wrapping
|
||||
return dateTimeStr.replace("T", " ");
|
||||
},
|
||||
getImageDownloadName(datum) {
|
||||
let imageDownloadName = '';
|
||||
if (datum) {
|
||||
const key = this.imageDownloadNameHints.key;
|
||||
imageDownloadName = datum[key];
|
||||
}
|
||||
|
||||
return imageDownloadName;
|
||||
},
|
||||
parseTime(datum) {
|
||||
if (!datum) {
|
||||
return;
|
||||
@ -673,7 +655,6 @@ export default {
|
||||
image.formattedTime = this.formatTime(datum);
|
||||
image.url = this.formatImageUrl(datum);
|
||||
image.time = datum[this.timeKey];
|
||||
image.imageDownloadName = this.getImageDownloadName(datum);
|
||||
|
||||
this.imageHistory.push(image);
|
||||
|
||||
@ -796,9 +777,6 @@ export default {
|
||||
this.focusedImageNaturalAspectRatio = undefined;
|
||||
|
||||
const img = this.$refs.focusedImage;
|
||||
if (!img) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO - should probably cache this
|
||||
img.addEventListener('load', () => {
|
||||
|
@ -3,6 +3,10 @@ import myItemsInterceptor from "./myItemsInterceptor";
|
||||
|
||||
export default function plugin() {
|
||||
return function install(openmct) {
|
||||
openmct.objects.addRoot({
|
||||
namespace: '',
|
||||
key: 'mine'
|
||||
});
|
||||
myItemsInterceptor(openmct);
|
||||
missingObjectInterceptor(openmct);
|
||||
};
|
||||
|
41
src/plugins/localStorage/LocalStorageObjectProvider.js
Normal file
41
src/plugins/localStorage/LocalStorageObjectProvider.js
Normal file
@ -0,0 +1,41 @@
|
||||
export default class LocalStorageObjectProvider {
|
||||
constructor({spaceKey = 'mct'}) {
|
||||
this.localStorage = window.localStorage;
|
||||
this.space = this.initializeSpace(spaceKey);
|
||||
}
|
||||
get(identifier) {
|
||||
if (this.getSpaceAsObject()[identifier.key] !== undefined) {
|
||||
const persistedModel = this.getSpaceAsObject()[identifier.key];
|
||||
const domainObject = {
|
||||
identifier,
|
||||
...persistedModel
|
||||
};
|
||||
|
||||
return Promise.resolve(domainObject);
|
||||
} else {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
getSpaceAsObject() {
|
||||
return JSON.parse(this.space);
|
||||
}
|
||||
create(model) {
|
||||
return this.setModel(model);
|
||||
}
|
||||
update(model) {
|
||||
return this.setModel(model);
|
||||
}
|
||||
setModel(model) {
|
||||
this.space[model.identifier.key] = JSON.stringify(model);
|
||||
this.persist();
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
initializeSpace(spaceKey) {
|
||||
if (this.localStorage[spaceKey] === undefined) {
|
||||
this.localStorage[spaceKey] = JSON.stringify({});
|
||||
}
|
||||
|
||||
return this.localStorage[spaceKey];
|
||||
}
|
||||
}
|
29
src/plugins/localStorage/plugin.js
Normal file
29
src/plugins/localStorage/plugin.js
Normal file
@ -0,0 +1,29 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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 LocalStorageObjectProvider from './LocalStorageObjectProvider';
|
||||
|
||||
export default function (namespace = '', storageSpace = 'mct') {
|
||||
return function (openmct) {
|
||||
openmct.objects.addProvider(namespace, new LocalStorageObjectProvider(storageSpace));
|
||||
};
|
||||
}
|
@ -430,7 +430,7 @@ export default {
|
||||
}
|
||||
|
||||
// check for no entries first
|
||||
if (entries[section.id] && entries[section.id][page.id]) {
|
||||
if (entries[section.id]) {
|
||||
const pageEntries = entries[section.id][page.id];
|
||||
|
||||
pageEntries.forEach(entry => {
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
<template>
|
||||
<div class="c-notebook__search-results">
|
||||
<div class="c-notebook__search-results__header">Search Results ({{ results.length }})</div>
|
||||
<div class="c-notebook__search-results__header">Search Results</div>
|
||||
<div class="c-notebook__entries">
|
||||
<NotebookEntry v-for="(result, index) in results"
|
||||
:key="index"
|
||||
|
@ -26,7 +26,6 @@ import CouchObjectQueue from "./CouchObjectQueue";
|
||||
const REV = "_rev";
|
||||
const ID = "_id";
|
||||
const HEARTBEAT = 50000;
|
||||
const ALL_DOCS = "_all_docs?include_docs=true";
|
||||
|
||||
export default class CouchObjectProvider {
|
||||
// options {
|
||||
@ -42,8 +41,6 @@ export default class CouchObjectProvider {
|
||||
this.objectQueue = {};
|
||||
this.observeEnabled = options.disableObserve !== true;
|
||||
this.observers = {};
|
||||
this.batchIds = [];
|
||||
|
||||
if (this.observeEnabled) {
|
||||
this.observeObjectChanges(options.filter);
|
||||
}
|
||||
@ -70,9 +67,6 @@ export default class CouchObjectProvider {
|
||||
// stringify body if needed
|
||||
if (fetchOptions.body) {
|
||||
fetchOptions.body = JSON.stringify(fetchOptions.body);
|
||||
fetchOptions.headers = {
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
}
|
||||
|
||||
return fetch(this.url + '/' + subPath, fetchOptions)
|
||||
@ -84,18 +78,14 @@ export default class CouchObjectProvider {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the response to a create/update/delete request;
|
||||
* track the rev if it's valid, otherwise return false to
|
||||
* indicate that the request failed.
|
||||
* persist any queued objects
|
||||
* @private
|
||||
*/
|
||||
// Check the response to a create/update/delete request;
|
||||
// track the rev if it's valid, otherwise return false to
|
||||
// indicate that the request failed.
|
||||
// persist any queued objects
|
||||
checkResponse(response, intermediateResponse) {
|
||||
let requestSuccess = false;
|
||||
const id = response ? response.id : undefined;
|
||||
let rev;
|
||||
|
||||
if (response && response.ok) {
|
||||
rev = response.rev;
|
||||
requestSuccess = true;
|
||||
@ -116,9 +106,6 @@ export default class CouchObjectProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getModel(response) {
|
||||
if (response && response.model) {
|
||||
let key = response[ID];
|
||||
@ -144,118 +131,10 @@ export default class CouchObjectProvider {
|
||||
}
|
||||
|
||||
get(identifier, abortSignal) {
|
||||
this.batchIds.push(identifier.key);
|
||||
|
||||
if (this.bulkPromise === undefined) {
|
||||
this.bulkPromise = this.deferBatchedGet(abortSignal);
|
||||
}
|
||||
|
||||
return this.bulkPromise
|
||||
.then((domainObjectMap) => {
|
||||
return domainObjectMap[identifier.key];
|
||||
});
|
||||
return this.request(identifier.key, "GET", undefined, abortSignal).then(this.getModel.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
deferBatchedGet(abortSignal) {
|
||||
// We until the next event loop cycle to "collect" all of the get
|
||||
// requests triggered in this iteration of the event loop
|
||||
|
||||
return this.waitOneEventCycle().then(() => {
|
||||
let batchIds = this.batchIds;
|
||||
|
||||
this.clearBatch();
|
||||
|
||||
if (batchIds.length === 1) {
|
||||
let objectKey = batchIds[0];
|
||||
|
||||
//If there's only one request, just do a regular get
|
||||
return this.request(objectKey, "GET", undefined, abortSignal)
|
||||
.then(this.returnAsMap(objectKey));
|
||||
} else {
|
||||
return this.bulkGet(batchIds, abortSignal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
returnAsMap(objectKey) {
|
||||
return (result) => {
|
||||
let objectMap = {};
|
||||
objectMap[objectKey] = this.getModel(result);
|
||||
|
||||
return objectMap;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
clearBatch() {
|
||||
this.batchIds = [];
|
||||
delete this.bulkPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
waitOneEventCycle() {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
bulkGet(ids, signal) {
|
||||
ids = this.removeDuplicates(ids);
|
||||
|
||||
const query = {
|
||||
'keys': ids
|
||||
};
|
||||
|
||||
return this.request(ALL_DOCS, 'POST', query, signal).then((response) => {
|
||||
if (response && response.rows !== undefined) {
|
||||
return response.rows.reduce((map, row) => {
|
||||
if (row.doc !== undefined) {
|
||||
map[row.key] = this.getModel(row.doc);
|
||||
}
|
||||
|
||||
return map;
|
||||
}, {});
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
removeDuplicates(array) {
|
||||
return Array.from(new Set(array));
|
||||
}
|
||||
|
||||
search(query, abortSignal) {
|
||||
const filter = {
|
||||
"selector": {
|
||||
"model": {
|
||||
"name": {
|
||||
"$regex": `(?i)${query}`
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return this.getObjectsByFilter(filter, abortSignal);
|
||||
}
|
||||
|
||||
async getObjectsByFilter(filter, abortSignal) {
|
||||
async getObjectsByFilter(filter) {
|
||||
let objects = [];
|
||||
|
||||
let url = `${this.url}/_find`;
|
||||
@ -270,7 +149,6 @@ export default class CouchObjectProvider {
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
signal: abortSignal,
|
||||
body
|
||||
});
|
||||
|
||||
@ -325,9 +203,6 @@ export default class CouchObjectProvider {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
abortGetChanges() {
|
||||
if (this.controller) {
|
||||
this.controller.abort();
|
||||
@ -337,9 +212,6 @@ export default class CouchObjectProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async observeObjectChanges(filter) {
|
||||
let intermediateResponse = this.getIntermediateResponse();
|
||||
|
||||
@ -420,9 +292,6 @@ export default class CouchObjectProvider {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getIntermediateResponse() {
|
||||
let intermediateResponse = {};
|
||||
intermediateResponse.promise = new Promise(function (resolve, reject) {
|
||||
@ -433,9 +302,6 @@ export default class CouchObjectProvider {
|
||||
return intermediateResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
enqueueObject(key, model, intermediateResponse) {
|
||||
if (this.objectQueue[key]) {
|
||||
this.objectQueue[key].enqueue({
|
||||
@ -464,9 +330,6 @@ export default class CouchObjectProvider {
|
||||
return intermediateResponse.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
updateQueued(key) {
|
||||
if (!this.objectQueue[key].pending) {
|
||||
this.objectQueue[key].pending = true;
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
createOpenMct,
|
||||
resetApplicationState, spyOnBuiltins
|
||||
} from 'utils/testing';
|
||||
import CouchObjectProvider from './CouchObjectProvider';
|
||||
|
||||
describe('the plugin', () => {
|
||||
let openmct;
|
||||
@ -41,8 +42,7 @@ describe('the plugin', () => {
|
||||
namespace: '',
|
||||
key: 'some-value'
|
||||
},
|
||||
type: 'mock-type',
|
||||
modified: 0
|
||||
type: 'mock-type'
|
||||
};
|
||||
options = {
|
||||
url: testPath,
|
||||
@ -95,7 +95,6 @@ describe('the plugin', () => {
|
||||
return {
|
||||
ok: true,
|
||||
_id: 'some-value',
|
||||
id: 'some-value',
|
||||
_rev: 1,
|
||||
model: {}
|
||||
};
|
||||
@ -105,130 +104,44 @@ describe('the plugin', () => {
|
||||
});
|
||||
|
||||
it('gets an object', () => {
|
||||
return openmct.objects.get(mockDomainObject.identifier).then((result) => {
|
||||
openmct.objects.get(mockDomainObject.identifier).then((result) => {
|
||||
expect(result.identifier.key).toEqual(mockDomainObject.identifier.key);
|
||||
});
|
||||
});
|
||||
|
||||
it('creates an object', () => {
|
||||
return openmct.objects.save(mockDomainObject).then((result) => {
|
||||
openmct.objects.save(mockDomainObject).then((result) => {
|
||||
expect(provider.create).toHaveBeenCalled();
|
||||
expect(result).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
it('updates an object', () => {
|
||||
return openmct.objects.save(mockDomainObject).then((result) => {
|
||||
openmct.objects.save(mockDomainObject).then((result) => {
|
||||
expect(result).toBeTrue();
|
||||
expect(provider.create).toHaveBeenCalled();
|
||||
|
||||
//Set modified timestamp it detects a change and persists the updated model.
|
||||
mockDomainObject.modified = Date.now();
|
||||
|
||||
return openmct.objects.save(mockDomainObject).then((updatedResult) => {
|
||||
openmct.objects.save(mockDomainObject).then((updatedResult) => {
|
||||
expect(updatedResult).toBeTrue();
|
||||
expect(provider.update).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('batches requests', () => {
|
||||
let mockPromise;
|
||||
beforeEach(() => {
|
||||
mockPromise = Promise.resolve({
|
||||
json: () => {
|
||||
return {
|
||||
total_rows: 0,
|
||||
rows: []
|
||||
};
|
||||
}
|
||||
});
|
||||
fetch.and.returnValue(mockPromise);
|
||||
|
||||
it('updates queued objects', () => {
|
||||
let couchProvider = new CouchObjectProvider(openmct, options, '');
|
||||
let intermediateResponse = couchProvider.getIntermediateResponse();
|
||||
spyOn(couchProvider, 'updateQueued');
|
||||
couchProvider.enqueueObject(mockDomainObject.identifier.key, mockDomainObject, intermediateResponse);
|
||||
couchProvider.objectQueue[mockDomainObject.identifier.key].updateRevision(1);
|
||||
couchProvider.update(mockDomainObject);
|
||||
expect(couchProvider.objectQueue[mockDomainObject.identifier.key].hasNext()).toBe(2);
|
||||
couchProvider.checkResponse({
|
||||
ok: true,
|
||||
rev: 2,
|
||||
id: mockDomainObject.identifier.key
|
||||
}, intermediateResponse);
|
||||
|
||||
expect(couchProvider.updateQueued).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
it('for multiple simultaneous gets', () => {
|
||||
const objectIds = [
|
||||
{
|
||||
namespace: '',
|
||||
key: 'object-1'
|
||||
}, {
|
||||
namespace: '',
|
||||
key: 'object-2'
|
||||
}, {
|
||||
namespace: '',
|
||||
key: 'object-3'
|
||||
}
|
||||
];
|
||||
|
||||
const getAllObjects = Promise.all(
|
||||
objectIds.map((identifier) =>
|
||||
openmct.objects.get(identifier)
|
||||
));
|
||||
|
||||
return getAllObjects.then(() => {
|
||||
const requestUrl = fetch.calls.mostRecent().args[0];
|
||||
const requestMethod = fetch.calls.mostRecent().args[1].method;
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
expect(requestUrl.includes('_all_docs')).toBeTrue();
|
||||
expect(requestMethod).toEqual('POST');
|
||||
});
|
||||
});
|
||||
|
||||
it('but not for single gets', () => {
|
||||
const objectId = {
|
||||
namespace: '',
|
||||
key: 'object-1'
|
||||
};
|
||||
|
||||
const getObject = openmct.objects.get(objectId);
|
||||
|
||||
return getObject.then(() => {
|
||||
const requestUrl = fetch.calls.mostRecent().args[0];
|
||||
const requestMethod = fetch.calls.mostRecent().args[1].method;
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
expect(requestUrl.endsWith(`${objectId.key}`)).toBeTrue();
|
||||
expect(requestMethod).toEqual('GET');
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('implements server-side search', () => {
|
||||
let mockPromise;
|
||||
beforeEach(() => {
|
||||
mockPromise = Promise.resolve({
|
||||
body: {
|
||||
getReader() {
|
||||
return {
|
||||
read() {
|
||||
return Promise.resolve({
|
||||
done: true,
|
||||
value: undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
fetch.and.returnValue(mockPromise);
|
||||
});
|
||||
|
||||
it("using Couch's 'find' endpoint", () => {
|
||||
return Promise.all(openmct.objects.search('test')).then(() => {
|
||||
const requestUrl = fetch.calls.mostRecent().args[0];
|
||||
|
||||
expect(fetch).toHaveBeenCalled();
|
||||
expect(requestUrl.endsWith('_find')).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
it("and supports search by object name", () => {
|
||||
return Promise.all(openmct.objects.search('test')).then(() => {
|
||||
const requestPayload = JSON.parse(fetch.calls.mostRecent().args[1].body);
|
||||
|
||||
expect(requestPayload).toBeDefined();
|
||||
expect(requestPayload.selector.model.name.$regex).toEqual('(?i)test');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -85,33 +85,33 @@ define([
|
||||
}
|
||||
],
|
||||
"views": [
|
||||
{
|
||||
"name": "Plot",
|
||||
"key": "plot-single",
|
||||
"cssClass": "icon-telemetry",
|
||||
"template": PlotTemplate,
|
||||
"needs": [
|
||||
"telemetry"
|
||||
],
|
||||
"delegation": false,
|
||||
"priority": "mandatory"
|
||||
},
|
||||
{
|
||||
"name": "Overlay Plot",
|
||||
"key": "overlayPlot",
|
||||
"cssClass": "icon-plot-overlay",
|
||||
"type": "telemetry.plot.overlay",
|
||||
"template": PlotTemplate,
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"name": "Stacked Plot",
|
||||
"key": "stackedPlot",
|
||||
"cssClass": "icon-plot-stacked",
|
||||
"type": "telemetry.plot.stacked",
|
||||
"template": StackedPlotTemplate,
|
||||
"editable": true
|
||||
}
|
||||
// {
|
||||
// "name": "Plot",
|
||||
// "key": "plot-single",
|
||||
// "cssClass": "icon-telemetry",
|
||||
// "template": PlotTemplate,
|
||||
// "needs": [
|
||||
// "telemetry"
|
||||
// ],
|
||||
// "delegation": false,
|
||||
// "priority": "mandatory"
|
||||
// },
|
||||
// {
|
||||
// "name": "Overlay Plot",
|
||||
// "key": "overlayPlot",
|
||||
// "cssClass": "icon-plot-overlay",
|
||||
// "type": "telemetry.plot.overlay",
|
||||
// "template": PlotTemplate,
|
||||
// "editable": true
|
||||
// },
|
||||
// {
|
||||
// "name": "Stacked Plot",
|
||||
// "key": "stackedPlot",
|
||||
// "cssClass": "icon-plot-stacked",
|
||||
// "type": "telemetry.plot.stacked",
|
||||
// "template": StackedPlotTemplate,
|
||||
// "editable": true
|
||||
// }
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
|
@ -413,10 +413,6 @@ define([
|
||||
* @public
|
||||
*/
|
||||
updateFiltersAndRefresh: function (updatedFilters) {
|
||||
if (updatedFilters === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
|
||||
|
||||
if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
|
||||
|
@ -1,64 +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>
|
||||
<div v-if="canEdit">
|
||||
<plot-options-edit />
|
||||
</div>
|
||||
<div v-else>
|
||||
<plot-options-browse />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PlotOptionsBrowse from "@/plugins/plot/vue/inspector/PlotOptionsBrowse.vue";
|
||||
import PlotOptionsEdit from "@/plugins/plot/vue/inspector/PlotOptionsEdit.vue";
|
||||
export default {
|
||||
components: {
|
||||
PlotOptionsBrowse,
|
||||
PlotOptionsEdit
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
isEditing: this.openmct.editor.isEditing()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canEdit() {
|
||||
return this.isEditing && !this.domainObject.locked;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
},
|
||||
methods: {
|
||||
setEditState(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,198 +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="config && loaded"
|
||||
class="js-plot-options-browse"
|
||||
>
|
||||
<ul class="c-tree">
|
||||
<h2 title="Plot series display properties in this object">Plot Series</h2>
|
||||
<plot-options-item v-for="series in plotSeries"
|
||||
:key="series.key"
|
||||
:series="series"
|
||||
/>
|
||||
</ul>
|
||||
<div class="grid-properties">
|
||||
<ul class="l-inspector-part">
|
||||
<h2 title="Y axis settings for this object">Y Axis</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Manually override how the Y axis is labeled."
|
||||
>Label</div>
|
||||
<div class="grid-cell value">{{ label ? label : "Not defined" }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Automatically scale the Y axis to keep all values in view."
|
||||
>Autoscale</div>
|
||||
<div class="grid-cell value">
|
||||
{{ autoscale ? "Enabled: " : "Disabled" }}
|
||||
{{ autoscale ? autoscalePadding : "" }}
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="!autoscale && rangeMin"
|
||||
class="grid-row"
|
||||
>
|
||||
<div class="grid-cell label"
|
||||
title="Minimum Y axis value."
|
||||
>Minimum value</div>
|
||||
<div class="grid-cell value">{{ rangeMin }}</div>
|
||||
</li>
|
||||
<li v-if="!autoscale && rangeMax"
|
||||
class="grid-row"
|
||||
>
|
||||
<div class="grid-cell label"
|
||||
title="Maximum Y axis value."
|
||||
>Maximum value</div>
|
||||
<div class="grid-cell value">{{ rangeMax }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="l-inspector-part">
|
||||
<h2 title="Legend settings for this object">Legend</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The position of the legend relative to the plot display area."
|
||||
>Position</div>
|
||||
<div class="grid-cell value capitalize">{{ position }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Hide the legend when the plot is small"
|
||||
>Hide when plot small</div>
|
||||
<div class="grid-cell value">{{ hideLegendWhenSmall ? "Yes" : "No" }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Show the legend expanded by default"
|
||||
>Expand by Default</div>
|
||||
<div class="grid-cell value">{{ expandByDefault ? "Yes" : "No" }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's collapsed."
|
||||
>Show when collapsed:</div>
|
||||
<div class="grid-cell value">{{
|
||||
valueToShowWhenCollapsed.replace('nearest', '')
|
||||
}}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's expanded."
|
||||
>Show when expanded:</div>
|
||||
<div class="grid-cell value comma-list">
|
||||
<span v-if="showTimestampWhenExpanded">Timestamp</span>
|
||||
<span v-if="showValueWhenExpanded">Value</span>
|
||||
<span v-if="showMinimumWhenExpanded">Min</span>
|
||||
<span v-if="showMaximumWhenExpanded">Max</span>
|
||||
<span v-if="showUnitsWhenExpanded">Units</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PlotOptionsItem from "./PlotOptionsItem.vue";
|
||||
import configStore from "../single/configuration/configStore";
|
||||
import eventHelpers from "../single/lib/eventHelpers";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PlotOptionsItem
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
config: undefined,
|
||||
label: '',
|
||||
autoscale: '',
|
||||
autoscalePadding: '',
|
||||
rangeMin: '',
|
||||
rangeMax: '',
|
||||
position: '',
|
||||
hideLegendWhenSmall: '',
|
||||
expandByDefault: '',
|
||||
valueToShowWhenCollapsed: '',
|
||||
showTimestampWhenExpanded: '',
|
||||
showValueWhenExpanded: '',
|
||||
showMinimumWhenExpanded: '',
|
||||
showMaximumWhenExpanded: '',
|
||||
showUnitsWhenExpanded: '',
|
||||
loaded: false,
|
||||
plotSeries: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.config = this.getConfig();
|
||||
this.initConfiguration();
|
||||
this.registerListeners();
|
||||
this.loaded = true;
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopListening();
|
||||
},
|
||||
methods: {
|
||||
initConfiguration() {
|
||||
this.label = this.config.yAxis.get('label');
|
||||
this.autoscale = this.config.yAxis.get('autoscale');
|
||||
this.autoscalePadding = this.config.yAxis.get('autoscalePadding');
|
||||
const range = this.config.yAxis.get('range');
|
||||
if (range) {
|
||||
this.rangeMin = range.min;
|
||||
this.rangeMax = range.max;
|
||||
}
|
||||
|
||||
this.position = this.config.legend.get('position');
|
||||
this.hideLegendWhenSmall = this.config.legend.get('hideLegendWhenSmall');
|
||||
this.expandByDefault = this.config.legend.get('expandByDefault');
|
||||
this.valueToShowWhenCollapsed = this.config.legend.get('valueToShowWhenCollapsed');
|
||||
this.showTimestampWhenExpanded = this.config.legend.get('showTimestampWhenExpanded');
|
||||
this.showValueWhenExpanded = this.config.legend.get('showValueWhenExpanded');
|
||||
this.showMinimumWhenExpanded = this.config.legend.get('showMinimumWhenExpanded');
|
||||
this.showMaximumWhenExpanded = this.config.legend.get('showMaximumWhenExpanded');
|
||||
this.showUnitsWhenExpanded = this.config.legend.get('showUnitsWhenExpanded');
|
||||
},
|
||||
getConfig() {
|
||||
this.configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
return configStore.get(this.configId);
|
||||
},
|
||||
registerListeners() {
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.config.series, 'remove', this.resetAllSeries, this);
|
||||
},
|
||||
|
||||
addSeries(series, index) {
|
||||
this.plotSeries[index] = series;
|
||||
},
|
||||
|
||||
resetAllSeries() {
|
||||
this.plotSeries = [];
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,100 +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="config && loaded"
|
||||
class="js-plot-options-edit"
|
||||
>
|
||||
<ul class="c-tree">
|
||||
<h2 title="Display properties for this object">Plot Series</h2>
|
||||
<li v-for="series in plotSeries"
|
||||
:key="series.key"
|
||||
>
|
||||
<series-form :series="series" />
|
||||
</li>
|
||||
</ul>
|
||||
<y-axis-form v-show="!!plotSeries.length"
|
||||
class="grid-properties"
|
||||
:y-axis="config.yAxis"
|
||||
/>
|
||||
<ul class="l-inspector-part">
|
||||
<h2 title="Legend options">Legend</h2>
|
||||
<legend-form v-show="!!plotSeries.length"
|
||||
class="grid-properties"
|
||||
:legend="config.legend"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import SeriesForm from "@/plugins/plot/vue/inspector/forms/SeriesForm.vue";
|
||||
import YAxisForm from "@/plugins/plot/vue/inspector/forms/YAxisForm.vue";
|
||||
import LegendForm from "@/plugins/plot/vue/inspector/forms/LegendForm.vue";
|
||||
import eventHelpers from "@/plugins/plot/vue/single/lib/eventHelpers";
|
||||
import configStore from "@/plugins/plot/vue/single/configuration/configStore";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LegendForm,
|
||||
SeriesForm,
|
||||
YAxisForm
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
config: {},
|
||||
plotSeries: [],
|
||||
loaded: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.config = this.getConfig();
|
||||
this.registerListeners();
|
||||
this.loaded = true;
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopListening();
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
this.configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
return configStore.get(this.configId);
|
||||
},
|
||||
registerListeners() {
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.config.series, 'remove', this.resetAllSeries, this);
|
||||
},
|
||||
|
||||
addSeries(series, index) {
|
||||
this.plotSeries[index] = series;
|
||||
},
|
||||
|
||||
resetAllSeries() {
|
||||
this.plotSeries = [];
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,155 +0,0 @@
|
||||
<template>
|
||||
<ul>
|
||||
<li class="c-tree__item menus-to-left">
|
||||
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||
:class="expandedCssClass"
|
||||
@click="toggleExpanded"
|
||||
>
|
||||
</span>
|
||||
<div class="c-object-label"
|
||||
:class="statusClass"
|
||||
>
|
||||
<div class="c-object-label__type-icon"
|
||||
:class="getSeriesClass"
|
||||
>
|
||||
<span class="is-status__indicator"
|
||||
title="This item is missing or suspect"
|
||||
></span>
|
||||
</div>
|
||||
<div class="c-object-label__name">{{ series.domainObject.name }}</div>
|
||||
</div>
|
||||
</li>
|
||||
<li v-show="expanded"
|
||||
class="c-tree__item menus-to-left"
|
||||
>
|
||||
<ul class="grid-properties js-plot-options-browse-properties">
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The field to be plotted as a value for this series."
|
||||
>Value</div>
|
||||
<div class="grid-cell value">
|
||||
{{ yKey }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The rendering method to join lines for this series."
|
||||
>Line Method</div>
|
||||
<div class="grid-cell value">{{ {
|
||||
'none': 'None',
|
||||
'linear': 'Linear interpolation',
|
||||
'stepAfter': 'Step After'
|
||||
}[interpolate] }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Whether markers are displayed, and their size."
|
||||
>Markers</div>
|
||||
<div class="grid-cell value">
|
||||
{{ markerOptionsDisplayText }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Display markers visually denoting points in alarm."
|
||||
>Alarm Markers</div>
|
||||
<div class="grid-cell value">
|
||||
{{ alarmMarkers ? "Enabled" : "Disabled" }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The plot line and marker color for this series."
|
||||
>Color</div>
|
||||
<div class="grid-cell value">
|
||||
<span class="c-color-swatch"
|
||||
:style="{
|
||||
'background': seriesHexColor
|
||||
}"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
series: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
getSeriesClass() {
|
||||
let cssClass = '';
|
||||
let legacyObject = this.openmct.legacyObject(this.series.domainObject);
|
||||
let location = legacyObject.getCapability('location');
|
||||
if (location && location.isLink()) {
|
||||
cssClass = 'l-icon-link';
|
||||
}
|
||||
|
||||
let type = legacyObject.getCapability('type');
|
||||
if (type) {
|
||||
cssClass = `${cssClass} ${type.getCssClass()}`;
|
||||
}
|
||||
|
||||
return cssClass;
|
||||
},
|
||||
expandedCssClass() {
|
||||
if (this.expanded === true) {
|
||||
return 'c-disclosure-triangle--expanded';
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
statusClass() {
|
||||
return (this.status) ? `is-status--${this.status}` : '';
|
||||
},
|
||||
yKey() {
|
||||
return this.series.get('yKey');
|
||||
},
|
||||
interpolate() {
|
||||
return this.series.get('interpolate');
|
||||
},
|
||||
markerOptionsDisplayText() {
|
||||
return this.series.markerOptionsDisplayText();
|
||||
},
|
||||
alarmMarkers() {
|
||||
return this.series.get('alarmMarkers');
|
||||
},
|
||||
seriesHexColor() {
|
||||
return this.series.get('color').asHexString();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.status = this.openmct.status.get(this.series.domainObject.identifier);
|
||||
this.removeStatusListener = this.openmct.status.observe(this.series.domainObject.identifier, this.setStatus);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.removeStatusListener) {
|
||||
this.removeStatusListener();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleExpanded() {
|
||||
this.expanded = !this.expanded;
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,51 +0,0 @@
|
||||
|
||||
import PlotOptions from "./PlotOptions.vue";
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function PlotsInspectorViewProvider(openmct) {
|
||||
return {
|
||||
key: 'plots-inspector',
|
||||
name: 'Plots Inspector View',
|
||||
canView: function (selection) {
|
||||
if (selection.length === 0 || selection[0].length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let parent = selection[0].length > 1 && selection[0][1].context.item;
|
||||
let object = selection[0][0].context.item;
|
||||
|
||||
return parent
|
||||
&& parent.type === 'time-strip'
|
||||
&& object
|
||||
&& object.type === 'telemetry.plot.overlay';
|
||||
},
|
||||
view: function (selection) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
PlotOptions: PlotOptions
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject: openmct.selection.get()[0][0].context.item
|
||||
},
|
||||
template: '<plot-options></plot-options>'
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
@ -1,207 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The position of the legend relative to the plot display area."
|
||||
>Position</div>
|
||||
<div class="grid-cell value">
|
||||
<select v-model="position"
|
||||
@change="updateForm('position')"
|
||||
>
|
||||
<option value="top">Top</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="bottom">Bottom</option>
|
||||
<option value="left">Left</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Hide the legend when the plot is small"
|
||||
>Hide when plot small</div>
|
||||
<div class="grid-cell value"><input v-model="hideLegendWhenSmall"
|
||||
type="checkbox"
|
||||
@change="updateForm('hideLegendWhenSmall')"
|
||||
></div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Show the legend expanded by default"
|
||||
>Expand by default</div>
|
||||
<div class="grid-cell value"><input v-model="expandByDefault"
|
||||
type="checkbox"
|
||||
@change="updateForm('expandByDefault')"
|
||||
></div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's collapsed."
|
||||
>When collapsed show</div>
|
||||
<div class="grid-cell value">
|
||||
<select v-model="valueToShowWhenCollapsed"
|
||||
@change="updateForm('valueToShowWhenCollapsed')"
|
||||
>
|
||||
<option value="none">Nothing</option>
|
||||
<option value="nearestTimestamp">Nearest timestamp</option>
|
||||
<option value="nearestValue">Nearest value</option>
|
||||
<option value="min">Minimum value</option>
|
||||
<option value="max">Maximum value</option>
|
||||
<option value="units">Units</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's expanded."
|
||||
>When expanded show</div>
|
||||
<div class="grid-cell value">
|
||||
<ul>
|
||||
<li><input v-model="showTimestampWhenExpanded"
|
||||
type="checkbox"
|
||||
@change="updateForm('showTimestampWhenExpanded')"
|
||||
> Nearest timestamp</li>
|
||||
<li><input v-model="showValueWhenExpanded"
|
||||
type="checkbox"
|
||||
@change="updateForm('showValueWhenExpanded')"
|
||||
> Nearest value</li>
|
||||
<li><input v-model="showMinimumWhenExpanded"
|
||||
type="checkbox"
|
||||
@change="updateForm('showMinimumWhenExpanded')"
|
||||
> Minimum value</li>
|
||||
<li><input v-model="showMaximumWhenExpanded"
|
||||
type="checkbox"
|
||||
@change="updateForm('showMaximumWhenExpanded')"
|
||||
> Maximum value</li>
|
||||
<li><input v-model="showUnitsWhenExpanded"
|
||||
type="checkbox"
|
||||
@change="updateForm('showUnitsWhenExpanded')"
|
||||
> Units</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {coerce, objectPath, validate} from "@/plugins/plot/vue/inspector/forms/formUtil";
|
||||
import _ from "lodash";
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
legend: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
position: '',
|
||||
hideLegendWhenSmall: '',
|
||||
expandByDefault: '',
|
||||
valueToShowWhenCollapsed: '',
|
||||
showTimestampWhenExpanded: '',
|
||||
showValueWhenExpanded: '',
|
||||
showMinimumWhenExpanded: '',
|
||||
showMaximumWhenExpanded: '',
|
||||
showUnitsWhenExpanded: '',
|
||||
validation: {}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.initialize();
|
||||
this.initFormValues();
|
||||
},
|
||||
methods: {
|
||||
initialize() {
|
||||
this.fields = [
|
||||
{
|
||||
modelProp: 'position',
|
||||
objectPath: 'configuration.legend.position'
|
||||
},
|
||||
{
|
||||
modelProp: 'hideLegendWhenSmall',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.hideLegendWhenSmall'
|
||||
},
|
||||
{
|
||||
modelProp: 'expandByDefault',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.expandByDefault'
|
||||
},
|
||||
{
|
||||
modelProp: 'valueToShowWhenCollapsed',
|
||||
objectPath: 'configuration.legend.valueToShowWhenCollapsed'
|
||||
},
|
||||
{
|
||||
modelProp: 'showValueWhenExpanded',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.showValueWhenExpanded'
|
||||
},
|
||||
{
|
||||
modelProp: 'showTimestampWhenExpanded',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.showTimestampWhenExpanded'
|
||||
},
|
||||
{
|
||||
modelProp: 'showMaximumWhenExpanded',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.showMaximumWhenExpanded'
|
||||
},
|
||||
{
|
||||
modelProp: 'showMinimumWhenExpanded',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.showMinimumWhenExpanded'
|
||||
},
|
||||
{
|
||||
modelProp: 'showUnitsWhenExpanded',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.showUnitsWhenExpanded'
|
||||
}
|
||||
];
|
||||
},
|
||||
initFormValues() {
|
||||
this.position = this.legend.get('position');
|
||||
this.hideLegendWhenSmall = this.legend.get('hideLegendWhenSmall');
|
||||
this.expandByDefault = this.legend.get('expandByDefault');
|
||||
this.valueToShowWhenCollapsed = this.legend.get('valueToShowWhenCollapsed');
|
||||
this.showTimestampWhenExpanded = this.legend.get('showTimestampWhenExpanded');
|
||||
this.showValueWhenExpanded = this.legend.get('showValueWhenExpanded');
|
||||
this.showMinimumWhenExpanded = this.legend.get('showMinimumWhenExpanded');
|
||||
this.showMaximumWhenExpanded = this.legend.get('showMaximumWhenExpanded');
|
||||
this.showUnitsWhenExpanded = this.legend.get('showUnitsWhenExpanded');
|
||||
},
|
||||
updateForm(formKey) {
|
||||
const newVal = this[formKey];
|
||||
const oldVal = this.legend.get(formKey);
|
||||
const formField = this.fields.find((field) => field.modelProp === formKey);
|
||||
|
||||
const path = objectPath(formField.objectPath);
|
||||
const validationResult = validate(newVal, this.legend, formField.validate);
|
||||
if (validationResult === true) {
|
||||
delete this.validation[formKey];
|
||||
} else {
|
||||
this.validation[formKey] = validationResult;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
|
||||
this.legend.set(formKey, coerce(newVal, formField.coerce));
|
||||
if (path) {
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
path(this.domainObject, this.legend),
|
||||
coerce(newVal, formField.coerce)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,347 +0,0 @@
|
||||
<template>
|
||||
<ul>
|
||||
<li class="c-tree__item menus-to-left">
|
||||
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||
:class="expandedCssClass"
|
||||
@click="toggleExpanded"
|
||||
>
|
||||
</span>
|
||||
<div :class="objectLabelCss">
|
||||
<div class="c-object-label__type-icon"
|
||||
:class="[seriesCss, linkCss]"
|
||||
>
|
||||
<span class="is-status__indicator"
|
||||
title="This item is missing or suspect"
|
||||
></span>
|
||||
</div>
|
||||
<div class="c-object-label__name">{{ series.domainObject.name }}</div>
|
||||
</div>
|
||||
</li>
|
||||
<ul v-show="expanded"
|
||||
class="grid-properties js-plot-options-edit-properties"
|
||||
>
|
||||
<li class="grid-row">
|
||||
<!-- Value to be displayed -->
|
||||
<div class="grid-cell label"
|
||||
title="The field to be plotted as a value for this series."
|
||||
>Value</div>
|
||||
<div class="grid-cell value">
|
||||
<select v-model="yKey"
|
||||
@change="updateForm('yKey')"
|
||||
>
|
||||
<option v-for="option in yKeyOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
:selected="option.value == yKey"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The rendering method to join lines for this series."
|
||||
>Line Method</div>
|
||||
<div class="grid-cell value">
|
||||
<select v-model="interpolate"
|
||||
@change="updateForm('interpolate')"
|
||||
>
|
||||
<option value="none">None</option>
|
||||
<option value="linear">Linear interpolate</option>
|
||||
<option value="stepAfter">Step after</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Whether markers are displayed."
|
||||
>Markers</div>
|
||||
<div class="grid-cell value">
|
||||
<input v-model="markers"
|
||||
type="checkbox"
|
||||
@change="updateForm('markers')"
|
||||
>
|
||||
<select
|
||||
v-show="markers"
|
||||
v-model="markerShape"
|
||||
@change="updateForm('markerShape')"
|
||||
>
|
||||
<option
|
||||
v-for="option in markerShapeOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
:selected="option.value == markerShape"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Display markers visually denoting points in alarm."
|
||||
>Alarm Markers</div>
|
||||
<div class="grid-cell value">
|
||||
<input v-model="alarmMarkers"
|
||||
type="checkbox"
|
||||
@change="updateForm('alarmMarkers')"
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
<li v-show="markers || alarmMarkers"
|
||||
class="grid-row"
|
||||
>
|
||||
<div class="grid-cell label"
|
||||
title="The size of regular and alarm markers for this series."
|
||||
>Marker Size:</div>
|
||||
<div class="grid-cell value"><input v-model="markerSize"
|
||||
class="c-input--flex"
|
||||
type="text"
|
||||
@change="updateForm('markerSize')"
|
||||
></div>
|
||||
</li>
|
||||
<li v-show="interpolate !== 'none' || markers"
|
||||
class="grid-row"
|
||||
>
|
||||
<div class="grid-cell label"
|
||||
title="Manually set the plot line and marker color for this series."
|
||||
>Color</div>
|
||||
<div class="grid-cell value">
|
||||
<div class="c-click-swatch c-click-swatch--menu"
|
||||
@click="toggleSwatch()"
|
||||
>
|
||||
<span class="c-color-swatch"
|
||||
:style="{ background: seriesColorAsHex }"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<div class="c-palette c-palette--color">
|
||||
<div v-show="swatchActive"
|
||||
class="c-palette__items"
|
||||
>
|
||||
<div v-for="(group, index) in colorPalette"
|
||||
:key="index"
|
||||
class="u-contents"
|
||||
>
|
||||
<div v-for="(color, colorIndex) in group"
|
||||
:key="colorIndex"
|
||||
class="c-palette__item"
|
||||
:class="{ 'selected': series.get('color').equalTo(color) }"
|
||||
:style="{ background: color.asHexString() }"
|
||||
@click="setColor(color)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { MARKER_SHAPES } from "../../single/draw/MarkerShapes";
|
||||
import { objectPath, validate, coerce } from "./formUtil";
|
||||
import _ from 'lodash';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
series: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
markerShapeOptions: [],
|
||||
yKey: this.series.get('yKey'),
|
||||
yKeyOptions: [],
|
||||
interpolate: this.series.get('interpolate'),
|
||||
markers: this.series.get('markers'),
|
||||
markerShape: this.series.get('markerShape'),
|
||||
alarmMarkers: this.series.get('alarmMarkers'),
|
||||
markerSize: this.series.get('markerSize'),
|
||||
validation: {},
|
||||
swatchActive: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
colorPalette() {
|
||||
return this.series.collection.palette.groups();
|
||||
},
|
||||
objectLabelCss() {
|
||||
return this.status ? `c-object-label is-status--${this.status}'` : 'c-object-label';
|
||||
},
|
||||
seriesCss() {
|
||||
let legacyObject = this.openmct.legacyObject(this.series.domainObject);
|
||||
let type = legacyObject.getCapability('type');
|
||||
|
||||
return type ? `c-object-label__type-icon ${type.getCssClass()}` : `c-object-label__type-icon`;
|
||||
},
|
||||
linkCss() {
|
||||
let cssClass = '';
|
||||
let legacyObject = this.openmct.legacyObject(this.series.domainObject);
|
||||
let location = legacyObject.getCapability('location');
|
||||
if (location && location.isLink()) {
|
||||
cssClass = 'l-icon-link';
|
||||
}
|
||||
|
||||
return cssClass;
|
||||
},
|
||||
expandedCssClass() {
|
||||
return this.expanded ? 'c-disclosure-triangle--expanded' : '';
|
||||
},
|
||||
seriesColorAsHex() {
|
||||
return this.series.get('color').asHexString();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initialize();
|
||||
|
||||
this.status = this.openmct.status.get(this.series.domainObject.identifier);
|
||||
this.removeStatusListener = this.openmct.status.observe(this.series.domainObject.identifier, this.setStatus);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.removeStatusListener) {
|
||||
this.removeStatusListener();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initialize: function () {
|
||||
this.fields = [
|
||||
{
|
||||
modelProp: 'yKey',
|
||||
objectPath: this.dynamicPathForKey('yKey')
|
||||
},
|
||||
{
|
||||
modelProp: 'interpolate',
|
||||
objectPath: this.dynamicPathForKey('interpolate')
|
||||
},
|
||||
{
|
||||
modelProp: 'markers',
|
||||
objectPath: this.dynamicPathForKey('markers')
|
||||
},
|
||||
{
|
||||
modelProp: 'markerShape',
|
||||
objectPath: this.dynamicPathForKey('markerShape')
|
||||
},
|
||||
{
|
||||
modelProp: 'markerSize',
|
||||
coerce: Number,
|
||||
objectPath: this.dynamicPathForKey('markerSize')
|
||||
},
|
||||
{
|
||||
modelProp: 'alarmMarkers',
|
||||
coerce: Boolean,
|
||||
objectPath: this.dynamicPathForKey('alarmMarkers')
|
||||
}
|
||||
];
|
||||
|
||||
const metadata = this.series.metadata;
|
||||
this.yKeyOptions = metadata
|
||||
.valuesForHints(['range'])
|
||||
.map(function (o) {
|
||||
return {
|
||||
name: o.key,
|
||||
value: o.key
|
||||
};
|
||||
});
|
||||
this.markerShapeOptions = Object.entries(MARKER_SHAPES)
|
||||
.map(([key, obj]) => {
|
||||
return {
|
||||
name: obj.label,
|
||||
value: key
|
||||
};
|
||||
});
|
||||
},
|
||||
dynamicPathForKey(key) {
|
||||
return function (object, model) {
|
||||
const modelIdentifier = model.get('identifier');
|
||||
const index = object.configuration.series.findIndex(s => {
|
||||
return _.isEqual(s.identifier, modelIdentifier);
|
||||
});
|
||||
|
||||
return 'configuration.series[' + index + '].' + key;
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Set the color for the current plot series. If the new color was
|
||||
* already assigned to a different plot series, then swap the colors.
|
||||
*/
|
||||
setColor: function (color) {
|
||||
const oldColor = this.series.get('color');
|
||||
const otherSeriesWithColor = this.series.collection.filter(function (s) {
|
||||
return s.get('color') === color;
|
||||
})[0];
|
||||
|
||||
this.series.set('color', color);
|
||||
|
||||
const getPath = this.dynamicPathForKey('color');
|
||||
const seriesColorPath = getPath(this.domainObject, this.series);
|
||||
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
seriesColorPath,
|
||||
color.asHexString()
|
||||
);
|
||||
|
||||
if (otherSeriesWithColor) {
|
||||
otherSeriesWithColor.set('color', oldColor);
|
||||
|
||||
const otherSeriesColorPath = getPath(
|
||||
this.domainObject,
|
||||
otherSeriesWithColor
|
||||
);
|
||||
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
otherSeriesColorPath,
|
||||
oldColor.asHexString()
|
||||
);
|
||||
}
|
||||
},
|
||||
toggleExpanded() {
|
||||
this.expanded = !this.expanded;
|
||||
},
|
||||
updateForm(formKey) {
|
||||
const newVal = this[formKey];
|
||||
const oldVal = this.series.get(formKey);
|
||||
const formField = this.fields.find((field) => field.modelProp === formKey);
|
||||
|
||||
const path = objectPath(formField.objectPath);
|
||||
const validationResult = validate(newVal, this.series, formField.validate);
|
||||
if (validationResult === true) {
|
||||
delete this.validation[formKey];
|
||||
} else {
|
||||
this.validation[formKey] = validationResult;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
|
||||
this.series.set(formKey, coerce(newVal, formField.coerce));
|
||||
if (path) {
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
path(this.domainObject, this.series),
|
||||
coerce(newVal, formField.coerce)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
},
|
||||
toggleSwatch() {
|
||||
this.swatchActive = !this.swatchActive;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
@ -1,226 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="l-inspector-part">
|
||||
<h2>Y Axis</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Manually override how the Y axis is labeled."
|
||||
>Label</div>
|
||||
<div class="grid-cell value"><input v-model="label"
|
||||
class="c-input--flex"
|
||||
type="text"
|
||||
@change="updateForm('label')"
|
||||
></div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="l-inspector-part">
|
||||
<h2>Y Axis Scaling</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Automatically scale the Y axis to keep all values in view."
|
||||
>Auto scale</div>
|
||||
<div class="grid-cell value"><input v-model="autoscale"
|
||||
type="checkbox"
|
||||
@change="updateForm('autoscale')"
|
||||
></div>
|
||||
</li>
|
||||
<li v-show="autoscale"
|
||||
class="grid-row"
|
||||
>
|
||||
<div class="grid-cell label"
|
||||
title="Percentage of padding above and below plotted min and max values. 0.1, 1.0, etc."
|
||||
>
|
||||
Padding</div>
|
||||
<div class="grid-cell value">
|
||||
<input v-model="autoscalePadding"
|
||||
class="c-input--flex"
|
||||
type="text"
|
||||
@change="updateForm('autoscalePadding')"
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-show="!autoscale"
|
||||
class="l-inspector-part"
|
||||
>
|
||||
<div v-show="!autoscale && validation.range"
|
||||
class="grid-span-all form-error"
|
||||
>
|
||||
{{ validation.range }}
|
||||
</div>
|
||||
<li class="grid-row force-border">
|
||||
<div class="grid-cell label"
|
||||
title="Minimum Y axis value."
|
||||
>Minimum Value</div>
|
||||
<div class="grid-cell value">
|
||||
<input v-model="rangeMin"
|
||||
class="c-input--flex"
|
||||
type="number"
|
||||
@change="updateForm('range')"
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Maximum Y axis value."
|
||||
>Maximum Value</div>
|
||||
<div class="grid-cell value"><input v-model="rangeMax"
|
||||
class="c-input--flex"
|
||||
type="number"
|
||||
@change="updateForm('range')"
|
||||
></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { objectPath, validate, coerce } from "./formUtil";
|
||||
import _ from "lodash";
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
yAxis: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
label: '',
|
||||
autoscale: '',
|
||||
autoscalePadding: '',
|
||||
rangeMin: '',
|
||||
rangeMax: '',
|
||||
validation: {}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.initialize();
|
||||
this.initFormValues();
|
||||
},
|
||||
methods: {
|
||||
initialize: function () {
|
||||
this.fields = [
|
||||
{
|
||||
modelProp: 'label',
|
||||
objectPath: 'configuration.yAxis.label'
|
||||
},
|
||||
{
|
||||
modelProp: 'autoscale',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.yAxis.autoscale'
|
||||
},
|
||||
{
|
||||
modelProp: 'autoscalePadding',
|
||||
coerce: Number,
|
||||
objectPath: 'configuration.yAxis.autoscalePadding'
|
||||
},
|
||||
{
|
||||
modelProp: 'range',
|
||||
objectPath: 'configuration.yAxis.range',
|
||||
coerce: function coerceRange(range) {
|
||||
if (!range) {
|
||||
return {
|
||||
min: 0,
|
||||
max: 0
|
||||
};
|
||||
}
|
||||
|
||||
const newRange = {};
|
||||
if (typeof range.min !== 'undefined' && range.min !== null) {
|
||||
newRange.min = Number(range.min);
|
||||
}
|
||||
|
||||
if (typeof range.max !== 'undefined' && range.max !== null) {
|
||||
newRange.max = Number(range.max);
|
||||
}
|
||||
|
||||
return newRange;
|
||||
},
|
||||
validate: function validateRange(range, model) {
|
||||
if (!range) {
|
||||
return 'Need range';
|
||||
}
|
||||
|
||||
if (range.min === '' || range.min === null || typeof range.min === 'undefined') {
|
||||
return 'Must specify Minimum';
|
||||
}
|
||||
|
||||
if (range.max === '' || range.max === null || typeof range.max === 'undefined') {
|
||||
return 'Must specify Maximum';
|
||||
}
|
||||
|
||||
if (Number.isNaN(Number(range.min))) {
|
||||
return 'Minimum must be a number.';
|
||||
}
|
||||
|
||||
if (Number.isNaN(Number(range.max))) {
|
||||
return 'Maximum must be a number.';
|
||||
}
|
||||
|
||||
if (Number(range.min) > Number(range.max)) {
|
||||
return 'Minimum must be less than Maximum.';
|
||||
}
|
||||
|
||||
if (model.get('autoscale')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
];
|
||||
},
|
||||
initFormValues() {
|
||||
this.label = this.yAxis.get('label');
|
||||
this.autoscale = this.yAxis.get('autoscale');
|
||||
this.autoscalePadding = this.yAxis.get('autoscalePadding');
|
||||
const range = this.yAxis.get('range');
|
||||
if (range) {
|
||||
this.rangeMin = range.min;
|
||||
this.rangeMax = range.max;
|
||||
}
|
||||
},
|
||||
updateForm(formKey) {
|
||||
let newVal;
|
||||
if (formKey === 'range') {
|
||||
newVal = {
|
||||
min: this.rangeMin,
|
||||
max: this.rangeMax
|
||||
};
|
||||
} else {
|
||||
newVal = this[formKey];
|
||||
}
|
||||
|
||||
const oldVal = this.yAxis.get(formKey);
|
||||
const formField = this.fields.find((field) => field.modelProp === formKey);
|
||||
|
||||
const path = objectPath(formField.objectPath);
|
||||
const validationResult = validate(newVal, this.yAxis, formField.validate);
|
||||
if (validationResult === true) {
|
||||
delete this.validation[formKey];
|
||||
} else {
|
||||
this.validation[formKey] = validationResult;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
|
||||
this.yAxis.set(formKey, coerce(newVal, formField.coerce));
|
||||
if (path) {
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
path(this.domainObject, this.yAxis),
|
||||
coerce(newVal, formField.coerce)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
@ -1,29 +0,0 @@
|
||||
export function coerce(value, coerceFunc) {
|
||||
if (coerceFunc) {
|
||||
return coerceFunc(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function validate(value, model, validateFunc) {
|
||||
if (validateFunc) {
|
||||
return validateFunc(value, model);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function objectPath(path) {
|
||||
if (path) {
|
||||
if (typeof path !== "function") {
|
||||
const staticObjectPath = path;
|
||||
|
||||
return function (object, model) {
|
||||
return staticObjectPath;
|
||||
};
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
@ -426,12 +426,6 @@ export default {
|
||||
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;
|
||||
|
@ -100,7 +100,7 @@ export default {
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
|
||||
this.exportImageService = this.openmct.$injector.get('exportImageService');
|
||||
//this.exportImageService = this.openmct.$injector.get('exportImageService');
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.destroy();
|
||||
|
@ -40,7 +40,7 @@ export default function PlotViewProvider(openmct) {
|
||||
}
|
||||
|
||||
function isCompactView(objectPath) {
|
||||
return objectPath.find(object => object.type === 'time-strip');
|
||||
return true;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -403,10 +403,6 @@ export default class PlotSeries extends Model {
|
||||
* @public
|
||||
*/
|
||||
updateFiltersAndRefresh(updatedFilters) {
|
||||
if (updatedFilters === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
|
||||
|
||||
if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
|
||||
|
@ -71,8 +71,7 @@ export default class XAxisModel extends Model {
|
||||
defaults(options) {
|
||||
const bounds = options.openmct.time.bounds();
|
||||
const timeSystem = options.openmct.time.timeSystem();
|
||||
const format = options.openmct.$injector.get('formatService')
|
||||
.getFormat(timeSystem.timeFormat);
|
||||
const format = options.openmct.telemetry.getFormatter(timeSystem.timeFormat);
|
||||
|
||||
return {
|
||||
name: timeSystem.name,
|
||||
|
@ -23,14 +23,12 @@
|
||||
import PlotViewProvider from './PlotViewProvider';
|
||||
import OverlayPlotViewProvider from '../overlayPlot/OverlayPlotViewProvider';
|
||||
import StackedPlotViewProvider from '../stackedPlot/StackedPlotViewProvider';
|
||||
import PlotsInspectorViewProvider from '../inspector/PlotsInspectorViewProvider';
|
||||
|
||||
export default function () {
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new StackedPlotViewProvider(openmct));
|
||||
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
|
||||
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
|
||||
openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,8 +26,6 @@ import Vue from "vue";
|
||||
import StackedPlot from "../stackedPlot/StackedPlot.vue";
|
||||
import configStore from "@/plugins/plot/vue/single/configuration/configStore";
|
||||
import EventEmitter from "EventEmitter";
|
||||
import PlotOptions from "../inspector/PlotOptions.vue";
|
||||
import PlotConfigurationModel from "@/plugins/plot/vue/single/configuration/PlotConfigurationModel";
|
||||
|
||||
describe("the plugin", function () {
|
||||
let element;
|
||||
@ -176,35 +174,6 @@ describe("the plugin", function () {
|
||||
expect(plotView).toBeDefined();
|
||||
});
|
||||
|
||||
it('provides an inspector view for overlay plots', () => {
|
||||
let selection = [
|
||||
[
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
id: "test-object",
|
||||
type: "telemetry.plot.overlay",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "some-key"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
type: 'time-strip'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
const plotInspectorView = openmct.inspectorViews.get(selection);
|
||||
expect(plotInspectorView.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("provides a stacked plot view for objects with telemetry", () => {
|
||||
const testTelemetryObject = {
|
||||
id: "test-object",
|
||||
@ -609,218 +578,4 @@ describe("the plugin", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('the inspector view', () => {
|
||||
let component;
|
||||
let viewComponentObject;
|
||||
let mockComposition;
|
||||
let testTelemetryObject;
|
||||
let selection;
|
||||
let config;
|
||||
beforeEach((done) => {
|
||||
testTelemetryObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object"
|
||||
},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "utc",
|
||||
format: "utc",
|
||||
name: "Time",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
selection = [
|
||||
[
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
id: "test-object",
|
||||
identifier: {
|
||||
key: "test-object",
|
||||
namespace: ''
|
||||
},
|
||||
type: "telemetry.plot.overlay",
|
||||
configuration: {
|
||||
series: [
|
||||
{
|
||||
identifier: {
|
||||
key: "test-object",
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
composition: []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
type: 'time-strip',
|
||||
identifier: {
|
||||
key: 'some-other-key',
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
mockComposition = new EventEmitter();
|
||||
mockComposition.load = () => {
|
||||
mockComposition.emit('add', testTelemetryObject);
|
||||
|
||||
return [testTelemetryObject];
|
||||
};
|
||||
|
||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||
|
||||
const configId = openmct.objects.makeKeyString(selection[0][0].context.item.identifier);
|
||||
config = new PlotConfigurationModel({
|
||||
id: configId,
|
||||
domainObject: selection[0][0].context.item,
|
||||
openmct: openmct
|
||||
});
|
||||
configStore.add(configId, config);
|
||||
|
||||
let viewContainer = document.createElement('div');
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
PlotOptions
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: selection[0][0].context.item,
|
||||
path: [selection[0][0].context.item, selection[0][1].context.item]
|
||||
},
|
||||
template: '<plot-options/>'
|
||||
});
|
||||
|
||||
Vue.nextTick(() => {
|
||||
viewComponentObject = component.$root.$children[0];
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('in view only mode', () => {
|
||||
let browseOptionsEl;
|
||||
let editOptionsEl;
|
||||
beforeEach(() => {
|
||||
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
|
||||
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
|
||||
});
|
||||
|
||||
it('does not show the edit options', () => {
|
||||
expect(editOptionsEl).toBeNull();
|
||||
});
|
||||
|
||||
it('shows the name', () => {
|
||||
const seriesEl = browseOptionsEl.querySelector('.c-object-label__name');
|
||||
expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name);
|
||||
});
|
||||
|
||||
it('shows in collapsed mode', () => {
|
||||
const seriesEl = browseOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
||||
expect(seriesEl.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('shows in expanded mode', () => {
|
||||
let expandControl = browseOptionsEl.querySelector(".c-disclosure-triangle");
|
||||
const clickEvent = createMouseEvent("click");
|
||||
expandControl.dispatchEvent(clickEvent);
|
||||
|
||||
const plotOptionsProperties = browseOptionsEl.querySelectorAll('.js-plot-options-browse-properties .grid-row');
|
||||
expect(plotOptionsProperties.length).toEqual(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('in edit mode', () => {
|
||||
let editOptionsEl;
|
||||
let browseOptionsEl;
|
||||
|
||||
beforeEach((done) => {
|
||||
viewComponentObject.setEditState(true);
|
||||
Vue.nextTick(() => {
|
||||
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
|
||||
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show the browse options', () => {
|
||||
expect(browseOptionsEl).toBeNull();
|
||||
});
|
||||
|
||||
it('shows the name', () => {
|
||||
const seriesEl = editOptionsEl.querySelector('.c-object-label__name');
|
||||
expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name);
|
||||
});
|
||||
|
||||
it('shows in collapsed mode', () => {
|
||||
const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
||||
expect(seriesEl.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('shows in collapsed mode', () => {
|
||||
const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
||||
expect(seriesEl.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('renders expanded', () => {
|
||||
const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle");
|
||||
const clickEvent = createMouseEvent("click");
|
||||
expandControl.dispatchEvent(clickEvent);
|
||||
|
||||
const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row");
|
||||
expect(plotOptionsProperties.length).toEqual(6);
|
||||
});
|
||||
|
||||
it('shows yKeyOptions', () => {
|
||||
const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle");
|
||||
const clickEvent = createMouseEvent("click");
|
||||
expandControl.dispatchEvent(clickEvent);
|
||||
|
||||
const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row");
|
||||
|
||||
const yKeySelection = plotOptionsProperties[0].querySelector('select');
|
||||
const options = Array.from(yKeySelection.options).map((option) => {
|
||||
return option.value;
|
||||
});
|
||||
expect(options).toEqual([testTelemetryObject.telemetry.values[1].key, testTelemetryObject.telemetry.values[2].key]);
|
||||
});
|
||||
|
||||
it('shows yAxis options', () => {
|
||||
const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle");
|
||||
const clickEvent = createMouseEvent("click");
|
||||
expandControl.dispatchEvent(clickEvent);
|
||||
|
||||
const yAxisProperties = editOptionsEl.querySelectorAll("div.grid-properties:first-of-type .l-inspector-part");
|
||||
expect(yAxisProperties.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -65,7 +65,9 @@ define([
|
||||
'./interceptors/plugin',
|
||||
'./performanceIndicator/plugin',
|
||||
'./CouchDBSearchFolder/plugin',
|
||||
'./timeline/plugin'
|
||||
'./localStorage/plugin',
|
||||
'./timeline/plugin',
|
||||
'./devices/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
@ -111,10 +113,11 @@ define([
|
||||
ObjectInterceptors,
|
||||
PerformanceIndicator,
|
||||
CouchDBSearchFolder,
|
||||
Timeline
|
||||
LocalStorage,
|
||||
Timeline,
|
||||
Devices
|
||||
) {
|
||||
const bundleMap = {
|
||||
LocalStorage: 'platform/persistence/local',
|
||||
MyItems: 'platform/features/my-items',
|
||||
Elasticsearch: 'platform/persistence/elastic'
|
||||
};
|
||||
@ -127,12 +130,14 @@ define([
|
||||
};
|
||||
});
|
||||
|
||||
plugins.LocalStorage = LocalStorage.default;
|
||||
plugins.UTCTimeSystem = UTCTimeSystem;
|
||||
plugins.LocalTimeSystem = LocalTimeSystem;
|
||||
|
||||
plugins.ImportExport = ImportExport;
|
||||
|
||||
plugins.StaticRootPlugin = StaticRootPlugin;
|
||||
plugins.Devices = Devices.default;
|
||||
|
||||
/**
|
||||
* A tabular view showing the latest values of multiple telemetry points at
|
||||
|
@ -94,7 +94,6 @@ define([
|
||||
initialize() {
|
||||
if (this.domainObject.type === 'table') {
|
||||
this.filterObserver = this.openmct.objects.observe(this.domainObject, 'configuration.filters', this.updateFilters);
|
||||
this.filters = this.domainObject.configuration.filters;
|
||||
this.loadComposition();
|
||||
} else {
|
||||
this.addTelemetryObject(this.domainObject);
|
||||
@ -139,18 +138,7 @@ define([
|
||||
this.emit('object-added', telemetryObject);
|
||||
}
|
||||
|
||||
updateFilters(updatedFilters) {
|
||||
let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
|
||||
|
||||
if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
|
||||
this.filters = deepCopiedFilters;
|
||||
this.clearAndResubscribe();
|
||||
} else {
|
||||
this.filters = deepCopiedFilters;
|
||||
}
|
||||
}
|
||||
|
||||
clearAndResubscribe() {
|
||||
updateFilters() {
|
||||
this.filteredRows.clear();
|
||||
this.boundedRows.clear();
|
||||
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
|
||||
@ -234,15 +222,11 @@ define([
|
||||
getColumnMapForObject(objectKeyString) {
|
||||
let columns = this.configuration.getColumns();
|
||||
|
||||
if (columns[objectKeyString]) {
|
||||
return columns[objectKeyString].reduce((map, column) => {
|
||||
map[column.getKey()] = column;
|
||||
return columns[objectKeyString].reduce((map, column) => {
|
||||
map[column.getKey()] = column;
|
||||
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return {};
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
addColumnsForObject(telemetryObject) {
|
||||
|
@ -37,6 +37,8 @@ define([
|
||||
this.objectMutated = this.objectMutated.bind(this);
|
||||
//Make copy of configuration, otherwise change detection is impossible if shared instance is being modified.
|
||||
this.oldConfiguration = JSON.parse(JSON.stringify(this.getConfiguration()));
|
||||
|
||||
this.unlistenFromMutation = openmct.objects.observe(domainObject, '*', this.objectMutated);
|
||||
}
|
||||
|
||||
getConfiguration() {
|
||||
@ -162,7 +164,9 @@ define([
|
||||
this.updateConfiguration(configuration);
|
||||
}
|
||||
|
||||
destroy() {}
|
||||
destroy() {
|
||||
this.unlistenFromMutation();
|
||||
}
|
||||
}
|
||||
|
||||
return TelemetryTableConfiguration;
|
||||
|
@ -100,9 +100,6 @@ define([
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
},
|
||||
_getTable: function () {
|
||||
return table;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -46,7 +46,6 @@ define(
|
||||
filter = filter.trim().toLowerCase();
|
||||
|
||||
let rowsToFilter = this.getRowsToFilter(columnKey, filter);
|
||||
|
||||
if (filter.length === 0) {
|
||||
delete this.columnFilters[columnKey];
|
||||
} else {
|
||||
@ -57,16 +56,6 @@ define(
|
||||
this.emit('filter');
|
||||
}
|
||||
|
||||
setColumnRegexFilter(columnKey, filter) {
|
||||
filter = filter.trim();
|
||||
|
||||
let rowsToFilter = this.masterCollection.getRows();
|
||||
|
||||
this.columnFilters[columnKey] = new RegExp(filter);
|
||||
this.rows = rowsToFilter.filter(this.matchesFilters, this);
|
||||
this.emit('filter');
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -82,10 +71,6 @@ define(
|
||||
* @private
|
||||
*/
|
||||
isSubsetOfCurrentFilter(columnKey, filter) {
|
||||
if (this.columnFilters[columnKey] instanceof RegExp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.columnFilters[columnKey]
|
||||
&& filter.startsWith(this.columnFilters[columnKey])
|
||||
// startsWith check will otherwise fail when filter cleared
|
||||
@ -112,11 +97,7 @@ define(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.columnFilters[key] instanceof RegExp) {
|
||||
doesMatchFilters = this.columnFilters[key].test(formattedValue);
|
||||
} else {
|
||||
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
|
||||
}
|
||||
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
|
||||
});
|
||||
|
||||
return doesMatchFilters;
|
||||
|
@ -188,17 +188,7 @@
|
||||
class="c-table__search"
|
||||
@input="filterChanged(key)"
|
||||
@clear="clearFilter(key)"
|
||||
>
|
||||
|
||||
<button
|
||||
class="c-search__use-regex"
|
||||
:class="{ 'is-active': enableRegexSearch[key] }"
|
||||
title="Click to enable regex: enter a string with slashes, like this: /regex_exp/"
|
||||
@click="toggleRegex(key)"
|
||||
>
|
||||
/R/
|
||||
</button>
|
||||
</search>
|
||||
/>
|
||||
</table-column-header>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -371,7 +361,6 @@ export default {
|
||||
paused: false,
|
||||
markedRows: [],
|
||||
isShowingMarkedRowsOnly: false,
|
||||
enableRegexSearch: {},
|
||||
hideHeaders: configuration.hideHeaders,
|
||||
totalNumberOfRows: 0
|
||||
};
|
||||
@ -629,16 +618,7 @@ export default {
|
||||
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
||||
},
|
||||
filterChanged(columnKey) {
|
||||
if (this.enableRegexSearch[columnKey]) {
|
||||
if (this.isCompleteRegex(this.filters[columnKey])) {
|
||||
this.table.filteredRows.setColumnRegexFilter(columnKey, this.filters[columnKey].slice(1, -1));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
|
||||
}
|
||||
|
||||
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
|
||||
this.setHeight();
|
||||
},
|
||||
clearFilter(columnKey) {
|
||||
@ -976,18 +956,6 @@ export default {
|
||||
|
||||
this.$nextTick().then(this.calculateColumnWidths);
|
||||
},
|
||||
toggleRegex(key) {
|
||||
this.$set(this.filters, key, '');
|
||||
|
||||
if (this.enableRegexSearch[key] === undefined) {
|
||||
this.$set(this.enableRegexSearch, key, true);
|
||||
} else {
|
||||
this.$set(this.enableRegexSearch, key, !this.enableRegexSearch[key]);
|
||||
}
|
||||
},
|
||||
isCompleteRegex(string) {
|
||||
return (string.length > 2 && string[0] === '/' && string[string.length - 1] === '/');
|
||||
},
|
||||
getViewContext() {
|
||||
return {
|
||||
type: 'telemetry-table',
|
||||
|
@ -113,7 +113,6 @@ describe("the plugin", () => {
|
||||
let applicableViews;
|
||||
let tableViewProvider;
|
||||
let tableView;
|
||||
let tableInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
testTelemetryObject = {
|
||||
@ -180,8 +179,6 @@ describe("the plugin", () => {
|
||||
tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]);
|
||||
tableView.show(child, true);
|
||||
|
||||
tableInstance = tableView._getTable();
|
||||
|
||||
return telemetryPromise.then(() => Vue.nextTick());
|
||||
});
|
||||
|
||||
@ -231,41 +228,5 @@ describe("the plugin", () => {
|
||||
expect(toColumnText).toEqual(firstColumnText);
|
||||
});
|
||||
});
|
||||
|
||||
it("Supports filtering telemetry by regular text search", () => {
|
||||
tableInstance.filteredRows.setColumnFilter("some-key", "1");
|
||||
|
||||
return Vue.nextTick().then(() => {
|
||||
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||
|
||||
expect(filteredRowElements.length).toEqual(1);
|
||||
|
||||
tableInstance.filteredRows.setColumnFilter("some-key", "");
|
||||
|
||||
return Vue.nextTick().then(() => {
|
||||
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||
|
||||
expect(allRowElements.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Supports filtering using Regex", () => {
|
||||
tableInstance.filteredRows.setColumnRegexFilter("some-key", "^some-value$");
|
||||
|
||||
return Vue.nextTick().then(() => {
|
||||
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||
|
||||
expect(filteredRowElements.length).toEqual(0);
|
||||
|
||||
tableInstance.filteredRows.setColumnRegexFilter("some-key", "^some-value");
|
||||
|
||||
return Vue.nextTick().then(() => {
|
||||
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||
|
||||
expect(allRowElements.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -39,9 +39,6 @@ const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
const LOCAL_STORAGE_HISTORY_KEY_FIXED = 'tcHistory';
|
||||
const LOCAL_STORAGE_HISTORY_KEY_REALTIME = 'tcHistoryRealtime';
|
||||
const DEFAULT_RECORDS = 10;
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
const ONE_HOUR = ONE_MINUTE * 60;
|
||||
const ONE_DAY = ONE_HOUR * 24;
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'configuration'],
|
||||
@ -138,20 +135,10 @@ export default {
|
||||
methods: {
|
||||
getHistoryMenuItems() {
|
||||
const history = this.historyForCurrentTimeSystem.map(timespan => {
|
||||
let name;
|
||||
let startTime = this.formatTime(timespan.start);
|
||||
let description = `${startTime} - ${this.formatTime(timespan.end)}`;
|
||||
|
||||
if (this.timeSystem.isUTCBased && !this.openmct.time.clock()) {
|
||||
name = `${startTime} ${this.getDuration(timespan.end - timespan.start)}`;
|
||||
} else {
|
||||
name = description;
|
||||
}
|
||||
|
||||
return {
|
||||
cssClass: 'icon-history',
|
||||
name,
|
||||
description,
|
||||
name: `${this.formatTime(timespan.start)} - ${this.formatTime(timespan.end)}`,
|
||||
description: `${this.formatTime(timespan.start)} - ${this.formatTime(timespan.end)}`,
|
||||
callBack: () => this.selectTimespan(timespan)
|
||||
};
|
||||
});
|
||||
@ -176,41 +163,6 @@ export default {
|
||||
};
|
||||
});
|
||||
},
|
||||
getDuration(numericDuration) {
|
||||
let result;
|
||||
let age;
|
||||
|
||||
if (numericDuration > ONE_DAY - 1) {
|
||||
age = this.normalizeAge((numericDuration / ONE_DAY).toFixed(2));
|
||||
result = `+ ${age} day`;
|
||||
|
||||
if (age !== 1) {
|
||||
result += 's';
|
||||
}
|
||||
} else if (numericDuration > ONE_HOUR - 1) {
|
||||
age = this.normalizeAge((numericDuration / ONE_HOUR).toFixed(2));
|
||||
result = `+ ${age} hour`;
|
||||
|
||||
if (age !== 1) {
|
||||
result += 's';
|
||||
}
|
||||
} else {
|
||||
age = this.normalizeAge((numericDuration / ONE_MINUTE).toFixed(2));
|
||||
result = `+ ${age} min`;
|
||||
|
||||
if (age !== 1) {
|
||||
result += 's';
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
normalizeAge(num) {
|
||||
const hundredtized = num * 100;
|
||||
const isWhole = hundredtized % 100 === 0;
|
||||
|
||||
return isWhole ? hundredtized / 100 : num;
|
||||
},
|
||||
getHistoryFromLocalStorage() {
|
||||
const localStorageHistory = localStorage.getItem(this.storageKey);
|
||||
const history = localStorageHistory ? JSON.parse(localStorageHistory) : undefined;
|
||||
@ -232,16 +184,24 @@ export default {
|
||||
start: this.isFixed ? this.bounds.start : this.offsets.start,
|
||||
end: this.isFixed ? this.bounds.end : this.offsets.end
|
||||
};
|
||||
let self = this;
|
||||
|
||||
// no dupes
|
||||
currentHistory = currentHistory.filter(ts => !(ts.start === timespan.start && ts.end === timespan.end));
|
||||
currentHistory.unshift(timespan); // add to front
|
||||
function isNotEqual(entry) {
|
||||
const start = entry.start !== self.start;
|
||||
const end = entry.end !== self.end;
|
||||
|
||||
if (currentHistory.length > this.records) {
|
||||
currentHistory.length = this.records;
|
||||
return start || end;
|
||||
}
|
||||
|
||||
currentHistory = currentHistory.filter(isNotEqual, timespan);
|
||||
|
||||
while (currentHistory.length >= this.records) {
|
||||
currentHistory.pop();
|
||||
}
|
||||
|
||||
currentHistory.unshift(timespan);
|
||||
this.$set(this[this.currentHistory], key, currentHistory);
|
||||
|
||||
this.persistHistoryToLocalStorage();
|
||||
},
|
||||
selectTimespan(timespan) {
|
||||
|
@ -1,113 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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>
|
||||
<swim-lane :icon-class="item.type.definition.cssClass"
|
||||
:min-height="item.height"
|
||||
:show-ucontents="item.domainObject.type === 'plan'"
|
||||
:span-rows-count="item.rowCount"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ item.domainObject.name }}
|
||||
</template>
|
||||
<object-view
|
||||
ref="objectView"
|
||||
slot="object"
|
||||
class="u-contents"
|
||||
:default-object="item.domainObject"
|
||||
:object-view-key="item.viewKey"
|
||||
:object-path="item.objectPath"
|
||||
/>
|
||||
</swim-lane>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ObjectView from '@/ui/components/ObjectView.vue';
|
||||
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ObjectView,
|
||||
SwimLane
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domainObject: undefined,
|
||||
mutablePromise: undefined
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
item(newItem) {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.item = newItem.domainObject;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.openmct.objects.supportsMutation(this.item.domainObject.identifier)) {
|
||||
this.mutablePromise = this.openmct.objects.getMutable(this.item.domainObject.identifier)
|
||||
.then(this.setObject);
|
||||
} else {
|
||||
this.openmct.objects.get(this.item.domainObject.identifier)
|
||||
.then(this.setObject);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
|
||||
if (this.mutablePromise) {
|
||||
this.mutablePromise.then(() => {
|
||||
this.openmct.objects.destroyMutable(this.domainObject);
|
||||
});
|
||||
} else {
|
||||
this.openmct.objects.destroyMutable(this.domainObject);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setObject(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.mutablePromise = undefined;
|
||||
this.$nextTick(() => {
|
||||
let reference = this.$refs.objectView;
|
||||
|
||||
if (reference) {
|
||||
let childContext = this.$refs.objectView.getSelectionContext();
|
||||
childContext.item = domainObject;
|
||||
this.context = childContext;
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, this.context);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -52,10 +52,22 @@
|
||||
:key="item.keyString"
|
||||
class="u-contents c-timeline__content"
|
||||
>
|
||||
<timeline-object-view
|
||||
class="u-contents"
|
||||
:item="item"
|
||||
/>
|
||||
<swim-lane :icon-class="item.type.definition.cssClass"
|
||||
:min-height="item.height"
|
||||
:show-ucontents="item.domainObject.type === 'plan'"
|
||||
:span-rows-count="item.rowCount"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ item.domainObject.name }}
|
||||
</template>
|
||||
<object-view
|
||||
slot="object"
|
||||
class="u-contents"
|
||||
:default-object="item.domainObject"
|
||||
:object-view-key="item.viewKey"
|
||||
:object-path="item.objectPath"
|
||||
/>
|
||||
</swim-lane>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -63,7 +75,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TimelineObjectView from './TimelineObjectView.vue';
|
||||
import ObjectView from '@/ui/components/ObjectView.vue';
|
||||
import TimelineAxis from '../../ui/components/TimeSystemAxis.vue';
|
||||
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
||||
import { getValidatedPlan } from "../plan/util";
|
||||
@ -89,7 +101,7 @@ function getViewKey(domainObject, objectPath, openmct) {
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TimelineObjectView,
|
||||
ObjectView,
|
||||
TimelineAxis,
|
||||
SwimLane
|
||||
},
|
||||
@ -146,7 +158,6 @@ export default {
|
||||
},
|
||||
removeItem(identifier) {
|
||||
let index = this.items.findIndex(item => this.openmct.objects.areIdsEqual(identifier, item.domainObject.identifier));
|
||||
this.removeSelectable(this.items[index]);
|
||||
this.items.splice(index, 1);
|
||||
},
|
||||
reorder(reorderPlan) {
|
||||
|
62
src/plugins/utcTimeSystem/DurationFormat.js
Normal file
62
src/plugins/utcTimeSystem/DurationFormat.js
Normal file
@ -0,0 +1,62 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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([
|
||||
'moment'
|
||||
], function (
|
||||
moment
|
||||
) {
|
||||
|
||||
const DATE_FORMAT = "HH:mm:ss";
|
||||
const DATE_FORMATS = [
|
||||
DATE_FORMAT
|
||||
];
|
||||
|
||||
/**
|
||||
* Formatter for duration. Uses moment to produce a date from a given
|
||||
* value, but output is formatted to display only time. Can be used for
|
||||
* specifying a time duration. For specifying duration, it's best to
|
||||
* specify a date of January 1, 1970, as the ms offset will equal the
|
||||
* duration represented by the time.
|
||||
*
|
||||
* @implements {Format}
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/formats
|
||||
*/
|
||||
function DurationFormat() {
|
||||
this.key = "duration";
|
||||
}
|
||||
|
||||
DurationFormat.prototype.format = function (value) {
|
||||
return moment.utc(value).format(DATE_FORMAT);
|
||||
};
|
||||
|
||||
DurationFormat.prototype.parse = function (text) {
|
||||
return moment.duration(text).asMilliseconds();
|
||||
};
|
||||
|
||||
DurationFormat.prototype.validate = function (text) {
|
||||
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||
};
|
||||
|
||||
return DurationFormat;
|
||||
});
|
83
src/plugins/utcTimeSystem/UTCTimeFormat.js
Normal file
83
src/plugins/utcTimeSystem/UTCTimeFormat.js
Normal file
@ -0,0 +1,83 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, 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([
|
||||
'moment'
|
||||
], function (
|
||||
moment
|
||||
) {
|
||||
|
||||
const DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS";
|
||||
const DATE_FORMATS = [
|
||||
DATE_FORMAT,
|
||||
DATE_FORMAT + "Z",
|
||||
"YYYY-MM-DD HH:mm:ss",
|
||||
"YYYY-MM-DD HH:mm",
|
||||
"YYYY-MM-DD"
|
||||
];
|
||||
|
||||
/**
|
||||
* @typedef Scale
|
||||
* @property {number} min the minimum scale value, in ms
|
||||
* @property {number} max the maximum scale value, in ms
|
||||
*/
|
||||
|
||||
/**
|
||||
* Formatter for UTC timestamps. Interprets numeric values as
|
||||
* milliseconds since the start of 1970.
|
||||
*
|
||||
* @implements {Format}
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/formats
|
||||
*/
|
||||
function UTCTimeFormat() {
|
||||
this.key = "utc";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value The value to format.
|
||||
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
||||
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
||||
* in the array.
|
||||
*/
|
||||
UTCTimeFormat.prototype.format = function (value) {
|
||||
if (value !== undefined) {
|
||||
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
UTCTimeFormat.prototype.parse = function (text) {
|
||||
if (typeof text === 'number') {
|
||||
return text;
|
||||
}
|
||||
|
||||
return moment.utc(text, DATE_FORMATS).valueOf();
|
||||
};
|
||||
|
||||
UTCTimeFormat.prototype.validate = function (text) {
|
||||
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||
};
|
||||
|
||||
return UTCTimeFormat;
|
||||
});
|
@ -22,10 +22,14 @@
|
||||
|
||||
define([
|
||||
"./UTCTimeSystem",
|
||||
"./LocalClock"
|
||||
"./LocalClock",
|
||||
"./UTCTimeFormat",
|
||||
"./DurationFormat"
|
||||
], function (
|
||||
UTCTimeSystem,
|
||||
LocalClock
|
||||
LocalClock,
|
||||
UTCTimeFormat,
|
||||
DurationFormat
|
||||
) {
|
||||
/**
|
||||
* Install a time system that supports UTC times. It also installs a local
|
||||
@ -36,6 +40,8 @@ define([
|
||||
const timeSystem = new UTCTimeSystem();
|
||||
openmct.time.addTimeSystem(timeSystem);
|
||||
openmct.time.addClock(new LocalClock(100));
|
||||
openmct.telemetry.addFormat(new UTCTimeFormat());
|
||||
openmct.telemetry.addFormat(new DurationFormat());
|
||||
};
|
||||
};
|
||||
});
|
||||
|
@ -237,13 +237,11 @@ define(
|
||||
|
||||
const capture = this.capture.bind(this, selectable);
|
||||
const selectCapture = this.selectCapture.bind(this, selectable);
|
||||
let removeMutable = false;
|
||||
|
||||
element.addEventListener('click', capture, true);
|
||||
element.addEventListener('click', selectCapture);
|
||||
|
||||
if (context.item && context.item.isMutable !== true) {
|
||||
removeMutable = true;
|
||||
if (context.item) {
|
||||
context.item = this.openmct.objects._toMutable(context.item);
|
||||
}
|
||||
|
||||
@ -259,7 +257,7 @@ define(
|
||||
element.removeEventListener('click', capture, true);
|
||||
element.removeEventListener('click', selectCapture);
|
||||
|
||||
if (context.item !== undefined && context.item.isMutable && removeMutable === true) {
|
||||
if (context.item !== undefined && context.item.isMutable) {
|
||||
this.openmct.objects.destroyMutable(context.item);
|
||||
}
|
||||
}).bind(this);
|
||||
|
@ -1,11 +1,6 @@
|
||||
@mixin visibleRegexButton {
|
||||
opacity: 1;
|
||||
padding: 1px 3px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.c-search {
|
||||
@include wrappedInput();
|
||||
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
|
||||
@ -14,46 +9,11 @@
|
||||
content: $glyph-icon-magnify;
|
||||
}
|
||||
|
||||
&__use-regex {
|
||||
// Button
|
||||
$c: $colorBodyFg;
|
||||
background: rgba($c, 0.2);
|
||||
border: 1px solid rgba($c, 0.3);
|
||||
color: $c;
|
||||
border-radius: $controlCr;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
font-size: 0.8em;
|
||||
margin-left: $interiorMarginSm;
|
||||
min-width: 0;
|
||||
opacity: 0;
|
||||
order: 2;
|
||||
overflow: hidden;
|
||||
padding: 1px 0;
|
||||
transform-origin: left;
|
||||
transition: $transOut;
|
||||
width: 0;
|
||||
|
||||
&.is-active {
|
||||
$c: $colorBtnActiveBg;
|
||||
@include visibleRegexButton();
|
||||
background: rgba($c, 0.3);
|
||||
border-color: $c;
|
||||
color: $c;
|
||||
}
|
||||
}
|
||||
|
||||
&__clear-input {
|
||||
display: none;
|
||||
order: 99;
|
||||
padding: 1px 0;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
.c-search__use-regex {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.c-search__clear-input {
|
||||
display: block;
|
||||
}
|
||||
@ -61,15 +21,6 @@
|
||||
|
||||
input[type='text'],
|
||||
input[type='search'] {
|
||||
margin-left: $interiorMargin;
|
||||
order: 3;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.c-search__use-regex {
|
||||
@include visibleRegexButton();
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
class="c-search__clear-input icon-x-in-circle"
|
||||
@click="clearInput"
|
||||
></a>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
<div class="c-swimlane__lane-object"
|
||||
:style="{'min-height': minHeight}"
|
||||
:class="{'u-contents': showUcontents}"
|
||||
data-selectable
|
||||
>
|
||||
<slot name="object"></slot>
|
||||
</div>
|
||||
|
@ -13,7 +13,7 @@
|
||||
</div>
|
||||
<span v-if="!singleSelectNonObject"
|
||||
class="c-inspector__selected c-object-label__name"
|
||||
>{{ item.name }}</span>
|
||||
>{{ domainObject.name }}</span>
|
||||
<div v-if="singleSelectNonObject"
|
||||
class="c-inspector__selected c-inspector__selected--non-domain-object c-object-label"
|
||||
>
|
||||
@ -36,7 +36,7 @@ export default {
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
return {
|
||||
domainObject: {},
|
||||
domainObject: undefined,
|
||||
keyString: undefined,
|
||||
multiSelect: false,
|
||||
itemsSelected: 0,
|
||||
@ -44,21 +44,22 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
item() {
|
||||
return this.domainObject || {};
|
||||
},
|
||||
type() {
|
||||
return this.openmct.types.get(this.item.type);
|
||||
if (this.domainObject !== undefined) {
|
||||
return this.openmct.types.get(this.domainObject.type);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
typeCssClass() {
|
||||
if (this.type.definition.cssClass === undefined) {
|
||||
if (this.type === undefined || this.type.definition.cssClass === undefined) {
|
||||
return 'icon-object';
|
||||
}
|
||||
|
||||
return this.type.definition.cssClass;
|
||||
},
|
||||
singleSelectNonObject() {
|
||||
return !this.item.identifier && !this.multiSelect;
|
||||
return !this.domainObject && !this.multiSelect;
|
||||
},
|
||||
statusClass() {
|
||||
return this.status ? `is-status--${this.status}` : '';
|
||||
|
@ -108,7 +108,6 @@
|
||||
import Inspector from '../inspector/Inspector.vue';
|
||||
import MctTree from './mct-tree.vue';
|
||||
import ObjectView from '../components/ObjectView.vue';
|
||||
import MctTemplate from '../legacy/mct-template.vue';
|
||||
import CreateButton from './CreateButton.vue';
|
||||
import multipane from './multipane.vue';
|
||||
import pane from './pane.vue';
|
||||
@ -123,7 +122,6 @@ export default {
|
||||
Inspector,
|
||||
MctTree,
|
||||
ObjectView,
|
||||
'mct-template': MctTemplate,
|
||||
CreateButton,
|
||||
multipane,
|
||||
pane,
|
||||
|
@ -308,9 +308,6 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async initialize() {
|
||||
// required to index tree objects that do not have search providers
|
||||
this.openmct.$injector.get('searchService');
|
||||
|
||||
window.addEventListener('resize', this.handleWindowResize);
|
||||
|
||||
await this.calculateHeights();
|
||||
@ -710,25 +707,25 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
aggregateSearchResults(results, abortSignal) {
|
||||
async aggregateSearchResults(results, abortSignal) {
|
||||
for (const result of results) {
|
||||
if (!abortSignal.aborted) {
|
||||
this.openmct.objects.getOriginalPath(result.identifier).then((objectPath) => {
|
||||
// removing the item itself, as the path we pass to buildTreeItem is a parent path
|
||||
objectPath.shift();
|
||||
const objectPath = await this.openmct.objects.getOriginalPath(result.identifier);
|
||||
|
||||
// if root, remove, we're not using in object path for tree
|
||||
let lastObject = objectPath.length ? objectPath[objectPath.length - 1] : false;
|
||||
if (lastObject && lastObject.type === 'root') {
|
||||
objectPath.pop();
|
||||
}
|
||||
// removing the item itself, as the path we pass to buildTreeItem is a parent path
|
||||
objectPath.shift();
|
||||
|
||||
// we reverse the objectPath in the tree, so have to do it here first,
|
||||
// since this one is already in the correct direction
|
||||
let resultObject = this.buildTreeItem(result, objectPath.reverse());
|
||||
// if root, remove, we're not using in object path for tree
|
||||
let lastObject = objectPath.length ? objectPath[objectPath.length - 1] : false;
|
||||
if (lastObject && lastObject.type === 'root') {
|
||||
objectPath.pop();
|
||||
}
|
||||
|
||||
this.searchResultItems.push(resultObject);
|
||||
});
|
||||
// we reverse the objectPath in the tree, so have to do it here first,
|
||||
// since this one is already in the correct direction
|
||||
let resultObject = this.buildTreeItem(result, objectPath.reverse());
|
||||
|
||||
this.searchResultItems.push(resultObject);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -66,9 +66,6 @@ export default {
|
||||
watch: {
|
||||
highlight() {
|
||||
this.highlightText();
|
||||
},
|
||||
text() {
|
||||
this.highlightText();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
Reference in New Issue
Block a user