Compare commits

..

2 Commits

Author SHA1 Message Date
cf5ef9fa60 Remove angular, hack around things that don't work 2021-04-05 15:09:22 -07:00
756e5d301e Removing angular 2021-04-02 18:27:09 -07:00
88 changed files with 1621 additions and 2939 deletions

1
API.md
View File

@ -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

View File

@ -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
}
}
]
};

View File

@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "1.7.2",
"version": "1.6.3-SNAPSHOT",
"description": "The Open MCT core platform",
"dependencies": {},
"devDependencies": {

View File

@ -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) {

View 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'))
```

View 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"
]
}
]
}
}
};
});

View 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;
}
);

View 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;
}
);

View 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;
}
);

View 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);
});
});
}
);

View 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");
});
});
}
);

View 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);
});
});
}
);

View File

@ -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 () {

View File

@ -52,7 +52,7 @@ define([
oldStyleObject.getCapability('mutation').mutate(function () {
return utils.toOldFormat(newStyleObject);
}, newStyleObject.modified);
});
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
}.bind(this);

View File

@ -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) {

View File

@ -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');
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
};
/**

View File

@ -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 = {};

View File

@ -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',

View File

@ -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) {

View File

@ -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]);

View File

@ -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>

View File

@ -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);

View File

@ -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 => {

View File

@ -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) => {

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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,

View File

@ -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 {

View 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');
});
};
}

View File

@ -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);
}
},

View File

@ -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));

View File

@ -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);
},

View File

@ -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));
};

View File

@ -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: {

View File

@ -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() {

View File

@ -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%);
}
}
}

View File

@ -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', () => {

View File

@ -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);
};

View 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];
}
}

View 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));
};
}

View File

@ -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 => {

View File

@ -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"

View File

@ -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;

View File

@ -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');
});
});
});
});

View File

@ -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": [
{

View File

@ -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)) {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
};
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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();

View File

@ -40,7 +40,7 @@ export default function PlotViewProvider(openmct) {
}
function isCompactView(objectPath) {
return objectPath.find(object => object.type === 'time-strip');
return true;
}
return {

View File

@ -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)) {

View File

@ -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,

View File

@ -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));
};
}

View File

@ -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);
});
});
});
});

View File

@ -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

View File

@ -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) {

View File

@ -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;

View File

@ -100,9 +100,6 @@ define([
destroy: function (element) {
component.$destroy();
component = undefined;
},
_getTable: function () {
return table;
}
};

View File

@ -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;

View File

@ -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',

View File

@ -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);
});
});
});
});
});

View File

@ -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) {

View File

@ -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>

View File

@ -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) {

View 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;
});

View 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;
});

View File

@ -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());
};
};
});

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -15,7 +15,6 @@
class="c-search__clear-input icon-x-in-circle"
@click="clearInput"
></a>
<slot></slot>
</div>
</template>

View File

@ -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>

View File

@ -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}` : '';

View File

@ -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,

View File

@ -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);
}
}
},

View File

@ -66,9 +66,6 @@ export default {
watch: {
highlight() {
this.highlightText();
},
text() {
this.highlightText();
}
},
mounted() {