Compare commits

..

2 Commits

Author SHA1 Message Date
5f0096ba16 Merge branch 'master' into angular-upgrade-test 2020-06-19 14:09:21 -07:00
b4e6893627 bumped angular to >=1.8.0 2020-06-18 09:19:27 -07:00
126 changed files with 1678 additions and 3741 deletions

3
.gitignore vendored
View File

@ -37,7 +37,4 @@ protractor/logs
# npm-debug log # npm-debug log
npm-debug.log npm-debug.log
# karma reports
report.*.json
package-lock.json package-lock.json

View File

@ -226,9 +226,9 @@ typically from the author of the change and its reviewer.
Automated testing shall occur whenever changes are merged into the main Automated testing shall occur whenever changes are merged into the main
development branch and must be confirmed alongside any pull request. development branch and must be confirmed alongside any pull request.
Automated tests are tests which exercise plugins, API, and utility classes. Automated tests are typically unit tests which exercise individual software
Tests are subject to code review along with the actual implementation, to components. Tests are subject to code review along with the actual
ensure that tests are applicable and useful. implementation, to ensure that tests are applicable and useful.
Examples of useful tests: Examples of useful tests:
* Tests which replicate bugs (or their root causes) to verify their * Tests which replicate bugs (or their root causes) to verify their
@ -238,26 +238,8 @@ Examples of useful tests:
* Tests which verify expected interactions with other components in the * Tests which verify expected interactions with other components in the
system. system.
#### Guidelines During automated testing, code coverage metrics will be reported. Line
* 100% statement coverage is achievable and desirable. coverage must remain at or above 80%.
* Do blackbox testing. Test external behaviors, not internal details. Write tests that describe what your plugin is supposed to do. How it does this doesn't matter, so don't test it.
* Unit test specs for plugins should be defined at the plugin level. Start with one test spec per plugin named pluginSpec.js, and as this test spec grows too big, break it up into multiple test specs that logically group related tests.
* Unit tests for API or for utility functions and classes may be defined at a per-source file level.
* Wherever possible only use and mock public API, builtin functions, and UI in your test specs. Do not directly invoke any private functions. ie. only call or mock functions and objects exposed by openmct.* (eg. openmct.telemetry, openmct.objectView, etc.), and builtin browser functions (fetch, requestAnimationFrame, setTimeout, etc.).
* Where builtin functions have been mocked, be sure to clear them between tests.
* Test at an appropriate level of isolation. Eg.
* If youre testing a view, you do not need to test the whole application UI, you can just fetch the view provider using the public API and render the view into an element that you have created.
* You do not need to test that the view switcher works, there should be separate tests for that.
* You do not need to test that telemetry providers work, you can mock openmct.telemetry.request() to feed test data to the view.
* Use your best judgement when deciding on appropriate scope.
* Automated tests for plugins should start by actually installing the plugin being tested, and then test that installing the plugin adds the desired features and behavior to Open MCT, observing the above rules.
* All variables used in a test spec, including any instances of the Open MCT API should be declared inside of an appropriate block scope (not at the root level of the source file), and should be initialized in the relevant beforeEach block. `beforeEach` is preferable to `beforeAll` to avoid leaking of state between tests.
* A `afterEach` or `afterAll` should be used to do any clean up necessary to prevent leakage of state between test specs. This can happen when functions on `window` are wrapped, or when the URL is changed. [A convenience function](https://github.com/nasa/openmct/blob/master/src/utils/testing.js#L59) is provided for resetting the URL and clearing builtin spies between tests.
* If writing unit tests for legacy Angular code be sure to follow [best practices in order to avoid memory leaks](https://www.thecodecampus.de/blog/avoid-memory-leaks-angularjs-unit-tests/).
#### Examples
* [Example of an automated test spec for an object view plugin](https://github.com/nasa/openmct/blob/master/src/plugins/telemetryTable/pluginSpec.js)
* [Example of an automated test spec for API](https://github.com/nasa/openmct/blob/master/src/api/time/TimeAPISpec.js)
### Commit Message Standards ### Commit Message Standards

View File

@ -1,6 +1,6 @@
# Open MCT License # Open MCT License
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, Copyright (c) 2014-2019, 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. 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.

View File

@ -28,16 +28,6 @@ define([
domain: 2 domain: 2
} }
}, },
// Need to enable "LocalTimeSystem" plugin to make use of this
// {
// key: "local",
// name: "Time",
// format: "local-format",
// source: "utc",
// hints: {
// domain: 3
// }
// },
{ {
key: "sin", key: "sin",
name: "Sine", name: "Sine",
@ -71,15 +61,6 @@ define([
domain: 1 domain: 1
} }
}, },
{
key: "local",
name: "Time",
format: "utc",
source: "utc",
hints: {
domain: 2
}
},
{ {
key: "state", key: "state",
source: "value", source: "value",

View File

@ -34,8 +34,8 @@
<body> <body>
</body> </body>
<script> <script>
const THIRTY_SECONDS = 30 * 1000; const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_MINUTES = THIRTY_SECONDS * 60; const THIRTY_MINUTES = 30 * 60 * 1000;
[ [
'example/eventGenerator' 'example/eventGenerator'
@ -63,39 +63,7 @@
bounds: { bounds: {
start: Date.now() - THIRTY_MINUTES, start: Date.now() - THIRTY_MINUTES,
end: Date.now() end: Date.now()
}, }
// commonly used bounds can be stored in history
// bounds (start and end) can accept either a milliseconds number
// or a callback function returning a milliseconds number
// a function is useful for invoking Date.now() at exact moment of preset selection
presets: [
{
label: 'Last Day',
bounds: {
start: () => Date.now() - 1000 * 60 * 60 * 24,
end: () => Date.now()
}
},
{
label: 'Last 2 hours',
bounds: {
start: () => Date.now() - 1000 * 60 * 60 * 2,
end: () => Date.now()
}
},
{
label: 'Last hour',
bounds: {
start: () => Date.now() - 1000 * 60 * 60,
end: () => Date.now()
}
}
],
// maximum recent bounds to retain in conductor history
records: 10,
// maximum duration between start and end bounds
// for utc-based time systems this is in milliseconds
limit: 1000 * 60 * 60 * 24
}, },
{ {
name: "Realtime", name: "Realtime",
@ -103,7 +71,7 @@
clock: 'local', clock: 'local',
clockOffsets: { clockOffsets: {
start: - THIRTY_MINUTES, start: - THIRTY_MINUTES,
end: THIRTY_SECONDS end: FIVE_MINUTES
} }
} }
] ]
@ -113,10 +81,7 @@
openmct.install(openmct.plugins.LADTable()); openmct.install(openmct.plugins.LADTable());
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay'])); openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
openmct.install(openmct.plugins.ObjectMigration()); openmct.install(openmct.plugins.ObjectMigration());
openmct.install(openmct.plugins.ClearData( openmct.install(openmct.plugins.ClearData(['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked']));
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
{indicator: true}
));
openmct.start(); openmct.start();
</script> </script>
</html> </html>

View File

@ -23,7 +23,7 @@
/*global module,process*/ /*global module,process*/
const devMode = process.env.NODE_ENV !== 'production'; const devMode = process.env.NODE_ENV !== 'production';
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'FirefoxHeadless']; const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
const coverageEnabled = process.env.COVERAGE === 'true'; const coverageEnabled = process.env.COVERAGE === 'true';
const reporters = ['progress', 'html']; const reporters = ['progress', 'html'];
@ -82,7 +82,7 @@ module.exports = (config) => {
reports: ['html', 'lcovonly', 'text-summary'], reports: ['html', 'lcovonly', 'text-summary'],
thresholds: { thresholds: {
global: { global: {
lines: 63 lines: 62
} }
} }
}, },
@ -95,7 +95,6 @@ module.exports = (config) => {
stats: 'errors-only', stats: 'errors-only',
logLevel: 'warn' logLevel: 'warn'
}, },
singleRun: true, singleRun: true
browserNoActivityTimeout: 90000
}); });
} }

View File

@ -41,7 +41,6 @@
"jsdoc": "^3.3.2", "jsdoc": "^3.3.2",
"karma": "^2.0.3", "karma": "^2.0.3",
"karma-chrome-launcher": "^2.2.0", "karma-chrome-launcher": "^2.2.0",
"karma-firefox-launcher": "^1.3.0",
"karma-cli": "^1.0.1", "karma-cli": "^1.0.1",
"karma-coverage": "^1.1.2", "karma-coverage": "^1.1.2",
"karma-coverage-istanbul-reporter": "^2.1.1", "karma-coverage-istanbul-reporter": "^2.1.1",
@ -60,7 +59,7 @@
"moment-timezone": "0.5.28", "moment-timezone": "0.5.28",
"node-bourbon": "^4.2.3", "node-bourbon": "^4.2.3",
"node-sass": "^4.9.2", "node-sass": "^4.9.2",
"painterro": "^1.0.35", "painterro": "^0.2.65",
"printj": "^1.2.1", "printj": "^1.2.1",
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"request": "^2.69.0", "request": "^2.69.0",

View File

@ -81,15 +81,10 @@ define(
* context. * context.
*/ */
PropertiesAction.appliesTo = function (context) { PropertiesAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject, var domainObject = (context || {}).domainObject,
type = domainObject && domainObject.getCapability('type'), type = domainObject && domainObject.getCapability('type'),
creatable = type && type.hasFeature('creation'); creatable = type && type.hasFeature('creation');
if (domainObject && domainObject.model && domainObject.model.locked) {
return false;
}
// Only allow creatable types to be edited // Only allow creatable types to be edited
return domainObject && creatable; return domainObject && creatable;
}; };

View File

@ -36,6 +36,8 @@ define(
} }
EditPersistableObjectsPolicy.prototype.allow = function (action, context) { EditPersistableObjectsPolicy.prototype.allow = function (action, context) {
var identifier;
var provider;
var domainObject = context.domainObject; var domainObject = context.domainObject;
var key = action.getMetadata().key; var key = action.getMetadata().key;
var category = (context || {}).category; var category = (context || {}).category;
@ -44,8 +46,9 @@ define(
// is also invoked during the create process which should be allowed, // is also invoked during the create process which should be allowed,
// because it may be saved elsewhere // because it may be saved elsewhere
if ((key === 'edit' && category === 'view-control') || key === 'properties') { if ((key === 'edit' && category === 'view-control') || key === 'properties') {
let newStyleObject = objectUtils.toNewFormat(domainObject, domainObject.getId()); identifier = objectUtils.parseKeyString(domainObject.getId());
return this.openmct.objects.isPersistable(newStyleObject); provider = this.openmct.objects.getProvider(identifier);
return provider.save !== undefined;
} }
return true; return true;

View File

@ -43,7 +43,7 @@ define(
); );
mockObjectAPI = jasmine.createSpyObj('objectAPI', [ mockObjectAPI = jasmine.createSpyObj('objectAPI', [
'isPersistable' 'getProvider'
]); ]);
mockAPI = { mockAPI = {
@ -69,31 +69,34 @@ define(
}); });
it("Applies to edit action", function () { it("Applies to edit action", function () {
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); mockObjectAPI.getProvider.and.returnValue({});
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
policy.allow(mockEditAction, testContext); policy.allow(mockEditAction, testContext);
expect(mockObjectAPI.isPersistable).toHaveBeenCalled(); expect(mockObjectAPI.getProvider).toHaveBeenCalled();
}); });
it("Applies to properties action", function () { it("Applies to properties action", function () {
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); mockObjectAPI.getProvider.and.returnValue({});
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
policy.allow(mockPropertiesAction, testContext); policy.allow(mockPropertiesAction, testContext);
expect(mockObjectAPI.isPersistable).toHaveBeenCalled(); expect(mockObjectAPI.getProvider).toHaveBeenCalled();
}); });
it("does not apply to other actions", function () { it("does not apply to other actions", function () {
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); mockObjectAPI.getProvider.and.returnValue({});
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
policy.allow(mockOtherAction, testContext); policy.allow(mockOtherAction, testContext);
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
}); });
it("Tests object provider for editability", function () { it("Tests object provider for editability", function () {
mockObjectAPI.isPersistable.and.returnValue(false); mockObjectAPI.getProvider.and.returnValue({});
expect(policy.allow(mockEditAction, testContext)).toBe(false); expect(policy.allow(mockEditAction, testContext)).toBe(false);
expect(mockObjectAPI.isPersistable).toHaveBeenCalled(); expect(mockObjectAPI.getProvider).toHaveBeenCalled();
mockObjectAPI.isPersistable.and.returnValue(true); mockObjectAPI.getProvider.and.returnValue({save: function () {}});
expect(policy.allow(mockEditAction, testContext)).toBe(true); expect(policy.allow(mockEditAction, testContext)).toBe(true);
}); });
}); });

View File

@ -19,13 +19,7 @@
this source code distribution or the Licensing information page available this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<div class="c-object-label" <div class="c-object-label">
ng-class="{ 'is-missing': model.status === 'missing' }" <div class="c-object-label__type-icon {{type.getCssClass()}}" ng-class="{ 'l-icon-link':location.isLink() }"></div>
>
<div class="c-object-label__type-icon {{type.getCssClass()}}"
ng-class="{ 'l-icon-link':location.isLink() }"
>
<span class="is-missing__indicator" title="This item is missing"></span>
</div>
<div class='c-object-label__name'>{{model.name}}</div> <div class='c-object-label__name'>{{model.name}}</div>
</div> </div>

View File

@ -48,8 +48,9 @@ define(
// prevents editing of objects that cannot be persisted, so we can assume that this // prevents editing of objects that cannot be persisted, so we can assume that this
// is a new object. // is a new object.
if (!(parent.hasCapability('editor') && parent.getCapability('editor').isEditContextRoot())) { if (!(parent.hasCapability('editor') && parent.getCapability('editor').isEditContextRoot())) {
let newStyleObject = objectUtils.toNewFormat(parent, parent.getId()); var identifier = objectUtils.parseKeyString(parent.getId());
return this.openmct.objects.isPersistable(newStyleObject); var provider = this.openmct.objects.getProvider(identifier);
return provider.save !== undefined;
} }
return true; return true;
}; };

View File

@ -33,7 +33,7 @@ define(
beforeEach(function () { beforeEach(function () {
objectAPI = jasmine.createSpyObj('objectsAPI', [ objectAPI = jasmine.createSpyObj('objectsAPI', [
'isPersistable' 'getProvider'
]); ]);
mockOpenMCT = { mockOpenMCT = {
@ -51,6 +51,10 @@ define(
'isEditContextRoot' 'isEditContextRoot'
]); ]);
mockParent.getCapability.and.returnValue(mockEditorCapability); mockParent.getCapability.and.returnValue(mockEditorCapability);
objectAPI.getProvider.and.returnValue({
save: function () {}
});
persistableCompositionPolicy = new PersistableCompositionPolicy(mockOpenMCT); persistableCompositionPolicy = new PersistableCompositionPolicy(mockOpenMCT);
}); });
@ -61,22 +65,19 @@ define(
it("Does not allow composition for objects that are not persistable", function () { it("Does not allow composition for objects that are not persistable", function () {
mockEditorCapability.isEditContextRoot.and.returnValue(false); mockEditorCapability.isEditContextRoot.and.returnValue(false);
objectAPI.isPersistable.and.returnValue(true);
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true); expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true);
objectAPI.isPersistable.and.returnValue(false); objectAPI.getProvider.and.returnValue({});
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(false); expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(false);
}); });
it("Always allows composition of objects in edit mode to support object creation", function () { it("Always allows composition of objects in edit mode to support object creation", function () {
mockEditorCapability.isEditContextRoot.and.returnValue(true); mockEditorCapability.isEditContextRoot.and.returnValue(true);
objectAPI.isPersistable.and.returnValue(true);
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true); expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true);
expect(objectAPI.isPersistable).not.toHaveBeenCalled(); expect(objectAPI.getProvider).not.toHaveBeenCalled();
mockEditorCapability.isEditContextRoot.and.returnValue(false); mockEditorCapability.isEditContextRoot.and.returnValue(false);
objectAPI.isPersistable.and.returnValue(true);
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true); expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true);
expect(objectAPI.isPersistable).toHaveBeenCalled(); expect(objectAPI.getProvider).toHaveBeenCalled();
}); });
}); });

View File

@ -297,8 +297,7 @@ define([
"persistenceService", "persistenceService",
"identifierService", "identifierService",
"notificationService", "notificationService",
"$q", "$q"
"openmct"
] ]
}, },
{ {

View File

@ -20,8 +20,8 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(["objectUtils"], define(
function (objectUtils) { function () {
/** /**
* Defines the `persistence` capability, used to trigger the * Defines the `persistence` capability, used to trigger the
@ -47,7 +47,6 @@ define(["objectUtils"],
identifierService, identifierService,
notificationService, notificationService,
$q, $q,
openmct,
domainObject domainObject
) { ) {
// Cache modified timestamp // Cache modified timestamp
@ -59,7 +58,6 @@ define(["objectUtils"],
this.persistenceService = persistenceService; this.persistenceService = persistenceService;
this.notificationService = notificationService; this.notificationService = notificationService;
this.$q = $q; this.$q = $q;
this.openmct = openmct;
} }
/** /**
@ -68,7 +66,7 @@ define(["objectUtils"],
*/ */
function rejectIfFalsey(value, $q) { function rejectIfFalsey(value, $q) {
if (!value) { if (!value) {
return Promise.reject("Error persisting object"); return $q.reject("Error persisting object");
} else { } else {
return value; return value;
} }
@ -100,7 +98,7 @@ define(["objectUtils"],
dismissable: true dismissable: true
}); });
return Promise.reject(error); return $q.reject(error);
} }
/** /**
@ -112,16 +110,34 @@ define(["objectUtils"],
*/ */
PersistenceCapability.prototype.persist = function () { PersistenceCapability.prototype.persist = function () {
var self = this, var self = this,
domainObject = this.domainObject; domainObject = this.domainObject,
model = domainObject.getModel(),
modified = model.modified,
persisted = model.persisted,
persistenceService = this.persistenceService,
persistenceFn = persisted !== undefined ?
this.persistenceService.updateObject :
this.persistenceService.createObject;
let newStyleObject = objectUtils.toNewFormat(domainObject.getModel(), domainObject.getId()); if (persisted !== undefined && persisted === modified) {
return this.openmct.objects return this.$q.when(true);
.save(newStyleObject) }
.then(function (result) {
return rejectIfFalsey(result, self.$q); // Update persistence timestamp...
}).catch(function (error) { domainObject.useCapability("mutation", function (m) {
return notifyOnError(error, domainObject, self.notificationService, self.$q); m.persisted = modified;
}); }, modified);
// ...and persist
return persistenceFn.apply(persistenceService, [
this.getSpace(),
this.getKey(),
domainObject.getModel()
]).then(function (result) {
return rejectIfFalsey(result, self.$q);
}).catch(function (error) {
return notifyOnError(error, domainObject, self.notificationService, self.$q);
});
}; };
/** /**

View File

@ -19,6 +19,7 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/** /**
* PersistenceCapabilitySpec. Created by vwoeltje on 11/6/14. * PersistenceCapabilitySpec. Created by vwoeltje on 11/6/14.
*/ */
@ -39,8 +40,7 @@ define(
model, model,
SPACE = "some space", SPACE = "some space",
persistence, persistence,
mockOpenMCT, happyPromise;
mockNewStyleDomainObject;
function asPromise(value, doCatch) { function asPromise(value, doCatch) {
return (value || {}).then ? value : { return (value || {}).then ? value : {
@ -56,6 +56,7 @@ define(
} }
beforeEach(function () { beforeEach(function () {
happyPromise = asPromise(true);
model = { someKey: "some value", name: "domain object"}; model = { someKey: "some value", name: "domain object"};
mockPersistenceService = jasmine.createSpyObj( mockPersistenceService = jasmine.createSpyObj(
@ -93,23 +94,12 @@ define(
}, },
useCapability: jasmine.createSpy() useCapability: jasmine.createSpy()
}; };
mockNewStyleDomainObject = Object.assign({}, model);
mockNewStyleDomainObject.identifier = {
namespace: "",
key: id
}
// Simulate mutation capability // Simulate mutation capability
mockDomainObject.useCapability.and.callFake(function (capability, mutator) { mockDomainObject.useCapability.and.callFake(function (capability, mutator) {
if (capability === 'mutation') { if (capability === 'mutation') {
model = mutator(model) || model; model = mutator(model) || model;
} }
}); });
mockOpenMCT = {};
mockOpenMCT.objects = jasmine.createSpyObj('Object API', ['save']);
mockIdentifierService.parse.and.returnValue(mockIdentifier); mockIdentifierService.parse.and.returnValue(mockIdentifier);
mockIdentifier.getSpace.and.returnValue(SPACE); mockIdentifier.getSpace.and.returnValue(SPACE);
mockIdentifier.getKey.and.returnValue(key); mockIdentifier.getKey.and.returnValue(key);
@ -120,28 +110,51 @@ define(
mockIdentifierService, mockIdentifierService,
mockNofificationService, mockNofificationService,
mockQ, mockQ,
mockOpenMCT,
mockDomainObject mockDomainObject
); );
}); });
describe("successful persistence", function () { describe("successful persistence", function () {
beforeEach(function () { beforeEach(function () {
mockOpenMCT.objects.save.and.returnValue(Promise.resolve(true)); mockPersistenceService.updateObject.and.returnValue(happyPromise);
mockPersistenceService.createObject.and.returnValue(happyPromise);
}); });
it("creates unpersisted objects with the persistence service", function () { it("creates unpersisted objects with the persistence service", function () {
// Verify precondition; no call made during constructor // Verify precondition; no call made during constructor
expect(mockOpenMCT.objects.save).not.toHaveBeenCalled(); expect(mockPersistenceService.createObject).not.toHaveBeenCalled();
persistence.persist(); persistence.persist();
expect(mockOpenMCT.objects.save).toHaveBeenCalledWith(mockNewStyleDomainObject); expect(mockPersistenceService.createObject).toHaveBeenCalledWith(
SPACE,
key,
model
);
});
it("updates previously persisted objects with the persistence service", function () {
// Verify precondition; no call made during constructor
expect(mockPersistenceService.updateObject).not.toHaveBeenCalled();
model.persisted = 12321;
persistence.persist();
expect(mockPersistenceService.updateObject).toHaveBeenCalledWith(
SPACE,
key,
model
);
}); });
it("reports which persistence space an object belongs to", function () { it("reports which persistence space an object belongs to", function () {
expect(persistence.getSpace()).toEqual(SPACE); expect(persistence.getSpace()).toEqual(SPACE);
}); });
it("updates persisted timestamp on persistence", function () {
model.modified = 12321;
persistence.persist();
expect(model.persisted).toEqual(12321);
});
it("refreshes the domain object model from persistence", function () { it("refreshes the domain object model from persistence", function () {
var refreshModel = {someOtherKey: "some other value"}; var refreshModel = {someOtherKey: "some other value"};
model.persisted = 1; model.persisted = 1;
@ -152,37 +165,30 @@ define(
it("does not trigger error notification on successful" + it("does not trigger error notification on successful" +
" persistence", function () { " persistence", function () {
let rejected = false; persistence.persist();
return persistence.persist() expect(mockQ.reject).not.toHaveBeenCalled();
.catch(() => rejected = true) expect(mockNofificationService.error).not.toHaveBeenCalled();
.then(() => {
expect(rejected).toBe(false);
expect(mockNofificationService.error).not.toHaveBeenCalled();
});
}); });
}); });
describe("unsuccessful persistence", function () { describe("unsuccessful persistence", function () {
var sadPromise = {
then: function (callback) {
return asPromise(callback(0), true);
}
};
beforeEach(function () { beforeEach(function () {
mockOpenMCT.objects.save.and.returnValue(Promise.resolve(false)); mockPersistenceService.createObject.and.returnValue(sadPromise);
}); });
it("rejects on falsey persistence result", function () { it("rejects on falsey persistence result", function () {
let rejected = false; persistence.persist();
return persistence.persist() expect(mockQ.reject).toHaveBeenCalled();
.catch(() => rejected = true)
.then(() => {
expect(rejected).toBe(true);
});
}); });
it("notifies user on persistence failure", function () { it("notifies user on persistence failure", function () {
let rejected = false; persistence.persist();
return persistence.persist() expect(mockQ.reject).toHaveBeenCalled();
.catch(() => rejected = true) expect(mockNofificationService.error).toHaveBeenCalled();
.then(() => {
expect(rejected).toBe(true);
expect(mockNofificationService.error).toHaveBeenCalled();
});
}); });
}); });
}); });

View File

@ -40,18 +40,7 @@ define(
} }
MoveAction.prototype = Object.create(AbstractComposeAction.prototype); MoveAction.prototype = Object.create(AbstractComposeAction.prototype);
MoveAction.appliesTo = AbstractComposeAction.appliesTo;
MoveAction.appliesTo = function (context) {
var applicableObject =
context.selectedObject || context.domainObject;
if (applicableObject && applicableObject.model.locked) {
return false;
}
return Boolean(applicableObject &&
applicableObject.hasCapability('context'));
};
return MoveAction; return MoveAction;
} }

View File

@ -216,14 +216,8 @@ define(['zepto', 'objectUtils'], function ($, objectUtils) {
}; };
ImportAsJSONAction.appliesTo = function (context) { ImportAsJSONAction.appliesTo = function (context) {
let domainObject = context.domainObject; return context.domainObject !== undefined &&
context.domainObject.hasCapability("composition");
if (domainObject && domainObject.model.locked) {
return false;
}
return domainObject !== undefined &&
domainObject.hasCapability("composition");
}; };
return ImportAsJSONAction; return ImportAsJSONAction;

View File

@ -0,0 +1,621 @@
{
"header": {
"event": "Allocation failed - JavaScript heap out of memory",
"location": "OnFatalError",
"filename": "report.20200527.134750.93992.001.json",
"dumpEventTime": "2020-05-27T13:47:50Z",
"dumpEventTimeStamp": "1590612470877",
"processId": 93992,
"commandLine": [
"node",
"/Users/dtailor/Desktop/openmct/node_modules/.bin/karma",
"start",
"--single-run"
],
"nodejsVersion": "v11.9.0",
"wordSize": 64,
"componentVersions": {
"node": "11.9.0",
"v8": "7.0.276.38-node.16",
"uv": "1.25.0",
"zlib": "1.2.11",
"brotli": "1.0.7",
"ares": "1.15.0",
"modules": "67",
"nghttp2": "1.34.0",
"napi": "4",
"llhttp": "1.0.1",
"http_parser": "2.8.0",
"openssl": "1.1.1a",
"cldr": "34.0",
"icu": "63.1",
"tz": "2018e",
"unicode": "11.0",
"arch": "x64",
"platform": "darwin",
"release": "node"
},
"osVersion": "Darwin 18.7.0 Darwin Kernel Version 18.7.0: Thu Jan 23 06:52:12 PST 2020; root:xnu-4903.278.25~1/RELEASE_X86_64",
"machine": "Darwin 18.7.0 Darwin Kernel Version 18.7.0: Thu Jan 23 06:52:12 PST 2020; root:xnu-4903.278.25~1/RELEASE_X86_64tailor x86_64"
},
"javascriptStack": {
"message": "No stack.",
"stack": [
"Unavailable."
]
},
"nativeStack": [
" [pc=0x10013090e] report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, v8::Local<v8::String>) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x100063744] node::OnFatalError(char const*, char const*) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1001a8c47] v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1001a8be4] v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005add42] v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005b0273] v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005ac7a8] v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005aa965] v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005b720c] v8::internal::Heap::AllocateRawWithLightRetry(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005b728f] v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x100586484] v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationSpace) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1008389a4] v8::internal::Runtime_AllocateInNewSpace(int, v8::internal::Object**, v8::internal::Isolate*) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x14acfddcfc7d] "
],
"javascriptHeap": {
"totalMemory": 1479229440,
"totalCommittedMemory": 1477309024,
"usedMemory": 1445511032,
"availableMemory": 50296592,
"memoryLimit": 1526909922,
"heapSpaces": {
"read_only_space": {
"memorySize": 524288,
"committedMemory": 42224,
"capacity": 515584,
"used": 33520,
"available": 482064
},
"new_space": {
"memorySize": 4194304,
"committedMemory": 4194288,
"capacity": 2062336,
"used": 59016,
"available": 2003320
},
"old_space": {
"memorySize": 305860608,
"committedMemory": 305138544,
"capacity": 283264904,
"used": 282942208,
"available": 322696
},
"code_space": {
"memorySize": 6291456,
"committedMemory": 5687328,
"capacity": 5237152,
"used": 5237152,
"available": 0
},
"map_space": {
"memorySize": 5255168,
"committedMemory": 5143024,
"capacity": 2523280,
"used": 2523280,
"available": 0
},
"large_object_space": {
"memorySize": 1157103616,
"committedMemory": 1157103616,
"capacity": 1202204368,
"used": 1154715856,
"available": 47488512
},
"new_large_object_space": {
"memorySize": 0,
"committedMemory": 0,
"capacity": 0,
"used": 0,
"available": 0
}
}
},
"resourceUsage": {
"userCpuSeconds": 43.1616,
"kernelCpuSeconds": 43.1616,
"cpuConsumptionPercent": 5.42705e-06,
"maxRss": 1966080000000,
"pageFaults": {
"IORequired": 245,
"IONotRequired": 832598
},
"fsActivity": {
"reads": 0,
"writes": 0
}
},
"libuv": [
],
"environmentVariables": {
"npm_config_save_dev": "",
"npm_config_legacy_bundling": "",
"npm_config_dry_run": "",
"npm_package_devDependencies_markdown_toc": "^0.11.7",
"npm_config_only": "",
"npm_config_browser": "",
"npm_config_viewer": "man",
"npm_config_commit_hooks": "true",
"npm_package_gitHead": "7126abe7ec1d66d3252f3598fbd6bd27217018bc",
"npm_config_also": "",
"npm_package_scripts_otherdoc": "node docs/gendocs.js --in docs/src --out dist/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
"npm_package_devDependencies_minimist": "^1.1.1",
"npm_config_sign_git_commit": "",
"npm_config_rollback": "true",
"npm_package_devDependencies_fast_sass_loader": "1.4.6",
"TERM_PROGRAM": "Apple_Terminal",
"npm_config_usage": "",
"npm_config_audit": "true",
"npm_package_devDependencies_git_rev_sync": "^1.4.0",
"npm_package_devDependencies_file_loader": "^1.1.11",
"npm_package_devDependencies_d3_selection": "1.3.x",
"NODE": "/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node",
"npm_package_homepage": "https://github.com/nasa/openmct#readme",
"INIT_CWD": "/Users/dtailor/Desktop/openmct",
"NVM_CD_FLAGS": "",
"npm_config_globalignorefile": "/Users/dtailor/.nvm/versions/node/v11.9.0/etc/npmignore",
"npm_package_devDependencies_comma_separated_values": "^3.6.4",
"SHELL": "/bin/bash",
"TERM": "xterm-256color",
"npm_config_init_author_url": "",
"npm_config_shell": "/bin/bash",
"npm_config_maxsockets": "50",
"npm_package_devDependencies_vue_template_compiler": "2.5.6",
"npm_package_devDependencies_style_loader": "^1.0.1",
"npm_package_devDependencies_moment_duration_format": "^2.2.2",
"npm_config_parseable": "",
"npm_config_shrinkwrap": "true",
"npm_config_metrics_registry": "https://registry.npmjs.org/",
"TMPDIR": "/var/folders/ks/ytghmh9x4lj3cchr5km5lhkcb7v9y2/T/",
"npm_config_timing": "",
"npm_config_init_license": "ISC",
"npm_package_scripts_lint": "eslint platform example src --ext .js,.vue openmct.js",
"npm_package_devDependencies_d3_array": "1.2.x",
"Apple_PubSub_Socket_Render": "/private/tmp/com.apple.launchd.PsV6Dfq4Tm/Render",
"npm_config_if_present": "",
"npm_package_devDependencies_concurrently": "^3.6.1",
"TERM_PROGRAM_VERSION": "421.2",
"npm_package_scripts_jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
"npm_config_sign_git_tag": "",
"npm_config_init_author_email": "",
"npm_config_cache_max": "Infinity",
"npm_config_preid": "",
"npm_config_long": "",
"npm_config_local_address": "",
"npm_config_cert": "",
"npm_config_git_tag_version": "true",
"npm_package_devDependencies_exports_loader": "^0.7.0",
"TERM_SESSION_ID": "0630D2FA-BAC2-48D3-A21D-9AB58A79FB14",
"npm_config_noproxy": "",
"npm_config_registry": "https://registry.npmjs.org/",
"npm_config_fetch_retries": "2",
"npm_package_private": "true",
"npm_package_devDependencies_karma_jasmine": "^1.1.2",
"npm_package_repository_url": "git+https://github.com/nasa/openmct.git",
"npm_config_versions": "",
"npm_config_key": "",
"npm_config_message": "%s",
"npm_package_readmeFilename": "README.md",
"npm_package_devDependencies_painterro": "^0.2.65",
"npm_package_scripts_verify": "concurrently 'npm:test' 'npm:lint'",
"npm_package_devDependencies_webpack": "^4.16.2",
"npm_package_devDependencies_eventemitter3": "^1.2.0",
"npm_package_description": "The Open MCT core platform",
"USER": "dtailor",
"NVM_DIR": "/Users/dtailor/.nvm",
"npm_package_license": "Apache-2.0",
"npm_package_scripts_build_dev": "webpack",
"npm_package_devDependencies_webpack_cli": "^3.1.0",
"npm_package_devDependencies_location_bar": "^3.0.1",
"npm_package_devDependencies_jasmine_core": "^3.1.0",
"npm_config_globalconfig": "/Users/dtailor/.nvm/versions/node/v11.9.0/etc/npmrc",
"npm_package_devDependencies_karma": "^2.0.3",
"npm_config_prefer_online": "",
"npm_config_always_auth": "",
"npm_config_logs_max": "10",
"npm_package_devDependencies_angular": "1.7.9",
"SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.JH8E4KgH06/Listeners",
"npm_package_devDependencies_request": "^2.69.0",
"npm_package_devDependencies_eslint": "5.2.0",
"__CF_USER_TEXT_ENCODING": "0x167DA7C2:0x0:0x0",
"npm_execpath": "/Users/dtailor/.nvm/versions/node/v11.9.0/lib/node_modules/npm/bin/npm-cli.js",
"npm_config_global_style": "",
"npm_config_cache_lock_retries": "10",
"npm_config_cafile": "",
"npm_config_update_notifier": "true",
"npm_package_scripts_test_debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"npm_package_devDependencies_glob": ">= 3.0.0",
"npm_config_heading": "npm",
"npm_config_audit_level": "low",
"npm_package_devDependencies_mini_css_extract_plugin": "^0.4.1",
"npm_package_devDependencies_copy_webpack_plugin": "^4.5.2",
"npm_config_read_only": "",
"npm_config_offline": "",
"npm_config_searchlimit": "20",
"npm_config_fetch_retry_mintimeout": "10000",
"npm_package_devDependencies_webpack_dev_middleware": "^3.1.3",
"npm_config_json": "",
"npm_config_access": "",
"npm_config_argv": "{\"remain\":[],\"cooked\":[\"run\",\"test\"],\"original\":[\"run\",\"test\"]}",
"npm_package_scripts_lint_fix": "eslint platform example src --ext .js,.vue openmct.js --fix",
"npm_package_devDependencies_uuid": "^3.3.3",
"npm_package_devDependencies_karma_coverage": "^1.1.2",
"PATH": "/Users/dtailor/.nvm/versions/node/v11.9.0/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/dtailor/Desktop/openmct/node_modules/.bin:/Users/dtailor/.nvm/versions/node/v11.9.0/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/dtailor/Desktop/openmct/node_modules/.bin:/Users/dtailor/.nvm/versions/node/v11.9.0/bin:/Users/dtailor/.homebrew/bin:/Users/dtailor/local/bin:/Users/dtailor/.homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Users/dtailor/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/opt/local/bin:/Users/dtailor/.homebrew/bin:/Users/dtailor/.homebrew/bin:/Users/dtailor/.homebrew/bin:/Users/dtailor/local/bin:/Users/dtailor/.homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin",
"npm_config_allow_same_version": "",
"npm_config_https_proxy": "",
"npm_config_engine_strict": "",
"npm_config_description": "true",
"npm_package_devDependencies_html2canvas": "^1.0.0-alpha.12",
"_": "/Users/dtailor/Desktop/openmct/node_modules/.bin/karma",
"npm_config_userconfig": "/Users/dtailor/.npmrc",
"npm_config_init_module": "/Users/dtailor/.npm-init.js",
"npm_package_author": "",
"npm_package_devDependencies_karma_chrome_launcher": "^2.2.0",
"npm_package_devDependencies_d3_scale": "1.0.x",
"npm_config_cidr": "",
"npm_package_devDependencies_printj": "^1.2.1",
"PWD": "/Users/dtailor/Desktop/openmct",
"npm_config_user": "377333698",
"npm_config_node_version": "11.9.0",
"npm_package_bugs_url": "https://github.com/nasa/openmct/issues",
"npm_package_scripts_test_watch": "karma start --no-single-run",
"npm_lifecycle_event": "test",
"npm_package_devDependencies_v8_compile_cache": "^1.1.0",
"npm_config_ignore_prepublish": "",
"npm_config_save": "true",
"npm_config_editor": "vi",
"npm_config_auth_type": "legacy",
"npm_package_repository_type": "git",
"npm_package_devDependencies_vue": "2.5.6",
"npm_package_devDependencies_marked": "^0.3.5",
"npm_package_devDependencies_angular_route": "1.4.14",
"npm_package_name": "openmct",
"LANG": "en_US.UTF-8",
"npm_config_script_shell": "",
"npm_config_tag": "latest",
"npm_config_global": "",
"npm_config_progress": "true",
"npm_package_scripts_start": "node app.js",
"npm_package_devDependencies_karma_coverage_istanbul_reporter": "^2.1.1",
"npm_config_ham_it_up": "",
"npm_config_searchstaleness": "900",
"npm_config_optional": "true",
"npm_package_scripts_docs": "npm run jsdoc ; npm run otherdoc",
"npm_package_devDependencies_istanbul_instrumenter_loader": "^3.0.1",
"XPC_FLAGS": "0x0",
"npm_config_save_prod": "",
"npm_config_force": "",
"npm_config_bin_links": "true",
"npm_package_devDependencies_moment": "2.25.3",
"npm_package_devDependencies_karma_webpack": "^3.0.0",
"npm_package_devDependencies_express": "^4.13.1",
"npm_config_searchopts": "",
"npm_package_devDependencies_d3_time": "1.0.x",
"FORCE_COLOR": "2",
"npm_config_node_gyp": "/Users/dtailor/.nvm/versions/node/v11.9.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js",
"npm_config_depth": "Infinity",
"npm_package_scripts_build_prod": "cross-env NODE_ENV=production webpack",
"npm_config_sso_poll_frequency": "500",
"npm_config_rebuild_bundle": "true",
"npm_package_version": "1.0.0-snapshot",
"XPC_SERVICE_NAME": "0",
"npm_config_unicode": "true",
"npm_package_devDependencies_jsdoc": "^3.3.2",
"SHLVL": "4",
"HOME": "/Users/dtailor",
"npm_config_fetch_retry_maxtimeout": "60000",
"npm_package_scripts_test": "karma start --single-run",
"npm_package_devDependencies_zepto": "^1.2.0",
"npm_package_devDependencies_eslint_plugin_vue": "^6.0.0",
"npm_config_ca": "",
"npm_config_tag_version_prefix": "v",
"npm_config_strict_ssl": "true",
"npm_config_sso_type": "oauth",
"npm_config_scripts_prepend_node_path": "warn-only",
"npm_config_save_prefix": "^",
"npm_config_loglevel": "notice",
"npm_package_devDependencies_lodash": "^3.10.1",
"npm_package_devDependencies_karma_cli": "^1.0.1",
"npm_package_devDependencies_d3_color": "1.0.x",
"npm_config_save_exact": "",
"npm_config_dev": "",
"npm_config_group": "1286109195",
"npm_config_fetch_retry_factor": "10",
"npm_package_devDependencies_webpack_hot_middleware": "^2.22.3",
"npm_package_devDependencies_cross_env": "^6.0.3",
"npm_package_devDependencies_babel_eslint": "8.2.6",
"HOMEBREW_PREFIX": "/Users/dtailor/.homebrew",
"npm_config_version": "",
"npm_config_prefer_offline": "",
"npm_config_cache_lock_stale": "60000",
"npm_config_otp": "",
"npm_config_cache_min": "10",
"npm_package_devDependencies_vue_loader": "^15.2.6",
"npm_config_searchexclude": "",
"npm_config_cache": "/Users/dtailor/.npm",
"npm_package_scripts_test_coverage": "./scripts/test-coverage.sh",
"npm_package_devDependencies_d3_interpolate": "1.1.x",
"npm_package_devDependencies_d3_format": "1.2.x",
"LOGNAME": "dtailor",
"npm_lifecycle_script": "karma start --single-run",
"npm_config_color": "true",
"npm_package_devDependencies_node_bourbon": "^4.2.3",
"npm_package_devDependencies_karma_sourcemap_loader": "^0.3.7",
"npm_package_devDependencies_karma_html_reporter": "^0.2.7",
"npm_config_proxy": "",
"npm_config_package_lock": "true",
"npm_package_devDependencies_d3_time_format": "2.1.x",
"npm_package_devDependencies_d3_axis": "1.0.x",
"npm_config_package_lock_only": "",
"npm_package_devDependencies_moment_timezone": "0.5.28",
"npm_config_save_optional": "",
"NVM_BIN": "/Users/dtailor/.nvm/versions/node/v11.9.0/bin",
"npm_config_ignore_scripts": "",
"npm_config_user_agent": "npm/6.5.0 node/v11.9.0 darwin x64",
"npm_package_devDependencies_imports_loader": "^0.8.0",
"npm_package_devDependencies_file_saver": "^1.3.8",
"npm_config_cache_lock_wait": "10000",
"npm_config_production": "",
"npm_package_scripts_build_watch": "webpack --watch",
"DISPLAY": "/private/tmp/com.apple.launchd.E3N8oC6RMf/org.macosforge.xquartz:0",
"npm_config_send_metrics": "",
"npm_config_save_bundle": "",
"npm_package_scripts_prepare": "npm run build:prod",
"npm_config_node_options": "",
"npm_config_umask": "0022",
"npm_config_init_version": "1.0.0",
"npm_package_devDependencies_split": "^1.0.0",
"npm_package_devDependencies_raw_loader": "^0.5.1",
"npm_config_init_author_name": "",
"npm_config_git": "git",
"npm_config_scope": "",
"npm_package_scripts_clean": "rm -rf ./dist",
"npm_package_devDependencies_node_sass": "^4.9.2",
"npm_package_devDependencies_css_loader": "^1.0.0",
"DISABLE_UPDATE_CHECK": "1",
"npm_config_onload_script": "",
"npm_config_unsafe_perm": "true",
"npm_config_tmp": "/var/folders/ks/ytghmh9x4lj3cchr5km5lhkcb7v9y2/T",
"npm_package_devDependencies_d3_collection": "1.0.x",
"npm_node_execpath": "/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node",
"npm_config_link": "",
"npm_config_prefix": "/Users/dtailor/.nvm/versions/node/v11.9.0",
"npm_package_devDependencies_html_loader": "^0.5.5"
},
"userLimits": {
"core_file_size_blocks": {
"soft": 0,
"hard": "unlimited"
},
"data_seg_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"file_size_blocks": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_locked_memory_bytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_memory_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"open_files": {
"soft": 24576,
"hard": "unlimited"
},
"stack_size_bytes": {
"soft": 8388608,
"hard": 67104768
},
"cpu_time_seconds": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_user_processes": {
"soft": 1418,
"hard": 2128
},
"virtual_memory_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
}
},
"sharedObjects": [
"/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node",
"/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
"/usr/lib/libSystem.B.dylib",
"/usr/lib/libc++.1.dylib",
"/usr/lib/libobjc.A.dylib",
"/usr/lib/libDiagnosticMessagesClient.dylib",
"/usr/lib/libicucore.A.dylib",
"/usr/lib/libz.1.dylib",
"/usr/lib/libc++abi.dylib",
"/usr/lib/system/libcache.dylib",
"/usr/lib/system/libcommonCrypto.dylib",
"/usr/lib/system/libcompiler_rt.dylib",
"/usr/lib/system/libcopyfile.dylib",
"/usr/lib/system/libcorecrypto.dylib",
"/usr/lib/system/libdispatch.dylib",
"/usr/lib/system/libdyld.dylib",
"/usr/lib/system/libkeymgr.dylib",
"/usr/lib/system/liblaunch.dylib",
"/usr/lib/system/libmacho.dylib",
"/usr/lib/system/libquarantine.dylib",
"/usr/lib/system/libremovefile.dylib",
"/usr/lib/system/libsystem_asl.dylib",
"/usr/lib/system/libsystem_blocks.dylib",
"/usr/lib/system/libsystem_c.dylib",
"/usr/lib/system/libsystem_configuration.dylib",
"/usr/lib/system/libsystem_coreservices.dylib",
"/usr/lib/system/libsystem_darwin.dylib",
"/usr/lib/system/libsystem_dnssd.dylib",
"/usr/lib/system/libsystem_info.dylib",
"/usr/lib/system/libsystem_m.dylib",
"/usr/lib/system/libsystem_malloc.dylib",
"/usr/lib/system/libsystem_networkextension.dylib",
"/usr/lib/system/libsystem_notify.dylib",
"/usr/lib/system/libsystem_sandbox.dylib",
"/usr/lib/system/libsystem_secinit.dylib",
"/usr/lib/system/libsystem_kernel.dylib",
"/usr/lib/system/libsystem_platform.dylib",
"/usr/lib/system/libsystem_pthread.dylib",
"/usr/lib/system/libsystem_symptoms.dylib",
"/usr/lib/system/libsystem_trace.dylib",
"/usr/lib/system/libunwind.dylib",
"/usr/lib/system/libxpc.dylib",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices",
"/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics",
"/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO",
"/System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/LangAnalysis.framework/Versions/A/LangAnalysis",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis",
"/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight",
"/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface",
"/usr/lib/libxml2.2.dylib",
"/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate",
"/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation",
"/usr/lib/libcompression.dylib",
"/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration",
"/System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay",
"/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit",
"/System/Library/Frameworks/Metal.framework/Versions/A/Metal",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders",
"/System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport",
"/System/Library/Frameworks/Security.framework/Versions/A/Security",
"/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore",
"/usr/lib/libbsm.0.dylib",
"/usr/lib/liblzma.5.dylib",
"/usr/lib/libauto.dylib",
"/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration",
"/usr/lib/libarchive.2.dylib",
"/usr/lib/liblangid.dylib",
"/usr/lib/libCRFSuite.dylib",
"/usr/lib/libenergytrace.dylib",
"/usr/lib/system/libkxld.dylib",
"/System/Library/PrivateFrameworks/AppleFSCompression.framework/Versions/A/AppleFSCompression",
"/usr/lib/libOpenScriptingUtil.dylib",
"/usr/lib/libcoretls.dylib",
"/usr/lib/libcoretls_cfhelpers.dylib",
"/usr/lib/libpam.2.dylib",
"/usr/lib/libsqlite3.dylib",
"/usr/lib/libxar.1.dylib",
"/usr/lib/libbz2.1.0.dylib",
"/usr/lib/libnetwork.dylib",
"/usr/lib/libapple_nghttp2.dylib",
"/usr/lib/libpcap.A.dylib",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList",
"/System/Library/Frameworks/NetFS.framework/Versions/A/NetFS",
"/System/Library/PrivateFrameworks/NetAuth.framework/Versions/A/NetAuth",
"/System/Library/PrivateFrameworks/login.framework/Versions/A/Frameworks/loginsupport.framework/Versions/A/loginsupport",
"/System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC",
"/System/Library/PrivateFrameworks/CoreNLP.framework/Versions/A/CoreNLP",
"/System/Library/PrivateFrameworks/MetadataUtilities.framework/Versions/A/MetadataUtilities",
"/usr/lib/libmecabra.dylib",
"/usr/lib/libmecab.1.0.0.dylib",
"/usr/lib/libgermantok.dylib",
"/usr/lib/libThaiTokenizer.dylib",
"/usr/lib/libChineseTokenizer.dylib",
"/usr/lib/libiconv.2.dylib",
"/usr/lib/libcharset.1.dylib",
"/System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling",
"/System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji",
"/System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon",
"/System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData",
"/usr/lib/libcmph.dylib",
"/System/Library/Frameworks/CoreData.framework/Versions/A/CoreData",
"/System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory",
"/System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS",
"/usr/lib/libutil.dylib",
"/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement",
"/System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement",
"/usr/lib/libxslt.1.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib",
"/System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler",
"/System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator",
"/System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment",
"/System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSCore.framework/Versions/A/MPSCore",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSImage.framework/Versions/A/MPSImage",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSRayIntersector.framework/Versions/A/MPSRayIntersector",
"/System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools",
"/System/Library/PrivateFrameworks/AggregateDictionary.framework/Versions/A/AggregateDictionary",
"/usr/lib/libMobileGestalt.dylib",
"/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage",
"/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL",
"/System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer",
"/System/Library/PrivateFrameworks/FaceCore.framework/Versions/A/FaceCore",
"/System/Library/Frameworks/OpenCL.framework/Versions/A/OpenCL",
"/usr/lib/libFosl_dynamic.dylib",
"/System/Library/PrivateFrameworks/OTSVG.framework/Versions/A/OTSVG",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontParser.dylib",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib",
"/System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib",
"/usr/lib/libcups.2.dylib",
"/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos",
"/System/Library/Frameworks/GSS.framework/Versions/A/GSS",
"/usr/lib/libresolv.9.dylib",
"/System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal",
"/usr/lib/libheimdal-asn1.dylib",
"/System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory",
"/System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth",
"/System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation",
"/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio",
"/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox",
"/System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce",
"/System/Library/PrivateFrameworks/AssertionServices.framework/Versions/A/AssertionServices",
"/System/Library/PrivateFrameworks/BaseBoard.framework/Versions/A/BaseBoard"
]
}

View File

@ -268,7 +268,6 @@ define([
this.install(this.plugins.ConditionWidget()); this.install(this.plugins.ConditionWidget());
this.install(this.plugins.URLTimeSettingsSynchronizer()); this.install(this.plugins.URLTimeSettingsSynchronizer());
this.install(this.plugins.NotificationIndicator()); this.install(this.plugins.NotificationIndicator());
this.install(this.plugins.NewFolderAction());
} }
MCT.prototype = Object.create(EventEmitter.prototype); MCT.prototype = Object.create(EventEmitter.prototype);

View File

@ -25,11 +25,10 @@ define([
], function ( ], function (
utils utils
) { ) {
function ObjectServiceProvider(eventEmitter, objectService, instantiate, topic, $injector) { function ObjectServiceProvider(eventEmitter, objectService, instantiate, topic) {
this.eventEmitter = eventEmitter; this.eventEmitter = eventEmitter;
this.objectService = objectService; this.objectService = objectService;
this.instantiate = instantiate; this.instantiate = instantiate;
this.$injector = $injector;
this.generalTopic = topic('mutation'); this.generalTopic = topic('mutation');
this.bridgeEventBuses(); this.bridgeEventBuses();
@ -69,53 +68,16 @@ define([
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation); removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
}; };
ObjectServiceProvider.prototype.create = async function (object) { ObjectServiceProvider.prototype.save = function (object) {
let model = utils.toOldFormat(object); var key = object.key;
return this.getPersistenceService().createObject( return object.getCapability('persistence')
this.getSpace(utils.makeKeyString(object.identifier)), .persist()
object.identifier.key, .then(function () {
model return utils.toNewFormat(object, key);
); });
}
ObjectServiceProvider.prototype.update = async function (object) {
let model = utils.toOldFormat(object);
return this.getPersistenceService().updateObject(
this.getSpace(utils.makeKeyString(object.identifier)),
object.identifier.key,
model
);
}
/**
* Get the space in which this domain object is persisted;
* this is useful when, for example, decided which space a
* newly-created domain object should be persisted to (by
* default, this should be the space of its containing
* object.)
*
* @returns {string} the name of the space which should
* be used to persist this object
*/
ObjectServiceProvider.prototype.getSpace = function (keystring) {
return this.getIdentifierService().parse(keystring).getSpace();
}; };
ObjectServiceProvider.prototype.getIdentifierService = function () {
if (this.identifierService === undefined) {
this.identifierService = this.$injector.get('identifierService');
}
return this.identifierService;
};
ObjectServiceProvider.prototype.getPersistenceService = function () {
if (this.persistenceService === undefined) {
this.persistenceService = this.$injector.get('persistenceService');
}
return this.persistenceService;
}
ObjectServiceProvider.prototype.delete = function (object) { ObjectServiceProvider.prototype.delete = function (object) {
// TODO! // TODO!
}; };
@ -156,8 +118,7 @@ define([
eventEmitter, eventEmitter,
objectService, objectService,
instantiate, instantiate,
topic, topic
openmct.$injector
) )
); );

View File

@ -101,25 +101,14 @@ define([
*/ */
/** /**
* Create the given domain object in the corresponding persistence store * Save this domain object in its current state.
* *
* @method create * @method save
* @memberof module:openmct.ObjectProvider# * @memberof module:openmct.ObjectProvider#
* @param {module:openmct.DomainObject} domainObject the domain object to * @param {module:openmct.DomainObject} domainObject the domain object to
* create * save
* @returns {Promise} a promise which will resolve when the domain object * @returns {Promise} a promise which will resolve when the domain object
* has been created, or be rejected if it cannot be saved * has been saved, or be rejected if it cannot be saved
*/
/**
* Update this domain object in its persistence store
*
* @method update
* @memberof module:openmct.ObjectProvider#
* @param {module:openmct.DomainObject} domainObject the domain object to
* update
* @returns {Promise} a promise which will resolve when the domain object
* has been updated, or be rejected if it cannot be saved
*/ */
/** /**
@ -172,41 +161,8 @@ define([
throw new Error('Delete not implemented'); throw new Error('Delete not implemented');
}; };
ObjectAPI.prototype.isPersistable = function (domainObject) { ObjectAPI.prototype.save = function () {
let provider = this.getProvider(domainObject.identifier); throw new Error('Save not implemented');
return provider !== undefined &&
provider.create !== undefined &&
provider.update !== undefined;
}
/**
* Save this domain object in its current state. EXPERIMENTAL
*
* @private
* @memberof module:openmct.ObjectAPI#
* @param {module:openmct.DomainObject} domainObject the domain object to
* save
* @returns {Promise} a promise which will resolve when the domain object
* has been saved, or be rejected if it cannot be saved
*/
ObjectAPI.prototype.save = function (domainObject) {
let provider = this.getProvider(domainObject.identifier);
let result;
if (!this.isPersistable(domainObject)) {
result = Promise.reject('Object provider does not support saving');
} else if (hasAlreadyBeenPersisted(domainObject)) {
result = Promise.resolve(true);
} else {
if (domainObject.persisted === undefined) {
this.mutate(domainObject, 'persisted', domainObject.modified);
result = provider.create(domainObject);
} else {
this.mutate(domainObject, 'persisted', domainObject.modified);
result = provider.update(domainObject);
}
}
return result;
}; };
/** /**
@ -320,9 +276,5 @@ define([
* @memberof module:openmct * @memberof module:openmct
*/ */
function hasAlreadyBeenPersisted(domainObject) {
return domainObject.persisted !== undefined &&
domainObject.persisted === domainObject.modified;
}
return ObjectAPI; return ObjectAPI;
}); });

View File

@ -1,60 +0,0 @@
import ObjectAPI from './ObjectAPI.js';
describe("The Object API", () => {
let objectAPI;
let mockDomainObject;
const TEST_NAMESPACE = "test-namespace";
const FIFTEEN_MINUTES = 15 * 60 * 1000;
beforeEach(() => {
objectAPI = new ObjectAPI();
mockDomainObject = {
identifier: {
namespace: TEST_NAMESPACE,
key: "test-key"
},
name: "test object",
type: "test-type"
};
})
describe("The save function", () => {
it("Rejects if no provider available", () => {
let rejected = false;
return objectAPI.save(mockDomainObject)
.catch(() => rejected = true)
.then(() => expect(rejected).toBe(true));
});
describe("when a provider is available", () => {
let mockProvider;
beforeEach(() => {
mockProvider = jasmine.createSpyObj("mock provider", [
"create",
"update"
]);
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
})
it("Calls 'create' on provider if object is new", () => {
objectAPI.save(mockDomainObject);
expect(mockProvider.create).toHaveBeenCalled();
expect(mockProvider.update).not.toHaveBeenCalled();
});
it("Calls 'update' on provider if object is not new", () => {
mockDomainObject.persisted = Date.now() - FIFTEEN_MINUTES;
mockDomainObject.modified = Date.now();
objectAPI.save(mockDomainObject);
expect(mockProvider.create).not.toHaveBeenCalled();
expect(mockProvider.update).toHaveBeenCalled();
});
it("Does not persist if the object is unchanged", () => {
mockDomainObject.persisted =
mockDomainObject.modified = Date.now();
objectAPI.save(mockDomainObject);
expect(mockProvider.create).not.toHaveBeenCalled();
expect(mockProvider.update).not.toHaveBeenCalled();
});
});
})
});

View File

@ -334,8 +334,8 @@ define([
}); });
if (subscriber.callbacks.length === 0) { if (subscriber.callbacks.length === 0) {
subscriber.unsubscribe(); subscriber.unsubscribe();
delete this.subscribeCache[keyString];
} }
delete this.subscribeCache[keyString];
}.bind(this); }.bind(this);
}; };

View File

@ -156,29 +156,6 @@ define([
expect(callbacktwo).not.toHaveBeenCalledWith('anotherValue'); expect(callbacktwo).not.toHaveBeenCalledWith('anotherValue');
}); });
it('only deletes subscription cache when there are no more subscribers', function () {
var unsubFunc = jasmine.createSpy('unsubscribe');
telemetryProvider.subscribe.and.returnValue(unsubFunc);
telemetryProvider.supportsSubscribe.and.returnValue(true);
telemetryAPI.addProvider(telemetryProvider);
var callback = jasmine.createSpy('callback');
var callbacktwo = jasmine.createSpy('callback two');
var callbackThree = jasmine.createSpy('callback three');
var unsubscribe = telemetryAPI.subscribe(domainObject, callback);
var unsubscribeTwo = telemetryAPI.subscribe(domainObject, callbacktwo);
expect(telemetryProvider.subscribe.calls.count()).toBe(1);
unsubscribe();
var unsubscribeThree = telemetryAPI.subscribe(domainObject, callbackThree);
// Regression test for where subscription cache was deleted on each unsubscribe, resulting in
// superfluous additional subscriptions. If the subscription cache is being deleted on each unsubscribe,
// then a subsequent subscribe will result in a new subscription at the provider.
expect(telemetryProvider.subscribe.calls.count()).toBe(1);
unsubscribeTwo();
unsubscribeThree();
});
it('does subscribe/unsubscribe', function () { it('does subscribe/unsubscribe', function () {
var unsubFunc = jasmine.createSpy('unsubscribe'); var unsubFunc = jasmine.createSpy('unsubscribe');
telemetryProvider.subscribe.and.returnValue(unsubFunc); telemetryProvider.subscribe.and.returnValue(unsubFunc);

View File

@ -20,13 +20,20 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
export default function ladTableCompositionPolicy(openmct) { export default class LADTableCompositionPolicy {
return function (parent, child) {
constructor(openmct) {
this.openmct = openmct;
return this.allow.bind(this);
}
allow(parent, child) {
if(parent.type === 'LadTable') { if(parent.type === 'LadTable') {
return openmct.telemetry.isTelemetryObject(child); return this.openmct.telemetry.isTelemetryObject(child);
} else if(parent.type === 'LadTableSet') { } else if(parent.type === 'LadTableSet') {
return child.type === 'LadTable'; return child.type === 'LadTable';
} }
return true; return true;
} }
} }

View File

@ -64,7 +64,7 @@ export default {
}, },
computed: { computed: {
formattedTimestamp() { formattedTimestamp() {
return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---'; return this.timestamp !== undefined ? this.formats[this.timestampKey].format(this.timestamp) : '---';
} }
}, },
mounted() { mounted() {
@ -110,11 +110,11 @@ export default {
}, },
methods: { methods: {
updateValues(datum) { updateValues(datum) {
let newTimestamp = this.getParsedTimestamp(datum), let newTimestamp = this.formats[this.timestampKey].parse(datum),
limit; limit;
if(this.shouldUpdate(newTimestamp)) { if(this.shouldUpdate(newTimestamp)) {
this.timestamp = newTimestamp; this.timestamp = this.formats[this.timestampKey].parse(datum);
this.value = this.formats[this.valueKey].format(datum); this.value = this.formats[this.valueKey].format(datum);
limit = this.limitEvaluator.evaluate(datum, this.valueMetadata); limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) { if (limit) {
@ -125,12 +125,9 @@ export default {
} }
}, },
shouldUpdate(newTimestamp) { shouldUpdate(newTimestamp) {
let newTimestampInBounds = this.inBounds(newTimestamp), return (this.timestamp === undefined) ||
noExistingTimestamp = this.timestamp === undefined, (this.inBounds(newTimestamp) &&
newTimestampIsLatest = newTimestamp > this.timestamp; newTimestamp > this.timestamp);
return newTimestampInBounds &&
(noExistingTimestamp || newTimestampIsLatest);
}, },
requestHistory() { requestHistory() {
this.openmct this.openmct
@ -149,7 +146,6 @@ export default {
updateBounds(bounds, isTick) { updateBounds(bounds, isTick) {
this.bounds = bounds; this.bounds = bounds;
if(!isTick) { if(!isTick) {
this.resetValues();
this.requestHistory(); this.requestHistory();
} }
}, },
@ -157,34 +153,13 @@ export default {
return timestamp >= this.bounds.start && timestamp <= this.bounds.end; return timestamp >= this.bounds.start && timestamp <= this.bounds.end;
}, },
updateTimeSystem(timeSystem) { updateTimeSystem(timeSystem) {
this.resetValues(); this.value = '---';
this.timestamp = '---';
this.valueClass = '';
this.timestampKey = timeSystem.key; this.timestampKey = timeSystem.key;
}, },
showContextMenu(event) { showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS); this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
},
resetValues() {
this.value = '---';
this.timestamp = undefined;
this.valueClass = '';
},
getParsedTimestamp(timestamp) {
if(this.timeSystemFormat()) {
return this.formats[this.timestampKey].parse(timestamp);
}
},
getFormattedTimestamp(timestamp) {
if(this.timeSystemFormat()) {
return this.formats[this.timestampKey].format(timestamp);
}
},
timeSystemFormat() {
if(this.formats[this.timestampKey]) {
return true;
} else {
console.warn(`No formatter for ${this.timestampKey} time system for ${this.domainObject.name}.`);
return false;
}
} }
} }
} }

View File

@ -21,7 +21,7 @@
*****************************************************************************/ *****************************************************************************/
import LADTableViewProvider from './LADTableViewProvider'; import LADTableViewProvider from './LADTableViewProvider';
import LADTableSetViewProvider from './LADTableSetViewProvider'; import LADTableSetViewProvider from './LADTableSetViewProvider';
import ladTableCompositionPolicy from './LADTableCompositionPolicy'; import LADTableCompositionPolicy from './LADTableCompositionPolicy';
export default function plugin() { export default function plugin() {
return function install(openmct) { return function install(openmct) {
@ -49,6 +49,6 @@ export default function plugin() {
} }
}); });
openmct.composition.addPolicy(ladTableCompositionPolicy(openmct)); openmct.composition.addPolicy(new LADTableCompositionPolicy(openmct));
}; };
} }

View File

@ -24,7 +24,7 @@ import {
setAllSearchParams setAllSearchParams
} from 'utils/openmctLocation'; } from 'utils/openmctLocation';
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets']; const TIME_EVENTS = ['bounds', 'timeSystem', 'clock', 'clockOffsets'];
const SEARCH_MODE = 'tc.mode'; const SEARCH_MODE = 'tc.mode';
const SEARCH_TIME_SYSTEM = 'tc.timeSystem'; const SEARCH_TIME_SYSTEM = 'tc.timeSystem';
const SEARCH_START_BOUND = 'tc.startBound'; const SEARCH_START_BOUND = 'tc.startBound';
@ -42,7 +42,6 @@ export default class URLTimeSettingsSynchronizer {
this.destroy = this.destroy.bind(this); this.destroy = this.destroy.bind(this);
this.updateTimeSettings = this.updateTimeSettings.bind(this); this.updateTimeSettings = this.updateTimeSettings.bind(this);
this.setUrlFromTimeApi = this.setUrlFromTimeApi.bind(this); this.setUrlFromTimeApi = this.setUrlFromTimeApi.bind(this);
this.updateBounds = this.updateBounds.bind(this);
openmct.on('start', this.initialize); openmct.on('start', this.initialize);
openmct.on('destroy', this.destroy); openmct.on('destroy', this.destroy);
@ -55,7 +54,7 @@ export default class URLTimeSettingsSynchronizer {
TIME_EVENTS.forEach(event => { TIME_EVENTS.forEach(event => {
this.openmct.time.on(event, this.setUrlFromTimeApi); this.openmct.time.on(event, this.setUrlFromTimeApi);
}); });
this.openmct.time.on('bounds', this.updateBounds);
} }
destroy() { destroy() {
@ -66,7 +65,6 @@ export default class URLTimeSettingsSynchronizer {
TIME_EVENTS.forEach(event => { TIME_EVENTS.forEach(event => {
this.openmct.time.off(event, this.setUrlFromTimeApi); this.openmct.time.off(event, this.setUrlFromTimeApi);
}); });
this.openmct.time.on('bounds', this.updateBounds);
} }
updateTimeSettings() { updateTimeSettings() {
@ -74,6 +72,7 @@ export default class URLTimeSettingsSynchronizer {
if (!this.isUrlUpdateInProgress) { if (!this.isUrlUpdateInProgress) {
let timeParameters = this.parseParametersFromUrl(); let timeParameters = this.parseParametersFromUrl();
if (this.areTimeParametersValid(timeParameters)) { if (this.areTimeParametersValid(timeParameters)) {
this.setTimeApiFromUrl(timeParameters); this.setTimeApiFromUrl(timeParameters);
} else { } else {
@ -139,12 +138,6 @@ export default class URLTimeSettingsSynchronizer {
} }
} }
updateBounds(bounds, isTick) {
if (!isTick) {
this.setUrlFromTimeApi();
}
}
setUrlFromTimeApi() { setUrlFromTimeApi() {
let searchParams = getAllSearchParams(); let searchParams = getAllSearchParams();
let clock = this.openmct.time.clock(); let clock = this.openmct.time.clock();

View File

@ -29,28 +29,24 @@ define([
ClearDataAction, ClearDataAction,
Vue Vue
) { ) {
return function plugin(appliesToObjects, options = {indicator: true}) { return function plugin(appliesToObjects) {
let installIndicator = options.indicator;
appliesToObjects = appliesToObjects || []; appliesToObjects = appliesToObjects || [];
return function install(openmct) { return function install(openmct) {
if (installIndicator) { let component = new Vue ({
let component = new Vue ({ provide: {
provide: { openmct
openmct },
}, components: {
components: { GlobalClearIndicator: GlobaClearIndicator.default
GlobalClearIndicator: GlobaClearIndicator.default },
}, template: '<GlobalClearIndicator></GlobalClearIndicator>'
template: '<GlobalClearIndicator></GlobalClearIndicator>' }),
}), indicator = {
indicator = { element: component.$mount().$el
element: component.$mount().$el };
};
openmct.indicators.add(indicator); openmct.indicators.add(indicator);
}
openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects)); openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects));
}; };

View File

@ -150,7 +150,6 @@ export default class Condition extends EventEmitter {
criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct); criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct);
} }
criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
if (!this.criteria) { if (!this.criteria) {
this.criteria = []; this.criteria = [];
} }
@ -179,12 +178,10 @@ export default class Condition extends EventEmitter {
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration); const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct); let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
newCriterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
let criterion = found.item; let criterion = found.item;
criterion.unsubscribe(); criterion.unsubscribe();
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.off('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
this.criteria.splice(found.index, 1, newCriterion); this.criteria.splice(found.index, 1, newCriterion);
this.updateDescription(); this.updateDescription();
} }
@ -197,9 +194,6 @@ export default class Condition extends EventEmitter {
criterion.off('criterionUpdated', (obj) => { criterion.off('criterionUpdated', (obj) => {
this.handleCriterionUpdated(obj); this.handleCriterionUpdated(obj);
}); });
criterion.off('telemetryIsStale', (obj) => {
this.handleStaleCriterion(obj);
});
criterion.destroy(); criterion.destroy();
this.criteria.splice(found.index, 1); this.criteria.splice(found.index, 1);
this.updateDescription(); this.updateDescription();
@ -217,18 +211,6 @@ export default class Condition extends EventEmitter {
} }
} }
handleStaleCriterion(updatedCriterion) {
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
let latestTimestamp = {};
latestTimestamp = getLatestTimestamp(
latestTimestamp,
updatedCriterion.data,
this.timeSystems,
this.openmct.time.timeSystem()
);
this.conditionManager.updateCurrentCondition(latestTimestamp);
}
updateDescription() { updateDescription() {
const triggerDescription = this.getTriggerDescription(); const triggerDescription = this.getTriggerDescription();
let description = ''; let description = '';

View File

@ -103,8 +103,6 @@ export default class ConditionManager extends EventEmitter {
criterion.operation = ''; criterion.operation = '';
conditionChanged = true; conditionChanged = true;
} }
} else {
conditionChanged = true;
} }
}); });
if (conditionChanged) { if (conditionChanged) {
@ -317,10 +315,6 @@ export default class ConditionManager extends EventEmitter {
condition.getResult(normalizedDatum); condition.getResult(normalizedDatum);
}); });
this.updateCurrentCondition(timestamp);
}
updateCurrentCondition(timestamp) {
const currentCondition = this.getCurrentCondition(); const currentCondition = this.getCurrentCondition();
this.emit('conditionSetResultUpdated', this.emit('conditionSetResultUpdated',

View File

@ -46,7 +46,6 @@ export default class StyleRuleManager extends EventEmitter {
if (this.isEditing) { if (this.isEditing) {
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
} }
if (this.conditionSetIdentifier) { if (this.conditionSetIdentifier) {
this.applySelectedConditionStyle(); this.applySelectedConditionStyle();
@ -67,7 +66,6 @@ export default class StyleRuleManager extends EventEmitter {
subscribeToConditionSet() { subscribeToConditionSet() {
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
} }
this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => { this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
this.openmct.telemetry.request(conditionSetDomainObject) this.openmct.telemetry.request(conditionSetDomainObject)
@ -156,8 +154,8 @@ export default class StyleRuleManager extends EventEmitter {
this.applyStaticStyle(); this.applyStaticStyle();
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
} }
delete this.stopProvidingTelemetry;
this.conditionSetIdentifier = undefined; this.conditionSetIdentifier = undefined;
} }

View File

@ -27,13 +27,13 @@
> >
{{ condition.configuration.name }} {{ condition.configuration.name }}
</span> </span>
<span v-if="!condition.isDefault" <span class="c-style__condition-desc__text"
class="c-style__condition-desc__text" v-if="!condition.isDefault"
> >
{{ description }} {{ description }}
</span> </span>
<span v-else <span class="c-style__condition-desc__text"
class="c-style__condition-desc__text" v-else
> >
Match if no other condition is matched Match if no other condition is matched
</span> </span>

View File

@ -55,7 +55,6 @@
> >
{{ option.name }} {{ option.name }}
</option> </option>
<option value="dataReceived">any data received</option>
</select> </select>
</span> </span>
<span v-if="criterion.telemetry && criterion.metadata" <span v-if="criterion.telemetry && criterion.metadata"
@ -84,7 +83,6 @@
> >
<span v-if="inputIndex < inputCount-1">and</span> <span v-if="inputIndex < inputCount-1">and</span>
</span> </span>
<span v-if="criterion.metadata === 'dataReceived'">seconds</span>
</template> </template>
<span v-else> <span v-else>
<span v-if="inputCount && criterion.operation" <span v-if="inputCount && criterion.operation"
@ -150,11 +148,7 @@ export default {
return (this.index !== 0 ? operator : '') + ' when'; return (this.index !== 0 ? operator : '') + ' when';
}, },
filteredOps: function () { filteredOps: function () {
if (this.criterion.metadata === 'dataReceived') { return this.operations.filter(op => op.appliesTo.indexOf(this.operationFormat) !== -1);
return this.operations.filter(op => op.name === 'isStale');
} else {
return this.operations.filter(op => op.appliesTo.indexOf(this.operationFormat) !== -1);
}
}, },
setInputType: function () { setInputType: function () {
let type = ''; let type = '';
@ -220,8 +214,6 @@ export default {
} else { } else {
this.operationFormat = 'number'; this.operationFormat = 'number';
} }
} else if (this.criterion.metadata === 'dataReceived') {
this.operationFormat = 'number';
} }
this.updateInputVisibilityAndValues(); this.updateInputVisibilityAndValues();
}, },

View File

@ -197,7 +197,6 @@ export default {
} }
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
} }
}, },
initialize(conditionSetDomainObject) { initialize(conditionSetDomainObject) {
@ -211,7 +210,6 @@ export default {
if (this.isEditing) { if (this.isEditing) {
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
} }
} else { } else {
this.subscribeToConditionSet(); this.subscribeToConditionSet();
@ -309,7 +307,6 @@ export default {
this.persist(domainObjectStyles); this.persist(domainObjectStyles);
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
} }
}, },
updateDomainObjectItemStyles(newItems) { updateDomainObjectItemStyles(newItems) {
@ -378,7 +375,6 @@ export default {
subscribeToConditionSet() { subscribeToConditionSet() {
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
} }
if (this.conditionSetDomainObject) { if (this.conditionSetDomainObject) {
this.openmct.telemetry.request(this.conditionSetDomainObject) this.openmct.telemetry.request(this.conditionSetDomainObject)

View File

@ -37,13 +37,12 @@
> >
<style-editor class="c-inspect-styles__editor" <style-editor class="c-inspect-styles__editor"
:style-item="staticStyle" :style-item="staticStyle"
:is-editing="allowEditing" :is-editing="isEditing"
:mixed-styles="mixedStyles" :mixed-styles="mixedStyles"
@persist="updateStaticStyle" @persist="updateStaticStyle"
/> />
</div> </div>
<button <button
v-if="allowEditing"
id="addConditionSet" id="addConditionSet"
class="c-button c-button--major c-toggle-styling-button labeled" class="c-button c-button--major c-toggle-styling-button labeled"
@click="addConditionSet" @click="addConditionSet"
@ -64,7 +63,7 @@
> >
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span> <span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
</a> </a>
<template v-if="allowEditing"> <template v-if="isEditing">
<button <button
id="changeConditionSet" id="changeConditionSet"
class="c-button labeled" class="c-button labeled"
@ -97,7 +96,7 @@
/> />
<style-editor class="c-inspect-styles__editor" <style-editor class="c-inspect-styles__editor"
:style-item="conditionStyle" :style-item="conditionStyle"
:is-editing="allowEditing" :is-editing="isEditing"
@persist="updateConditionalStyle" @persist="updateConditionalStyle"
/> />
</div> </div>
@ -138,13 +137,7 @@ export default {
conditions: undefined, conditions: undefined,
conditionsLoaded: false, conditionsLoaded: false,
navigateToPath: '', navigateToPath: '',
selectedConditionId: '', selectedConditionId: ''
locked: false
}
},
computed: {
allowEditing() {
return this.isEditing && !this.locked;
} }
}, },
destroyed() { destroyed() {
@ -190,7 +183,6 @@ export default {
if (this.isEditing) { if (this.isEditing) {
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
} }
} else { } else {
this.subscribeToConditionSet(); this.subscribeToConditionSet();
@ -232,13 +224,7 @@ export default {
this.selection.forEach((selectionItem) => { this.selection.forEach((selectionItem) => {
const item = selectionItem[0].context.item; const item = selectionItem[0].context.item;
const layoutItem = selectionItem[0].context.layoutItem; const layoutItem = selectionItem[0].context.layoutItem;
const layoutDomainObject = selectionItem[0].context.item;
const isChildItem = selectionItem.length > 1; const isChildItem = selectionItem.length > 1;
if (layoutDomainObject && layoutDomainObject.locked) {
this.locked = true;
}
if (!isChildItem) { if (!isChildItem) {
domainObject = item; domainObject = item;
itemStyle = getApplicableStylesForItem(item); itemStyle = getApplicableStylesForItem(item);
@ -326,7 +312,6 @@ export default {
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
} }
if (this.unObserveObjects) { if (this.unObserveObjects) {
@ -339,7 +324,6 @@ export default {
subscribeToConditionSet() { subscribeToConditionSet() {
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
} }
if (this.conditionSetDomainObject) { if (this.conditionSetDomainObject) {
this.openmct.telemetry.request(this.conditionSetDomainObject) this.openmct.telemetry.request(this.conditionSetDomainObject)
@ -497,7 +481,6 @@ export default {
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
} }
}, },
removeConditionalStyles(domainObjectStyles, itemId) { removeConditionalStyles(domainObjectStyles, itemId) {

View File

@ -22,7 +22,7 @@
import TelemetryCriterion from './TelemetryCriterion'; import TelemetryCriterion from './TelemetryCriterion';
import { evaluateResults } from "../utils/evaluator"; import { evaluateResults } from "../utils/evaluator";
import {getLatestTimestamp, subscribeForStaleness} from '../utils/time'; import { getLatestTimestamp } from '../utils/time';
import { getOperatorText } from "@/plugins/condition/utils/operations"; import { getOperatorText } from "@/plugins/condition/utils/operations";
export default class AllTelemetryCriterion extends TelemetryCriterion { export default class AllTelemetryCriterion extends TelemetryCriterion {
@ -41,32 +41,6 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
initialize() { initialize() {
this.telemetryObjects = { ...this.telemetryDomainObjectDefinition.telemetryObjects }; this.telemetryObjects = { ...this.telemetryDomainObjectDefinition.telemetryObjects };
this.telemetryDataCache = {}; this.telemetryDataCache = {};
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData(this.telemetryObjects || {});
}
}
subscribeForStaleData(telemetryObjects) {
if (!this.stalenessSubscription) {
this.stalenessSubscription = {};
}
Object.values(telemetryObjects).forEach((telemetryObject) => {
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
if (!this.stalenessSubscription[id]) {
this.stalenessSubscription[id] = subscribeForStaleness((data) => {
this.handleStaleTelemetry(id, data);
}, this.input[0]*1000);
}
})
}
handleStaleTelemetry(id, data) {
if (this.telemetryDataCache) {
this.telemetryDataCache[id] = true;
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
}
this.emitEvent('telemetryIsStale', data);
} }
isValid() { isValid() {
@ -76,9 +50,6 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
updateTelemetryObjects(telemetryObjects) { updateTelemetryObjects(telemetryObjects) {
this.telemetryObjects = { ...telemetryObjects }; this.telemetryObjects = { ...telemetryObjects };
this.removeTelemetryDataCache(); this.removeTelemetryDataCache();
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData(this.telemetryObjects || {});
}
} }
removeTelemetryDataCache() { removeTelemetryDataCache() {
@ -92,7 +63,6 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
}); });
telemetryCacheIds.forEach(id => { telemetryCacheIds.forEach(id => {
delete (this.telemetryDataCache[id]); delete (this.telemetryDataCache[id]);
delete (this.stalenessSubscription[id]);
}); });
} }
@ -126,14 +96,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
const validatedData = this.isValid() ? data : {}; const validatedData = this.isValid() ? data : {};
if (validatedData) { if (validatedData) {
if (this.isStalenessCheck()) { this.telemetryDataCache[validatedData.id] = this.computeResult(validatedData);
if (this.stalenessSubscription[validatedData.id]) {
this.stalenessSubscription[validatedData.id].update(validatedData);
}
this.telemetryDataCache[validatedData.id] = false;
} else {
this.telemetryDataCache[validatedData.id] = this.computeResult(validatedData);
}
} }
Object.values(telemetryObjects).forEach(telemetryObject => { Object.values(telemetryObjects).forEach(telemetryObject => {
@ -199,7 +162,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
getDescription() { getDescription() {
const telemetryDescription = this.telemetry === 'all' ? 'all telemetry' : 'any telemetry'; const telemetryDescription = this.telemetry === 'all' ? 'all telemetry' : 'any telemetry';
let metadataValue = (this.metadata === 'dataReceived' ? '' : this.metadata); let metadataValue = this.metadata;
let inputValue = this.input; let inputValue = this.input;
if (this.metadata) { if (this.metadata) {
const telemetryObjects = Object.values(this.telemetryObjects); const telemetryObjects = Object.values(this.telemetryObjects);
@ -219,9 +182,5 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
destroy() { destroy() {
delete this.telemetryObjects; delete this.telemetryObjects;
delete this.telemetryDataCache; delete this.telemetryDataCache;
if (this.stalenessSubscription) {
Object.values(this.stalenessSubscription).forEach((subscription) => subscription.clear);
delete this.stalenessSubscription;
}
} }
} }

View File

@ -22,7 +22,6 @@
import EventEmitter from 'EventEmitter'; import EventEmitter from 'EventEmitter';
import { OPERATIONS, getOperatorText } from '../utils/operations'; import { OPERATIONS, getOperatorText } from '../utils/operations';
import { subscribeForStaleness } from "../utils/time";
export default class TelemetryCriterion extends EventEmitter { export default class TelemetryCriterion extends EventEmitter {
@ -44,7 +43,6 @@ export default class TelemetryCriterion extends EventEmitter {
this.input = telemetryDomainObjectDefinition.input; this.input = telemetryDomainObjectDefinition.input;
this.metadata = telemetryDomainObjectDefinition.metadata; this.metadata = telemetryDomainObjectDefinition.metadata;
this.result = undefined; this.result = undefined;
this.stalenessSubscription = undefined;
this.initialize(); this.initialize();
this.emitEvent('criterionUpdated', this); this.emitEvent('criterionUpdated', this);
@ -53,40 +51,14 @@ export default class TelemetryCriterion extends EventEmitter {
initialize() { initialize() {
this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry); this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry);
this.updateTelemetryObjects(this.telemetryDomainObjectDefinition.telemetryObjects); this.updateTelemetryObjects(this.telemetryDomainObjectDefinition.telemetryObjects);
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData()
}
}
subscribeForStaleData() {
if (this.stalenessSubscription) {
this.stalenessSubscription.clear();
}
this.stalenessSubscription = subscribeForStaleness(this.handleStaleTelemetry.bind(this), this.input[0]*1000);
}
handleStaleTelemetry(data) {
this.result = true;
this.emitEvent('telemetryIsStale', data);
} }
isValid() { isValid() {
return this.telemetryObject && this.metadata && this.operation; return this.telemetryObject && this.metadata && this.operation;
} }
isStalenessCheck() {
return this.metadata && this.metadata === 'dataReceived';
}
isValidInput() {
return this.input instanceof Array && this.input.length;
}
updateTelemetryObjects(telemetryObjects) { updateTelemetryObjects(telemetryObjects) {
this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString]; this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString];
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData()
}
} }
createNormalizedDatum(telemetryDatum, endpoint) { createNormalizedDatum(telemetryDatum, endpoint) {
@ -119,14 +91,7 @@ export default class TelemetryCriterion extends EventEmitter {
getResult(data) { getResult(data) {
const validatedData = this.isValid() ? data : {}; const validatedData = this.isValid() ? data : {};
if (this.isStalenessCheck()) { this.result = this.computeResult(validatedData);
if (this.stalenessSubscription) {
this.stalenessSubscription.update(validatedData);
}
this.result = false;
} else {
this.result = this.computeResult(validatedData);
}
} }
requestLAD() { requestLAD() {
@ -171,7 +136,7 @@ export default class TelemetryCriterion extends EventEmitter {
let comparator = this.findOperation(this.operation); let comparator = this.findOperation(this.operation);
let params = []; let params = [];
params.push(data[this.metadata]); params.push(data[this.metadata]);
if (this.isValidInput()) { if (this.input instanceof Array && this.input.length) {
this.input.forEach(input => params.push(input)); this.input.forEach(input => params.push(input));
} }
if (typeof comparator === 'function') { if (typeof comparator === 'function') {
@ -226,7 +191,7 @@ export default class TelemetryCriterion extends EventEmitter {
description = `Unknown ${this.metadata} ${getOperatorText(this.operation, this.input)}`; description = `Unknown ${this.metadata} ${getOperatorText(this.operation, this.input)}`;
} else { } else {
const metadataObject = this.getMetaDataObject(this.telemetryObject, this.metadata); const metadataObject = this.getMetaDataObject(this.telemetryObject, this.metadata);
const metadataValue = this.getMetadataValueFromMetaData(metadataObject) || (this.metadata === 'dataReceived' ? '' : this.metadata); const metadataValue = this.getMetadataValueFromMetaData(metadataObject) || this.metadata;
const inputValue = this.getInputValueFromMetaData(metadataObject, this.input) || this.input; const inputValue = this.getInputValueFromMetaData(metadataObject, this.input) || this.input;
description = `${this.telemetryObject.name} ${metadataValue} ${getOperatorText(this.operation, inputValue)}`; description = `${this.telemetryObject.name} ${metadataValue} ${getOperatorText(this.operation, inputValue)}`;
} }
@ -237,8 +202,5 @@ export default class TelemetryCriterion extends EventEmitter {
destroy() { destroy() {
delete this.telemetryObject; delete this.telemetryObject;
delete this.telemetryObjectIdAsString; delete this.telemetryObjectIdAsString;
if (this.stalenessSubscription) {
delete this.stalenessSubscription;
}
} }
} }

View File

@ -25,50 +25,19 @@ import ConditionPlugin from "./plugin";
import StylesView from "./components/inspector/StylesView.vue"; import StylesView from "./components/inspector/StylesView.vue";
import Vue from 'vue'; import Vue from 'vue';
import {getApplicableStylesForItem} from "./utils/styleUtils"; import {getApplicableStylesForItem} from "./utils/styleUtils";
import ConditionManager from "@/plugins/condition/ConditionManager";
describe('the plugin', function () { describe('the plugin', function () {
let conditionSetDefinition; let conditionSetDefinition;
let mockConditionSetDomainObject; let mockConditionSetDomainObject;
let mockListener;
let element; let element;
let child; let child;
let openmct; let openmct;
let testTelemetryObject;
beforeAll(() => { beforeAll(() => {
resetApplicationState(openmct); resetApplicationState(openmct);
}); });
beforeEach((done) => { beforeEach((done) => {
testTelemetryObject = {
identifier:{ namespace: "", key: "test-object"},
type: "test-object",
name: "Test Object",
telemetry: {
valueMetadatas: [{
key: "some-key",
name: "Some attribute",
hints: {
range: 2
}
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
}, {
key: "testSource",
source: "value",
name: "Test",
format: "string"
}]
}
};
openmct = createOpenMct(); openmct = createOpenMct();
openmct.install(new ConditionPlugin()); openmct.install(new ConditionPlugin());
@ -86,8 +55,6 @@ describe('the plugin', function () {
type: 'conditionSet' type: 'conditionSet'
}; };
mockListener = jasmine.createSpy('mockListener');
conditionSetDefinition.initialize(mockConditionSetDomainObject); conditionSetDefinition.initialize(mockConditionSetDomainObject);
openmct.on('start', done); openmct.on('start', done);
@ -389,113 +356,4 @@ describe('the plugin', function () {
}); });
}); });
describe('the condition check for staleness', () => {
let conditionSetDomainObject;
beforeEach(()=>{
conditionSetDomainObject = {
"configuration":{
"conditionTestData":[
{
"telemetry":"",
"metadata":"",
"input":""
}
],
"conditionCollection":[
{
"id":"39584410-cbf9-499e-96dc-76f27e69885d",
"configuration":{
"name":"Unnamed Condition",
"output":"Any stale telemetry",
"trigger":"all",
"criteria":[
{
"id":"35400132-63b0-425c-ac30-8197df7d5862",
"telemetry":"any",
"operation":"isStale",
"input":[
"1"
],
"metadata":"dataReceived"
}
]
},
"summary":"Match if all criteria are met: Any telemetry is stale after 5 seconds"
},
{
"isDefault":true,
"id":"2532d90a-e0d6-4935-b546-3123522da2de",
"configuration":{
"name":"Default",
"output":"Default",
"trigger":"all",
"criteria":[
]
},
"summary":""
}
]
},
"composition":[
{
"namespace":"",
"key":"test-object"
}
],
"telemetry":{
},
"name":"Condition Set",
"type":"conditionSet",
"identifier":{
"namespace":"",
"key":"cf4456a9-296a-4e6b-b182-62ed29cd15b9"
}
};
});
it('should evaluate as stale when telemetry is not received in the allotted time', (done) => {
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
conditionMgr.on('conditionSetResultUpdated', mockListener);
conditionMgr.telemetryObjects = {
"test-object": testTelemetryObject
};
conditionMgr.updateConditionTelemetryObjects();
setTimeout(() => {
expect(mockListener).toHaveBeenCalledWith({
output: 'Any stale telemetry',
id: { namespace: '', key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9' },
conditionId: '39584410-cbf9-499e-96dc-76f27e69885d',
utc: undefined
});
done();
}, 1500);
});
it('should not evaluate as stale when telemetry is received in the allotted time', (done) => {
const date = Date.now();
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input = ["2"];
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
conditionMgr.on('conditionSetResultUpdated', mockListener);
conditionMgr.telemetryObjects = {
"test-object": testTelemetryObject
};
conditionMgr.updateConditionTelemetryObjects();
conditionMgr.telemetryReceived(testTelemetryObject, {
utc: date
});
setTimeout(() => {
expect(mockListener).toHaveBeenCalledWith({
output: 'Default',
id: { namespace: '', key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9' },
conditionId: '2532d90a-e0d6-4935-b546-3123522da2de',
utc: undefined
});
done();
}, 1500);
});
});
}); });

View File

@ -283,18 +283,6 @@ export const OPERATIONS = [
getDescription: function (values) { getDescription: function (values) {
return ' is not one of ' + values[0]; return ' is not one of ' + values[0];
} }
},
{
name: 'isStale',
operation: function () {
return false;
},
text: 'is older than',
appliesTo: ["number"],
inputCount: 1,
getDescription: function (values) {
return ` is older than ${values[0] || ''} seconds`;
}
} }
]; ];

View File

@ -50,26 +50,3 @@ function updateLatestTimeStamp(timestamp, timeSystems) {
return latest; return latest;
} }
export const subscribeForStaleness = (callback, timeout) => {
let stalenessTimer = setTimeout(() => {
clearTimeout(stalenessTimer);
callback();
}, timeout);
return {
update: (data) => {
if (stalenessTimer) {
clearTimeout(stalenessTimer);
}
stalenessTimer = setTimeout(() => {
clearTimeout(stalenessTimer);
callback(data);
}, timeout);
},
clear: () => {
if (stalenessTimer) {
clearTimeout(stalenessTimer);
}
}
}
};

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.
*****************************************************************************/
import { subscribeForStaleness } from "./time";
describe('time related utils', () => {
let subscription;
let mockListener;
beforeEach(() => {
mockListener = jasmine.createSpy('listener');
subscription = subscribeForStaleness(mockListener, 100);
});
describe('subscribe for staleness', () => {
it('should call listeners when stale', (done) => {
setTimeout(() => {
expect(mockListener).toHaveBeenCalled();
done();
}, 200);
});
it('should update the subscription', (done) => {
function updated() {
setTimeout(() => {
expect(mockListener).not.toHaveBeenCalled();
done();
}, 50);
}
setTimeout(() => {
subscription.update();
updated();
}, 50);
});
it('should clear the subscription', (done) => {
subscription.clear();
setTimeout(() => {
expect(mockListener).not.toHaveBeenCalled();
done();
}, 200);
});
});
});

View File

@ -73,7 +73,7 @@ define(['lodash'], function (_) {
] ]
} }
}, },
VIEW_TYPES = { viewTypes = {
'telemetry-view': { 'telemetry-view': {
value: 'telemetry-view', value: 'telemetry-view',
name: 'Alphanumeric', name: 'Alphanumeric',
@ -95,34 +95,28 @@ define(['lodash'], function (_) {
class: 'icon-tabular-realtime' class: 'icon-tabular-realtime'
} }
}, },
APPLICABLE_VIEWS = { applicableViews = {
'telemetry-view': [ 'telemetry-view': [
VIEW_TYPES['telemetry.plot.overlay'], viewTypes['telemetry.plot.overlay'],
VIEW_TYPES['telemetry.plot.stacked'], viewTypes.table
VIEW_TYPES.table
], ],
'telemetry.plot.overlay': [ 'telemetry.plot.overlay': [
VIEW_TYPES['telemetry.plot.stacked'], viewTypes['telemetry.plot.stacked'],
VIEW_TYPES.table, viewTypes.table,
VIEW_TYPES['telemetry-view'] viewTypes['telemetry-view']
],
'telemetry.plot.stacked': [
VIEW_TYPES['telemetry.plot.overlay'],
VIEW_TYPES.table,
VIEW_TYPES['telemetry-view']
], ],
'table': [ 'table': [
VIEW_TYPES['telemetry.plot.overlay'], viewTypes['telemetry.plot.overlay'],
VIEW_TYPES['telemetry.plot.stacked'], viewTypes['telemetry.plot.stacked'],
VIEW_TYPES['telemetry-view'] viewTypes['telemetry-view']
], ],
'telemetry-view-multi': [ 'telemetry-view-multi': [
VIEW_TYPES['telemetry.plot.overlay'], viewTypes['telemetry.plot.overlay'],
VIEW_TYPES['telemetry.plot.stacked'], viewTypes['telemetry.plot.stacked'],
VIEW_TYPES.table viewTypes.table
], ],
'telemetry.plot.overlay-multi': [ 'telemetry.plot.overlay-multi': [
VIEW_TYPES['telemetry.plot.stacked'] viewTypes['telemetry.plot.stacked']
] ]
}; };
@ -516,7 +510,7 @@ define(['lodash'], function (_) {
selectedItemType = 'telemetry-view'; selectedItemType = 'telemetry-view';
} }
let viewOptions = APPLICABLE_VIEWS[selectedItemType]; let viewOptions = applicableViews[selectedItemType];
if (viewOptions) { if (viewOptions) {
return { return {
@ -539,7 +533,7 @@ define(['lodash'], function (_) {
domainObject: selectedParent, domainObject: selectedParent,
icon: "icon-object", icon: "icon-object",
title: "Merge into a telemetry table or plot", title: "Merge into a telemetry table or plot",
options: APPLICABLE_VIEWS['telemetry-view-multi'], options: applicableViews['telemetry-view-multi'],
method: function (option) { method: function (option) {
displayLayoutContext.mergeMultipleTelemetryViews(selection, option.value); displayLayoutContext.mergeMultipleTelemetryViews(selection, option.value);
} }
@ -552,7 +546,7 @@ define(['lodash'], function (_) {
domainObject: selectedParent, domainObject: selectedParent,
icon: "icon-object", icon: "icon-object",
title: "Merge into a stacked plot", title: "Merge into a stacked plot",
options: APPLICABLE_VIEWS['telemetry.plot.overlay-multi'], options: applicableViews['telemetry.plot.overlay-multi'],
method: function (option) { method: function (option) {
displayLayoutContext.mergeMultipleOverlayPlots(selection, option.value); displayLayoutContext.mergeMultipleOverlayPlots(selection, option.value);
} }
@ -596,7 +590,7 @@ define(['lodash'], function (_) {
let selectedParent = selectionPath[1].context.item; let selectedParent = selectionPath[1].context.item;
let layoutItem = selectionPath[0].context.layoutItem; let layoutItem = selectionPath[0].context.layoutItem;
if (!layoutItem || selectedParent.locked) { if (!layoutItem) {
return; return;
} }

View File

@ -24,7 +24,6 @@
<layout-frame <layout-frame
:item="item" :item="item"
:grid-size="gridSize" :grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)" @move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')" @endMove="() => $emit('endMove')"
> >
@ -71,11 +70,7 @@ export default {
type: Number, type: Number,
required: true required: true
}, },
initSelect: Boolean, initSelect: Boolean
isEditing: {
type: Boolean,
required: true
}
}, },
computed: { computed: {
style() { style() {

View File

@ -24,18 +24,14 @@
<div <div
class="l-layout" class="l-layout"
:class="{ :class="{
'is-multi-selected': selectedLayoutItems.length > 1, 'is-multi-selected': selectedLayoutItems.length > 1
'allow-editing': isEditing
}" }"
@dragover="handleDragOver" @dragover="handleDragOver"
@click.capture="bypassSelection" @click.capture="bypassSelection"
@drop="handleDrop" @drop="handleDrop"
> >
<!-- Background grid --> <!-- Background grid -->
<div <div class="l-layout__grid-holder c-grid">
v-if="isEditing"
class="l-layout__grid-holder c-grid"
>
<div <div
v-if="gridSize[0] >= 3" v-if="gridSize[0] >= 3"
class="c-grid__x l-grid l-grid-x" class="c-grid__x l-grid l-grid-x"
@ -57,7 +53,6 @@
:init-select="initSelectIndex === index" :init-select="initSelectIndex === index"
:index="index" :index="index"
:multi-select="selectedLayoutItems.length > 1" :multi-select="selectedLayoutItems.length > 1"
:is-editing="isEditing"
@move="move" @move="move"
@endMove="endMove" @endMove="endMove"
@endLineResize="endLineResize" @endLineResize="endLineResize"
@ -83,30 +78,6 @@ import ImageView from './ImageView.vue'
import EditMarquee from './EditMarquee.vue' import EditMarquee from './EditMarquee.vue'
import _ from 'lodash' import _ from 'lodash'
const TELEMETRY_IDENTIFIER_FUNCTIONS = {
'table': (domainObject) => {
return Promise.resolve(domainObject.composition);
},
'telemetry.plot.overlay': (domainObject) => {
return Promise.resolve(domainObject.composition);
},
'telemetry.plot.stacked': (domainObject, openmct) => {
let composition = openmct.composition.get(domainObject);
return composition.load().then((objects) => {
let identifiers = [];
objects.forEach(object => {
if (object.type === 'telemetry.plot.overlay') {
identifiers.push(...object.composition);
} else {
identifiers.push(object.identifier);
}
});
return Promise.resolve(identifiers);
});
}
}
const ITEM_TYPE_VIEW_MAP = { const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView, 'subobject-view': SubobjectView,
'telemetry-view': TelemetryView, 'telemetry-view': TelemetryView,
@ -143,10 +114,6 @@ export default {
domainObject: { domainObject: {
type: Object, type: Object,
required: true required: true
},
isEditing: {
type: Boolean,
required: true
} }
}, },
data() { data() {
@ -173,7 +140,7 @@ export default {
let selectionPath = this.selection[0]; let selectionPath = this.selection[0];
let singleSelectedLine = this.selection.length === 1 && let singleSelectedLine = this.selection.length === 1 &&
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type === 'line-view'; selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type === 'line-view';
return this.isEditing && selectionPath && selectionPath.length > 1 && !singleSelectedLine; return selectionPath && selectionPath.length > 1 && !singleSelectedLine;
} }
}, },
inject: ['openmct', 'options', 'objectPath'], inject: ['openmct', 'options', 'objectPath'],
@ -361,9 +328,6 @@ export default {
.some(childId => this.openmct.objects.areIdsEqual(childId, identifier)); .some(childId => this.openmct.objects.areIdsEqual(childId, identifier));
}, },
handleDragOver($event) { handleDragOver($event) {
if (this.internalDomainObject.locked) {
return;
}
// Get the ID of the dragged object // Get the ID of the dragged object
let draggedKeyString = $event.dataTransfer.types let draggedKeyString = $event.dataTransfer.types
.filter(type => type.startsWith(DRAG_OBJECT_TRANSFER_PREFIX)) .filter(type => type.startsWith(DRAG_OBJECT_TRANSFER_PREFIX))
@ -457,43 +421,15 @@ export default {
this.objectViewMap = {}; this.objectViewMap = {};
this.layoutItems.forEach(this.trackItem); this.layoutItems.forEach(this.trackItem);
}, },
isItemAlreadyTracked(child) {
let found = false,
keyString = this.openmct.objects.makeKeyString(child.identifier);
this.layoutItems.forEach(item => {
if (item.identifier) {
let itemKeyString = this.openmct.objects.makeKeyString(item.identifier);
if (itemKeyString === keyString) {
found = true;
return;
}
}
});
if (found) {
return true;
} else if (this.isTelemetry(child)) {
return this.telemetryViewMap[keyString] && this.objectViewMap[keyString];
} else {
return this.objectViewMap[keyString];
}
},
addChild(child) { addChild(child) {
if (this.isItemAlreadyTracked(child)) { let keyString = this.openmct.objects.makeKeyString(child.identifier);
return;
}
let type;
if (this.isTelemetry(child)) { if (this.isTelemetry(child)) {
type = 'telemetry-view'; if (!this.telemetryViewMap[keyString] && !this.objectViewMap[keyString]) {
} else { this.addItem('telemetry-view', child);
type = 'subobject-view'; }
} else if (!this.objectViewMap[keyString]) {
this.addItem('subobject-view', child);
} }
this.addItem(type, child);
}, },
removeChild(identifier) { removeChild(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier); let keyString = this.openmct.objects.makeKeyString(identifier);
@ -587,17 +523,14 @@ export default {
} }
}, },
updateTelemetryFormat(item, format) { updateTelemetryFormat(item, format) {
let index = this.layoutItems.findIndex((layoutItem) => { let index = this.layoutItems.findIndex(item);
return layoutItem.id === item.id;
});
item.format = format; item.format = format;
this.mutate(`configuration.items[${index}]`, item); this.mutate(`configuration.items[${index}]`, item);
}, },
createNewDomainObject(domainObject, composition, viewType, nameExtension, model) { createNewDomainObject(domainObject, composition, viewType, nameExtension, model) {
let identifier = { let identifier = {
key: uuid(), key: uuid(),
namespace: this.internalDomainObject.identifier.namespace namespace: domainObject.identifier.namespace
}, },
type = this.openmct.types.get(viewType), type = this.openmct.types.get(viewType),
parentKeyString = this.openmct.objects.makeKeyString(this.internalDomainObject.identifier), parentKeyString = this.openmct.objects.makeKeyString(this.internalDomainObject.identifier),
@ -616,7 +549,7 @@ export default {
object.identifier = identifier; object.identifier = identifier;
object.location = parentKeyString; object.location = parentKeyString;
this.openmct.objects.mutate(object, 'created', Date.now()); this.openmct.objects.mutate(object, 'persisted', Date.now());
return object; return object;
}, },
@ -738,42 +671,31 @@ export default {
this.removeItem(selection); this.removeItem(selection);
this.initSelectIndex = this.layoutItems.length - 1; this.initSelectIndex = this.layoutItems.length - 1;
}, },
getTelemetryIdentifiers(domainObject) {
let method = TELEMETRY_IDENTIFIER_FUNCTIONS[domainObject.type];
if (method) {
return method(domainObject, this.openmct);
} else {
throw 'No method identified for domainObject type';
}
},
switchViewType(context, viewType, selection) { switchViewType(context, viewType, selection) {
let domainObject = context.item, let domainObject = context.item,
layoutItem = context.layoutItem, layoutItem = context.layoutItem,
position = [layoutItem.x, layoutItem.y], position = [layoutItem.x, layoutItem.y],
newDomainObject,
layoutType = 'subobject-view'; layoutType = 'subobject-view';
if (layoutItem.type === 'telemetry-view') { if (layoutItem.type === 'telemetry-view') {
let newDomainObject = this.createNewDomainObject(domainObject, [domainObject.identifier], viewType); newDomainObject = this.createNewDomainObject(domainObject, [domainObject.identifier], viewType);
} else {
if (viewType !== 'telemetry-view') {
newDomainObject = this.createNewDomainObject(domainObject, domainObject.composition, viewType);
} else {
domainObject.composition.forEach((identifier , index) => {
let positionX = position[0] + (index * DUPLICATE_OFFSET),
positionY = position[1] + (index * DUPLICATE_OFFSET);
this.convertToTelemetryView(identifier, [positionX, positionY]);
});
}
}
if (newDomainObject) {
this.composition.add(newDomainObject); this.composition.add(newDomainObject);
this.addItem(layoutType, newDomainObject, position); this.addItem(layoutType, newDomainObject, position);
} else {
this.getTelemetryIdentifiers(domainObject).then((identifiers) => {
if (viewType === 'telemetry-view') {
identifiers.forEach((identifier, index) => {
let positionX = position[0] + (index * DUPLICATE_OFFSET),
positionY = position[1] + (index * DUPLICATE_OFFSET);
this.convertToTelemetryView(identifier, [positionX, positionY]);
});
} else {
let newDomainObject = this.createNewDomainObject(domainObject, identifiers, viewType);
this.composition.add(newDomainObject);
this.addItem(layoutType, newDomainObject, position);
}
});
} }
this.removeItem(selection); this.removeItem(selection);

View File

@ -24,7 +24,6 @@
<layout-frame <layout-frame
:item="item" :item="item"
:grid-size="gridSize" :grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)" @move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')" @endMove="() => $emit('endMove')"
> >
@ -71,11 +70,7 @@ export default {
type: Number, type: Number,
required: true required: true
}, },
initSelect: Boolean, initSelect: Boolean
isEditing: {
type: Boolean,
required: true
}
}, },
computed: { computed: {
style() { style() {

View File

@ -33,7 +33,7 @@
<div <div
class="c-frame-edit__move" class="c-frame-edit__move"
@mousedown="isEditing ? startMove([1,1], [0,0], $event) : null" @mousedown="startMove([1,1], [0,0], $event)"
></div> ></div>
</div> </div>
</template> </template>
@ -54,10 +54,6 @@ export default {
required: true, required: true,
validator: (arr) => arr && arr.length === 2 validator: (arr) => arr && arr.length === 2
&& arr.every(el => typeof el === 'number') && arr.every(el => typeof el === 'number')
},
isEditing: {
type: Boolean,
required: true
} }
}, },
computed: { computed: {

View File

@ -24,7 +24,6 @@
:item="item" :item="item"
:grid-size="gridSize" :grid-size="gridSize"
:title="domainObject && domainObject.name" :title="domainObject && domainObject.name"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)" @move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')" @endMove="() => $emit('endMove')"
> >
@ -96,10 +95,6 @@ export default {
index: { index: {
type: Number, type: Number,
required: true required: true
},
isEditing: {
type: Boolean,
required: true
} }
}, },
data() { data() {

View File

@ -24,23 +24,16 @@
<layout-frame <layout-frame
:item="item" :item="item"
:grid-size="gridSize" :grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)" @move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')" @endMove="() => $emit('endMove')"
> >
<div <div
v-if="domainObject" v-if="domainObject"
class="c-telemetry-view" class="c-telemetry-view"
:class="{ :class="styleClass"
styleClass,
'is-missing': domainObject.status === 'missing'
}"
:style="styleObject" :style="styleObject"
@contextmenu.prevent="showContextMenu" @contextmenu.prevent="showContextMenu"
> >
<div class="is-missing__indicator"
title="This item is missing"
></div>
<div <div
v-if="showLabel" v-if="showLabel"
class="c-telemetry-view__label" class="c-telemetry-view__label"
@ -112,10 +105,6 @@ export default {
index: { index: {
type: Number, type: Number,
required: true required: true
},
isEditing: {
type: Boolean,
required: true
} }
}, },
data() { data() {

View File

@ -24,7 +24,6 @@
<layout-frame <layout-frame
:item="item" :item="item"
:grid-size="gridSize" :grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)" @move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')" @endMove="() => $emit('endMove')"
> >
@ -76,11 +75,7 @@ export default {
type: Number, type: Number,
required: true required: true
}, },
initSelect: Boolean, initSelect: Boolean
isEditing: {
type: Boolean,
required: true
}
}, },
computed: { computed: {
style() { style() {

View File

@ -45,7 +45,8 @@
&[s-selected], &[s-selected],
&[s-selected-parent] { &[s-selected-parent] {
// Display grid and allow edit marquee to display in nested layouts when editing // Display grid and allow edit marquee to display in nested layouts when editing
> * > * > .l-layout + .allow-editing { > * > * > .l-layout {
background: $editUIGridColorBg;
box-shadow: inset $editUIGridColorFg 0 0 2px 1px; box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
> [class*='grid-holder'] { > [class*='grid-holder'] {

View File

@ -26,15 +26,4 @@
@include abs(); @include abs();
border: 1px solid transparent; border: 1px solid transparent;
} }
@include isMissing($absPos: true);
.is-missing__indicator {
top: 0;
left: 0;
}
&.is-missing {
border: $borderMissing;
}
} }

View File

@ -54,11 +54,10 @@ export default function DisplayLayoutPlugin(options) {
}, },
data() { data() {
return { return {
domainObject: domainObject, domainObject: domainObject
isEditing: openmct.editor.isEditing()
}; };
}, },
template: '<layout ref="displayLayout" :domain-object="domainObject" :is-editing="isEditing"></layout>' template: '<layout ref="displayLayout" :domain-object="domainObject"></layout>'
}); });
}, },
getSelectionContext() { getSelectionContext() {
@ -74,9 +73,6 @@ export default function DisplayLayoutPlugin(options) {
mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots
}; };
}, },
onEditModeChange: function (isEditing) {
component.isEditing = isEditing;
},
destroy() { destroy() {
component.$destroy(); component.$destroy();
} }

View File

@ -53,7 +53,6 @@
:index="i" :index="i"
:container-index="index" :container-index="index"
:is-editing="isEditing" :is-editing="isEditing"
:object-path="objectPath"
/> />
<drop-hint <drop-hint
@ -106,14 +105,6 @@ export default {
isEditing: { isEditing: {
type: Boolean, type: Boolean,
default: false default: false
},
locked: {
type: Boolean,
default: false
},
objectPath: {
type: Array,
required: true
} }
}, },
computed: { computed: {
@ -139,10 +130,6 @@ export default {
}, },
methods: { methods: {
allowDrop(event, index) { allowDrop(event, index) {
if (this.locked) {
return false;
}
if (event.dataTransfer.types.includes('openmct/domain-object-path')) { if (event.dataTransfer.types.includes('openmct/domain-object-path')) {
return true; return true;
} }

View File

@ -57,8 +57,6 @@
:container="container" :container="container"
:rows-layout="rowsLayout" :rows-layout="rowsLayout"
:is-editing="isEditing" :is-editing="isEditing"
:locked="domainObject.locked"
:object-path="objectPath"
@move-frame="moveFrame" @move-frame="moveFrame"
@new-frame="setFrameLocation" @new-frame="setFrameLocation"
@persist="persist" @persist="persist"
@ -138,7 +136,7 @@ function sizeToFill(items) {
} }
export default { export default {
inject: ['openmct', 'objectPath', 'layoutObject'], inject: ['openmct', 'layoutObject'],
components: { components: {
ContainerComponent, ContainerComponent,
ResizeHandle, ResizeHandle,

View File

@ -37,7 +37,7 @@
v-if="domainObject" v-if="domainObject"
ref="objectFrame" ref="objectFrame"
:domain-object="domainObject" :domain-object="domainObject"
:object-path="currentObjectPath" :object-path="objectPath"
:has-frame="hasFrame" :has-frame="hasFrame"
:show-edit-view="false" :show-edit-view="false"
/> />
@ -77,16 +77,12 @@ export default {
isEditing: { isEditing: {
type: Boolean, type: Boolean,
default: false default: false
},
objectPath: {
type: Array,
required: true
} }
}, },
data() { data() {
return { return {
domainObject: undefined, domainObject: undefined,
currentObjectPath: undefined objectPath: undefined
} }
}, },
computed: { computed: {
@ -111,7 +107,7 @@ export default {
methods: { methods: {
setDomainObject(object) { setDomainObject(object) {
this.domainObject = object; this.domainObject = object;
this.currentObjectPath = [object].concat(this.objectPath); this.objectPath = [object];
this.setSelection(); this.setSelection();
}, },
setSelection() { setSelection() {

View File

@ -38,7 +38,7 @@ define([
canEdit: function (domainObject) { canEdit: function (domainObject) {
return domainObject.type === 'flexible-layout'; return domainObject.type === 'flexible-layout';
}, },
view: function (domainObject, objectPath) { view: function (domainObject) {
let component; let component;
return { return {
@ -46,7 +46,6 @@ define([
component = new Vue({ component = new Vue({
provide: { provide: {
openmct, openmct,
objectPath,
layoutObject: domainObject layoutObject: domainObject
}, },
el: element, el: element,

View File

@ -70,10 +70,6 @@ function ToolbarProvider(openmct) {
} }
if (primary.context.type === 'frame') { if (primary.context.type === 'frame') {
if (secondary.context.item.locked) {
return [];
}
let frameId = primary.context.frameId; let frameId = primary.context.frameId;
let layoutObject = tertiary.context.item; let layoutObject = tertiary.context.item;
let containers = layoutObject let containers = layoutObject
@ -147,9 +143,6 @@ function ToolbarProvider(openmct) {
toggleContainer.domainObject = secondary.context.item; toggleContainer.domainObject = secondary.context.item;
} else if (primary.context.type === 'container') { } else if (primary.context.type === 'container') {
if (primary.context.item.locked) {
return [];
}
deleteContainer = { deleteContainer = {
control: "button", control: "button",
@ -194,9 +187,6 @@ function ToolbarProvider(openmct) {
}; };
} else if (primary.context.type === 'flexible-layout') { } else if (primary.context.type === 'flexible-layout') {
if (primary.context.item.locked) {
return [];
}
addContainer = { addContainer = {
control: "button", control: "button",

View File

@ -1,18 +1,13 @@
<template> <template>
<a <a
class="l-grid-view__item c-grid-item" class="l-grid-view__item c-grid-item"
:class="{ :class="{ 'is-alias': item.isAlias === true }"
'is-alias': item.isAlias === true,
'is-missing': item.model.status === 'missing',
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
}"
:href="objectLink" :href="objectLink"
> >
<div <div
class="c-grid-item__type-icon" class="c-grid-item__type-icon"
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'" :class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'"
> ></div>
</div>
<div class="c-grid-item__details"> <div class="c-grid-item__details">
<!-- Name and metadata --> <!-- Name and metadata -->
<div <div
@ -27,9 +22,6 @@
</div> </div>
</div> </div>
<div class="c-grid-item__controls"> <div class="c-grid-item__controls">
<div class="is-missing__indicator"
title="This item is missing"
></div>
<div <div
class="icon-people" class="icon-people"
title="Shared" title="Shared"

View File

@ -7,19 +7,13 @@
<td class="c-list-item__name"> <td class="c-list-item__name">
<a <a
ref="objectLink" ref="objectLink"
class="c-object-label"
:class="{ 'is-missing': item.model.status === 'missing' }"
:href="objectLink" :href="objectLink"
> >
<div <div
class="c-object-label__type-icon c-list-item__type-icon" class="c-list-item__type-icon"
:class="item.type.cssClass" :class="item.type.cssClass"
> ></div>
<span class="is-missing__indicator" <div class="c-list-item__name-value">{{ item.model.name }}</div>
title="This item is missing"
></span>
</div>
<div class="c-object-label__name c-list-item__name">{{ item.model.name }}</div>
</a> </a>
</td> </td>
<td class="c-list-item__type"> <td class="c-list-item__type">

View File

@ -38,15 +38,7 @@
// Object is an alias to an original. // Object is an alias to an original.
[class*='__type-icon'] { [class*='__type-icon'] {
@include isAlias(); @include isAlias();
} color: $colorIconAliasForKeyFilter;
}
&.is-missing {
@include isMissing();
[class*='__type-icon'],
[class*='__details'] {
opacity: $opacityMissing;
} }
} }
@ -93,14 +85,15 @@
body.desktop & { body.desktop & {
$transOutMs: 300ms; $transOutMs: 300ms;
flex-flow: column nowrap; flex-flow: column nowrap;
transition: $transOutMs ease-in-out; transition: background $transOutMs ease-in-out;
&:hover { &:hover {
filter: $filterItemHoverFg; background: $colorItemBgHov;
transition: $transIn; transition: $transIn;
.c-grid-item__type-icon { .c-grid-item__type-icon {
transform: scale(1.1); filter: $colorKeyFilterHov;
transform: scale(1);
transition: $transInBounce; transition: $transInBounce;
} }
} }
@ -110,7 +103,7 @@
} }
&__controls { &__controls {
align-items: baseline; align-items: start;
flex: 0 0 auto; flex: 0 0 auto;
order: 1; order: 1;
.c-info-button, .c-info-button,
@ -122,6 +115,7 @@
font-size: floor($gridItemDesk / 3); font-size: floor($gridItemDesk / 3);
margin: $interiorMargin 22.5% $interiorMargin * 3 22.5%; margin: $interiorMargin 22.5% $interiorMargin * 3 22.5%;
order: 2; order: 2;
transform: scale(0.9);
transform-origin: center; transform-origin: center;
transition: all $transOutMs ease-in-out; transition: all $transOutMs ease-in-out;
} }

View File

@ -1,17 +1,37 @@
/******************************* LIST ITEM */ /******************************* LIST ITEM */
.c-list-item { .c-list-item {
&__type-icon { &__name a {
color: $colorItemTreeIcon; display: flex;
> * + * { margin-left: $interiorMarginSm; }
} }
&__name { &__type-icon {
// Have to do it this way instead of using icon-* class, due to need to apply alias to the icon
color: $colorKey;
display: inline-block;
width: 1em;
margin-right:$interiorMarginSm;
}
&__name-value {
@include ellipsize(); @include ellipsize();
} }
&.is-alias { &.is-alias {
// Object is an alias to an original. // Object is an alias to an original.
[class*='__type-icon'] { [class*='__type-icon'] {
@include isAlias(); &:after {
color: $colorIconAlias;
content: $glyph-icon-link;
font-family: symbolsfont;
display: block;
position: absolute;
text-shadow: rgba(black, 0.5) 0 1px 2px;
top: auto; left: -1px; bottom: 1px; right: auto;
transform-origin: bottom left;
transform: scale(0.65);
}
} }
} }
} }

View File

@ -13,8 +13,7 @@
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: $colorItemTreeHoverBg; background: $colorListItemBgHov;
filter: $filterHov;
transition: $transIn; transition: $transIn;
} }
} }

View File

@ -24,17 +24,16 @@
</div> </div>
<div class="main-image s-image-main c-imagery__main-image" <div class="main-image s-image-main c-imagery__main-image"
:class="{'paused unnsynced': paused(),'stale':false }" :class="{'paused unnsynced': paused(),'stale':false }"
:style="{'background-image': getImageUrl() ? `url(${getImageUrl()})` : 'none', :style="{'background-image': `url(${getImageUrl()})`,
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}" 'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
> >
</div> </div>
<div class="c-imagery__control-bar"> <div class="c-imagery__control-bar">
<div class="c-imagery__timestamp">{{ getTime() }}</div> <div class="c-imagery__timestamp">{{ getTime() }}</div>
<div class="h-local-controls flex-elem"> <div class="h-local-controls flex-elem">
<a <a class="c-button icon-pause pause-play"
class="c-button icon-pause pause-play" :class="{'is-paused': paused()}"
:class="{'is-paused': paused()}" @click="paused(!paused())"
@click="paused(!paused())"
></a> ></a>
</div> </div>
</div> </div>
@ -186,10 +185,6 @@ export default {
setSelectedImage(image) { setSelectedImage(image) {
// If we are paused and the current image IS selected, unpause // If we are paused and the current image IS selected, unpause
// Otherwise, set current image and pause // Otherwise, set current image and pause
if (!image) {
return;
}
if (this.isPaused && image.selected) { if (this.isPaused && image.selected) {
this.paused(false); this.paused(false);
this.unselectAllImages(); this.unselectAllImages();
@ -202,7 +197,7 @@ export default {
} }
}, },
boundsChange(bounds, isTick) { boundsChange(bounds, isTick) {
if (!isTick) { if(!isTick) {
this.requestHistory(); this.requestHistory();
} }
}, },

View File

@ -41,7 +41,7 @@ define([], function () {
this.timeFormat = 'local-format'; this.timeFormat = 'local-format';
this.durationFormat = 'duration'; this.durationFormat = 'duration';
this.isUTCBased = true; this.isUTCBased = false;
} }
return LocalTimeSystem; return LocalTimeSystem;

View File

@ -1,82 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import uuid from 'uuid';
export default class NewFolderAction {
constructor(openmct) {
this.name = 'Add New Folder';
this.key = 'newFolder';
this.description = 'Create a new folder';
this.cssClass = 'icon-folder-new';
this._openmct = openmct;
this._dialogForm = {
name: "Add New Folder",
sections: [
{
rows: [
{
key: "name",
control: "textfield",
name: "Folder Name",
pattern: "\\S+",
required: true,
cssClass: "l-input-lg"
}
]
}
]
};
}
invoke(objectPath) {
let domainObject = objectPath[0],
parentKeystring = this._openmct.objects.makeKeyString(domainObject.identifier),
composition = this._openmct.composition.get(domainObject),
dialogService = this._openmct.$injector.get('dialogService'),
folderType = this._openmct.types.get('folder');
dialogService.getUserInput(this._dialogForm, {name: 'Unnamed Folder'}).then((userInput) => {
let name = userInput.name,
identifier = {
key: uuid(),
namespace: domainObject.identifier.namespace
},
objectModel = {
identifier,
type: 'folder',
location: parentKeystring
};
folderType.definition.initialize(objectModel);
objectModel.name = name || 'New Folder';
this._openmct.objects.mutate(objectModel, 'created', Date.now());
composition.add(objectModel);
});
}
appliesTo(objectPath) {
let domainObject = objectPath[0];
return domainObject.type === 'folder';
}
}

View File

@ -1,28 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import NewFolderAction from './newFolderAction';
export default function () {
return function (openmct) {
openmct.contextMenu.registerAction(new NewFolderAction(openmct));
};
}

View File

@ -1,99 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
describe("the plugin", () => {
let openmct,
compositionAPI,
newFolderAction,
mockObjectPath,
mockDialogService,
mockComposition,
mockPromise,
newFolderName = 'New Folder';
beforeEach((done) => {
openmct = createOpenMct();
openmct.on('start', done);
openmct.startHeadless();
newFolderAction = openmct.contextMenu._allActions.filter(action => {
return action.key === 'newFolder';
})[0];
});
afterEach(() => {
resetApplicationState(openmct);
});
it('installs the new folder action', () => {
expect(newFolderAction).toBeDefined();
});
describe('when invoked', () => {
beforeEach((done) => {
compositionAPI = openmct.composition;
mockObjectPath = [{
name: 'mock folder',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
}
}];
mockPromise = {
then: (callback) => {
callback({name: newFolderName});
done();
}
};
mockDialogService = jasmine.createSpyObj('dialogService', ['getUserInput']);
mockComposition = jasmine.createSpyObj('composition', ['add']);
mockDialogService.getUserInput.and.returnValue(mockPromise);
spyOn(openmct.$injector, 'get').and.returnValue(mockDialogService);
spyOn(compositionAPI, 'get').and.returnValue(mockComposition);
spyOn(openmct.objects, 'mutate');
newFolderAction.invoke(mockObjectPath);
});
it('gets user input for folder name', () => {
expect(mockDialogService.getUserInput).toHaveBeenCalled();
});
it('creates a new folder object', () => {
expect(openmct.objects.mutate).toHaveBeenCalled();
});
it('adds new folder object to parent composition', () => {
expect(mockComposition.add).toHaveBeenCalled();
});
});
});

View File

@ -60,7 +60,6 @@ export default {
}, },
mounted() { mounted() {
this.addPopupMenuItems(); this.addPopupMenuItems();
this.exportImageService = this.openmct.$injector.get('exportImageService');
}, },
methods: { methods: {
addPopupMenuItems() { addPopupMenuItems() {
@ -206,7 +205,7 @@ export default {
}, },
openSnapshot() { openSnapshot() {
const self = this; const self = this;
this.snapshot = new Vue({ const snapshot = new Vue({
data: () => { data: () => {
return { return {
embed: self.embed embed: self.embed
@ -214,15 +213,14 @@ export default {
}, },
methods: { methods: {
formatTime: self.formatTime, formatTime: self.formatTime,
annotateSnapshot: self.annotateSnapshot, annotateSnapshot: self.annotateSnapshot
exportImage: self.exportImage
}, },
template: SnapshotTemplate template: SnapshotTemplate
}); });
const snapshotOverlay = this.openmct.overlays.overlay({ const snapshotOverlay = this.openmct.overlays.overlay({
element: this.snapshot.$mount().$el, element: snapshot.$mount().$el,
onDestroy: () => { this.snapshot.$destroy(true) }, onDestroy: () => { snapshot.$destroy(true) },
size: 'large', size: 'large',
dismissable: true, dismissable: true,
buttons: [ buttons: [
@ -236,15 +234,6 @@ export default {
] ]
}); });
}, },
exportImage(type) {
let element = this.snapshot.$refs['snapshot-image'];
if (type === 'png') {
this.exportImageService.exportPNG(element, this.embed.name);
} else {
this.exportImageService.exportJPG(element, this.embed.name);
}
},
previewEmbed() { previewEmbed() {
const self = this; const self = this;
const previewAction = new PreviewAction(self.openmct); const previewAction = new PreviewAction(self.openmct);

View File

@ -2,16 +2,13 @@
<div class="c-snapshots-h"> <div class="c-snapshots-h">
<div class="l-browse-bar"> <div class="l-browse-bar">
<div class="l-browse-bar__start"> <div class="l-browse-bar__start">
<div class="l-browse-bar__object-name--w"> <div class="l-browse-bar__object-name--w icon-notebook">
<div class="l-browse-bar__object-name c-object-label"> <div class="l-browse-bar__object-name">
<div class="c-object-label__type-icon icon-notebook"></div> Notebook Snapshots
<div class="c-object-label__name"> <span v-if="snapshots.length"
Notebook Snapshots class="l-browse-bar__object-details"
<span v-if="snapshots.length" >&nbsp;{{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
class="l-browse-bar__object-details" </span>
>&nbsp;{{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
</span>
</div>
</div> </div>
<PopupMenu v-if="snapshots.length > 0" <PopupMenu v-if="snapshots.length > 0"
:popup-menu-items="popupMenuItems" :popup-menu-items="popupMenuItems"

View File

@ -15,32 +15,14 @@
<div class="l-browse-bar__snapshot-datetime"> <div class="l-browse-bar__snapshot-datetime">
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}} SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
</div> </div>
<span class="c-button-set c-button-set--strip-h">
<button
class="c-button icon-download"
title="Export This View's Data as PNG"
@click="exportImage('png')"
>
<span class="c-button__label">PNG</span>
</button>
<button
class="c-button"
title="Export This View's Data as JPG"
@click="exportImage('jpg')"
>
<span class="c-button__label">JPG</span>
</button>
</span>
<a class="l-browse-bar__annotate-button c-button icon-pencil" title="Annotate" @click="annotateSnapshot"> <a class="l-browse-bar__annotate-button c-button icon-pencil" title="Annotate" @click="annotateSnapshot">
<span class="title-label">Annotate</span> <span class="title-label">Annotate</span>
</a> </a>
</div> </div>
</div> </div>
<div <div class="c-notebook-snapshot__image"
ref="snapshot-image" :style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
class="c-notebook-snapshot__image"
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
> >
</div> </div>
</div> </div>

View File

@ -28,22 +28,17 @@
ng-click="legend.set('expanded', !legend.get('expanded'));"> ng-click="legend.set('expanded', !legend.get('expanded'));">
</div> </div>
<div class="c-plot-legend__wrapper" <div class="c-plot-legend__wrapper">
ng-class="{ 'is-cursor-locked': !!lockHighlightPoint }">
<!-- COLLAPSED PLOT LEGEND --> <!-- COLLAPSED PLOT LEGEND -->
<div class="plot-wrapper-collapsed-legend" <div class="plot-wrapper-collapsed-legend"
ng-class="{'is-cursor-locked': !!lockHighlightPoint }"> ng-class="{'icon-cursor-lock': !!lockHighlightPoint}">
<div class="c-state-indicator__alert-cursor-lock icon-cursor-lock" title="Cursor is point locked. Click anywhere in the plot to unlock."></div>
<div class="plot-legend-item" <div class="plot-legend-item"
ng-class="{'is-missing': series.domainObject.status === 'missing'}" ng-repeat="series in series track by $index">
ng-repeat="series in series track by $index"
>
<div class="plot-series-swatch-and-name"> <div class="plot-series-swatch-and-name">
<span class="plot-series-color-swatch " <span class="plot-series-color-swatch"
ng-style="{ 'background-color': series.get('color').asHexString() }"> ng-style="{ 'background-color': series.get('color').asHexString() }">
</span> </span>
<span class="is-missing__indicator" title="This item is missing"></span>
<span class="plot-series-name">{{ series.get('name') }}</span> <span class="plot-series-name">{{ series.get('name') }}</span>
</div> </div>
<div class="plot-series-value hover-value-enabled value-to-display-{{ legend.get('valueToShowWhenCollapsed') }} {{ series.closest.mctLimitState.cssClass }}" <div class="plot-series-value hover-value-enabled value-to-display-{{ legend.get('valueToShowWhenCollapsed') }} {{ series.closest.mctLimitState.cssClass }}"
@ -60,10 +55,7 @@
</div> </div>
<!-- EXPANDED PLOT LEGEND --> <!-- EXPANDED PLOT LEGEND -->
<div class="plot-wrapper-expanded-legend" <div class="plot-wrapper-expanded-legend">
ng-class="{'is-cursor-locked': !!lockHighlightPoint }"
>
<div class="c-state-indicator__alert-cursor-lock--verbose icon-cursor-lock" title="Click anywhere in the plot to unlock."> Cursor locked to point</div>
<table> <table>
<thead> <thead>
<tr> <tr>
@ -84,15 +76,12 @@
</th> </th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="series in series" <tr ng-repeat="series in series" class="plot-legend-item">
class="plot-legend-item" <td class="plot-series-swatch-and-name"
ng-class="{'is-missing': series.domainObject.status === 'missing'}" ng-class="{'icon-cursor-lock': !!lockHighlightPoint}">
>
<td class="plot-series-swatch-and-name">
<span class="plot-series-color-swatch" <span class="plot-series-color-swatch"
ng-style="{ 'background-color': series.get('color').asHexString() }"> ng-style="{ 'background-color': series.get('color').asHexString() }">
</span> </span>
<span class="is-missing__indicator" title="This item is missing"></span>
<span class="plot-series-name">{{ series.get('name') }}</span> <span class="plot-series-name">{{ series.get('name') }}</span>
</td> </td>
@ -145,7 +134,7 @@
{{option.name}} {{option.name}}
</option> </option>
</select> </select>
<mct-ticks axis="yAxis"> <mct-ticks axis="yAxis">
<div ng-repeat="tick in ticks track by tick.value" <div ng-repeat="tick in ticks track by tick.value"

View File

@ -44,7 +44,7 @@
</li> </li>
<li class="grid-row"> <li class="grid-row">
<div class="grid-cell label" <div class="grid-cell label"
title="The rendering method to join lines for this series.">Line Method</div> title="The line rendering style for this series.">Line Style</div>
<div class="grid-cell value">{{ { <div class="grid-cell value">{{ {
'none': 'None', 'none': 'None',
'linear': 'Linear interpolation', 'linear': 'Linear interpolation',
@ -56,7 +56,7 @@
<div class="grid-cell label" <div class="grid-cell label"
title="Whether markers are displayed, and their size.">Markers</div> title="Whether markers are displayed, and their size.">Markers</div>
<div class="grid-cell value"> <div class="grid-cell value">
{{ series.markerOptionsDisplayText() }} {{series.get('markers') ? "Enabled: " + series.get('markerSize') + "px" : "Disabled"}}
</div> </div>
</li> </li>
<li class="grid-row"> <li class="grid-row">

View File

@ -52,7 +52,7 @@
</li> </li>
<li class="grid-row"> <li class="grid-row">
<div class="grid-cell label" <div class="grid-cell label"
title="The rendering method to join lines for this series.">Line Method</div> title="The line rendering style for this series.">Line Style</div>
<div class="grid-cell value"> <div class="grid-cell value">
<select ng-model="form.interpolate"> <select ng-model="form.interpolate">
<option value="none">None</option> <option value="none">None</option>
@ -64,27 +64,12 @@
<li class="grid-row"> <li class="grid-row">
<div class="grid-cell label" <div class="grid-cell label"
title="Whether markers are displayed.">Markers</div> title="Whether markers are displayed.">Markers</div>
<div class="grid-cell value"> <div class="grid-cell value"><input type="checkbox" ng-model="form.markers"/></div>
<input type="checkbox" ng-model="form.markers"/>
<select
ng-show="form.markers"
ng-model="form.markerShape">
<option
ng-repeat="option in markerShapeOptions"
value="{{ option.value }}"
ng-selected="option.value == form.markerShape"
>
{{ option.name }}
</option>
</select>
</div>
</li> </li>
<li class="grid-row"> <li class="grid-row">
<div class="grid-cell label" <div class="grid-cell label"
title="Display markers visually denoting points in alarm.">Alarm Markers</div> title="Display markers visually denoting points in alarm.">Alarm Markers</div>
<div class="grid-cell value"> <div class="grid-cell value"><input type="checkbox" ng-model="form.alarmMarkers"/></div>
<input type="checkbox" ng-model="form.alarmMarkers"/>
</div>
</li> </li>
<li class="grid-row" ng-show="form.markers || form.alarmMarkers"> <li class="grid-row" ng-show="form.markers || form.alarmMarkers">
<div class="grid-cell label" <div class="grid-cell label"

View File

@ -19,12 +19,12 @@
this source code distribution or the Licensing information page available this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<div ng-if="!domainObject.model.locked && domainObject.getCapability('editor').inEditContext()"> <div ng-if="domainObject.getCapability('editor').inEditContext()">
<mct-representation key="'plot-options-edit'" <mct-representation key="'plot-options-edit'"
mct-object="domainObject"> mct-object="domainObject">
</mct-representation> </mct-representation>
</div> </div>
<div ng-if="domainObject.model.locked || !domainObject.getCapability('editor').inEditContext()"> <div ng-if="!domainObject.getCapability('editor').inEditContext()">
<mct-representation key="'plot-options-browse'" <mct-representation key="'plot-options-browse'"
mct-object="domainObject"> mct-object="domainObject">
</mct-representation> </mct-representation>

View File

@ -30,7 +30,8 @@ define([
'./MCTChartPointSet', './MCTChartPointSet',
'./MCTChartAlarmPointSet', './MCTChartAlarmPointSet',
'../draw/DrawLoader', '../draw/DrawLoader',
'../lib/eventHelpers' '../lib/eventHelpers',
'lodash'
], ],
function ( function (
MCTChartLineLinear, MCTChartLineLinear,
@ -38,7 +39,8 @@ function (
MCTChartPointSet, MCTChartPointSet,
MCTChartAlarmPointSet, MCTChartAlarmPointSet,
DrawLoader, DrawLoader,
eventHelpers eventHelpers,
_
) { ) {
var MARKER_SIZE = 6.0, var MARKER_SIZE = 6.0,
@ -371,8 +373,7 @@ function (
chartElement.getBuffer(), chartElement.getBuffer(),
chartElement.color().asRGBAArray(), chartElement.color().asRGBAArray(),
chartElement.count, chartElement.count,
chartElement.series.get('markerSize'), chartElement.series.get('markerSize')
chartElement.series.get('markerShape')
); );
}; };
@ -396,10 +397,9 @@ function (
this.offset.yVal(highlight.point, highlight.series) this.offset.yVal(highlight.point, highlight.series)
]), ]),
color = highlight.series.get('color').asRGBAArray(), color = highlight.series.get('color').asRGBAArray(),
pointCount = 1, pointCount = 1;
shape = highlight.series.get('markerShape');
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE, shape); this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE);
}; };
MCTChartController.prototype.drawRectangles = function () { MCTChartController.prototype.drawRectangles = function () {

View File

@ -22,11 +22,13 @@
/*global define*/ /*global define*/
define([ define([
'lodash',
'EventEmitter', 'EventEmitter',
'./Model', './Model',
'../lib/extend', '../lib/extend',
'../lib/eventHelpers' '../lib/eventHelpers'
], function ( ], function (
_,
EventEmitter, EventEmitter,
Model, Model,
extend, extend,

View File

@ -25,14 +25,12 @@ define([
'lodash', 'lodash',
'../configuration/Model', '../configuration/Model',
'../lib/extend', '../lib/extend',
'EventEmitter', 'EventEmitter'
'../draw/MarkerShapes'
], function ( ], function (
_, _,
Model, Model,
extend, extend,
EventEmitter, EventEmitter
MARKER_SHAPES
) { ) {
/** /**
@ -58,7 +56,6 @@ define([
* `linear` (points are connected via straight lines), or * `linear` (points are connected via straight lines), or
* `stepAfter` (points are connected by steps). * `stepAfter` (points are connected by steps).
* `markers`: boolean, whether or not this series should render with markers. * `markers`: boolean, whether or not this series should render with markers.
* `markerShape`: string, shape of markers.
* `markerSize`: number, size in pixels of markers for this series. * `markerSize`: number, size in pixels of markers for this series.
* `alarmMarkers`: whether or not to display alarm markers for this series. * `alarmMarkers`: whether or not to display alarm markers for this series.
* `stats`: An object that tracks the min and max y values observed in this * `stats`: An object that tracks the min and max y values observed in this
@ -104,7 +101,6 @@ define([
xKey: options.collection.plot.xAxis.get('key'), xKey: options.collection.plot.xAxis.get('key'),
yKey: range.key, yKey: range.key,
markers: true, markers: true,
markerShape: 'point',
markerSize: 2.0, markerSize: 2.0,
alarmMarkers: true alarmMarkers: true
}; };
@ -414,18 +410,6 @@ define([
} else { } else {
this.filters = deepCopiedFilters; this.filters = deepCopiedFilters;
} }
},
markerOptionsDisplayText: function () {
const showMarkers = this.get('markers');
if (!showMarkers) {
return "Disabled";
}
const markerShapeKey = this.get('markerShape');
const markerShape = MARKER_SHAPES[markerShapeKey].label;
const markerSize = this.get('markerSize');
return `${markerShape}: ${markerSize}px`;
} }
}); });

View File

@ -22,13 +22,13 @@
define([ define([
'lodash',
'EventEmitter', 'EventEmitter',
'../lib/eventHelpers', '../lib/eventHelpers'
'./MarkerShapes'
], function ( ], function (
_,
EventEmitter, EventEmitter,
eventHelpers, eventHelpers
MARKER_SHAPES
) { ) {
/** /**
@ -123,17 +123,18 @@ define([
buf, buf,
color, color,
points, points,
pointSize, pointSize
shape
) { ) {
const drawC2DShape = MARKER_SHAPES[shape].drawC2D.bind(this); var i = 0,
offset = pointSize / 2;
this.setColor(color); this.setColor(color);
for (let i = 0; i < points; i++) { for (; i < points; i++) {
drawC2DShape( this.c2d.fillRect(
this.x(buf[i * 2]), this.x(buf[i * 2]) - offset,
this.y(buf[i * 2 + 1]), this.y(buf[i * 2 + 1]) - offset,
pointSize,
pointSize pointSize
); );
} }

View File

@ -22,65 +22,33 @@
define([ define([
'lodash',
'EventEmitter', 'EventEmitter',
'../lib/eventHelpers', '../lib/eventHelpers'
'./MarkerShapes'
], function ( ], function (
_,
EventEmitter, EventEmitter,
eventHelpers, eventHelpers
MARKER_SHAPES
) { ) {
// WebGL shader sources (for drawing plain colors) // WebGL shader sources (for drawing plain colors)
const FRAGMENT_SHADER = ` var FRAGMENT_SHADER = [
precision mediump float; "precision mediump float;",
uniform vec4 uColor; "uniform vec4 uColor;",
uniform int uMarkerShape; "void main(void) {",
"gl_FragColor = uColor;",
void main(void) { "}"
gl_FragColor = uColor; ].join('\n'),
VERTEX_SHADER = [
if (uMarkerShape > 1) { "attribute vec2 aVertexPosition;",
vec2 clipSpacePointCoord = 2.0 * gl_PointCoord - 1.0; "uniform vec2 uDimensions;",
"uniform vec2 uOrigin;",
if (uMarkerShape == 2) { // circle "uniform float uPointSize;",
float distance = length(clipSpacePointCoord); "void main(void) {",
"gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);",
if (distance > 1.0) { "gl_PointSize = uPointSize;",
discard; "}"
} ].join('\n');
} else if (uMarkerShape == 3) { // diamond
float distance = abs(clipSpacePointCoord.x) + abs(clipSpacePointCoord.y);
if (distance > 1.0) {
discard;
}
} else if (uMarkerShape == 4) { // triangle
float x = clipSpacePointCoord.x;
float y = clipSpacePointCoord.y;
float distance = 2.0 * x - 1.0;
float distance2 = -2.0 * x - 1.0;
if (distance > y || distance2 > y) {
discard;
}
}
}
}
`;
const VERTEX_SHADER = `
attribute vec2 aVertexPosition;
uniform vec2 uDimensions;
uniform vec2 uOrigin;
uniform float uPointSize;
void main(void) {
gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);
gl_PointSize = uPointSize;
}
`;
/** /**
* Create a draw api utilizing WebGL. * Create a draw api utilizing WebGL.
@ -124,7 +92,6 @@ define([
this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER); this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
this.gl.shaderSource(this.vertexShader, VERTEX_SHADER); this.gl.shaderSource(this.vertexShader, VERTEX_SHADER);
this.gl.compileShader(this.vertexShader); this.gl.compileShader(this.vertexShader);
this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER); this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
this.gl.shaderSource(this.fragmentShader, FRAGMENT_SHADER); this.gl.shaderSource(this.fragmentShader, FRAGMENT_SHADER);
this.gl.compileShader(this.fragmentShader); this.gl.compileShader(this.fragmentShader);
@ -140,7 +107,6 @@ define([
// shader programs (to pass values into shaders at draw-time) // shader programs (to pass values into shaders at draw-time)
this.aVertexPosition = this.gl.getAttribLocation(this.program, "aVertexPosition"); this.aVertexPosition = this.gl.getAttribLocation(this.program, "aVertexPosition");
this.uColor = this.gl.getUniformLocation(this.program, "uColor"); this.uColor = this.gl.getUniformLocation(this.program, "uColor");
this.uMarkerShape = this.gl.getUniformLocation(this.program, "uMarkerShape");
this.uDimensions = this.gl.getUniformLocation(this.program, "uDimensions"); this.uDimensions = this.gl.getUniformLocation(this.program, "uDimensions");
this.uOrigin = this.gl.getUniformLocation(this.program, "uOrigin"); this.uOrigin = this.gl.getUniformLocation(this.program, "uOrigin");
this.uPointSize = this.gl.getUniformLocation(this.program, "uPointSize"); this.uPointSize = this.gl.getUniformLocation(this.program, "uPointSize");
@ -150,6 +116,9 @@ define([
// Create a buffer to holds points which will be drawn // Create a buffer to holds points which will be drawn
this.buffer = this.gl.createBuffer(); this.buffer = this.gl.createBuffer();
// Use a line width of 2.0 for legibility
this.gl.lineWidth(2.0);
// Enable blending, for smoothness // Enable blending, for smoothness
this.gl.enable(this.gl.BLEND); this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
@ -171,18 +140,14 @@ define([
((v - this.origin[1]) / this.dimensions[1]) * this.height; ((v - this.origin[1]) / this.dimensions[1]) * this.height;
}; };
DrawWebGL.prototype.doDraw = function (drawType, buf, color, points, shape) { DrawWebGL.prototype.doDraw = function (drawType, buf, color, points) {
if (this.isContextLost) { if (this.isContextLost) {
return; return;
} }
const shapeCode = MARKER_SHAPES[shape] ? MARKER_SHAPES[shape].drawWebGL : 0;
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, buf, this.gl.DYNAMIC_DRAW); this.gl.bufferData(this.gl.ARRAY_BUFFER, buf, this.gl.DYNAMIC_DRAW);
this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0); this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0);
this.gl.uniform4fv(this.uColor, color); this.gl.uniform4fv(this.uColor, color);
this.gl.uniform1i(this.uMarkerShape, shapeCode)
this.gl.drawArrays(drawType, 0, points); this.gl.drawArrays(drawType, 0, points);
}; };
@ -247,12 +212,12 @@ define([
* Draw the buffer as points. * Draw the buffer as points.
* *
*/ */
DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize, shape) { DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize) {
if (this.isContextLost) { if (this.isContextLost) {
return; return;
} }
this.gl.uniform1f(this.uPointSize, pointSize); this.gl.uniform1f(this.uPointSize, pointSize);
this.doDraw(this.gl.POINTS, buf, color, points, shape); this.doDraw(this.gl.POINTS, buf, color, points);
}; };
/** /**

View File

@ -1,90 +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.
*****************************************************************************/
define([], function () {
/**
* @label string (required) display name of shape
* @drawWebGL integer (unique, required) index provided to WebGL Fragment Shader
* @drawC2D function (required) canvas2d draw function
*/
const MARKER_SHAPES = {
point: {
label: 'Point',
drawWebGL: 1,
drawC2D: function (x, y, size) {
const offset = size / 2;
this.c2d.fillRect(x - offset, y - offset, size, size);
}
},
circle: {
label: 'Circle',
drawWebGL: 2,
drawC2D: function (x, y, size) {
const radius = size / 2;
this.c2d.beginPath();
this.c2d.arc(x, y, radius, 0, 2 * Math.PI, false);
this.c2d.closePath();
this.c2d.fill();
}
},
diamond: {
label: 'Diamond',
drawWebGL: 3,
drawC2D: function (x, y, size) {
const offset = size / 2;
const top = [x, y + offset];
const right = [x + offset, y];
const bottom = [x, y - offset];
const left = [x - offset, y];
this.c2d.beginPath();
this.c2d.moveTo(...top);
this.c2d.lineTo(...right);
this.c2d.lineTo(...bottom);
this.c2d.lineTo(...left);
this.c2d.closePath();
this.c2d.fill();
}
},
triangle: {
label: 'Triangle',
drawWebGL: 4,
drawC2D: function (x, y, size) {
const offset = size / 2;
const v1 = [x, y - offset];
const v2 = [x - offset, y + offset];
const v3 = [x + offset, y + offset];
this.c2d.beginPath();
this.c2d.moveTo(...v1);
this.c2d.lineTo(...v2);
this.c2d.lineTo(...v3);
this.c2d.closePath();
this.c2d.fill();
}
}
};
return MARKER_SHAPES;
});

View File

@ -23,11 +23,13 @@
define([ define([
'../configuration/configStore', '../configuration/configStore',
'../lib/eventHelpers', '../lib/eventHelpers',
'objectUtils' 'objectUtils',
'lodash'
], function ( ], function (
configStore, configStore,
eventHelpers, eventHelpers,
objectUtils objectUtils,
_
) { ) {
function PlotOptionsController($scope, openmct, $timeout) { function PlotOptionsController($scope, openmct, $timeout) {

View File

@ -22,11 +22,9 @@
define([ define([
'./PlotModelFormController', './PlotModelFormController',
'../draw/MarkerShapes',
'lodash' 'lodash'
], function ( ], function (
PlotModelFormController, PlotModelFormController,
MARKER_SHAPES,
_ _
) { ) {
@ -95,13 +93,6 @@ define([
value: o.key value: o.key
}; };
}); });
this.$scope.markerShapeOptions = Object.entries(MARKER_SHAPES)
.map(([key, obj]) => {
return {
name: obj.label,
value: key
};
});
}, },
fields: [ fields: [
@ -117,10 +108,6 @@ define([
modelProp: 'markers', modelProp: 'markers',
objectPath: dynamicPathForKey('markers') objectPath: dynamicPathForKey('markers')
}, },
{
modelProp: 'markerShape',
objectPath: dynamicPathForKey('markerShape')
},
{ {
modelProp: 'markerSize', modelProp: 'markerSize',
coerce: Number, coerce: Number,

View File

@ -21,9 +21,11 @@
*****************************************************************************/ *****************************************************************************/
define([ define([
'./PlotModelFormController' './PlotModelFormController',
'lodash'
], function ( ], function (
PlotModelFormController PlotModelFormController,
_
) { ) {
var PlotYAxisFormController = PlotModelFormController.extend({ var PlotYAxisFormController = PlotModelFormController.extend({

View File

@ -71,6 +71,8 @@ define([
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this); this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this); this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this); this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this);
this.watchForMarquee();
}; };
MCTPlotController.prototype.initialize = function () { MCTPlotController.prototype.initialize = function () {
@ -81,6 +83,11 @@ define([
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this); this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this); this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this);
this.watchForMarquee();
this.listenTo(this.$window, 'keydown', this.toggleInteractionMode, this);
this.listenTo(this.$window, 'keyup', this.resetInteractionMode, this);
this.$scope.rectangles = []; this.$scope.rectangles = [];
this.$scope.tickWidth = 0; this.$scope.tickWidth = 0;
@ -236,16 +243,12 @@ define([
}; };
MCTPlotController.prototype.onMouseDown = function ($event) { MCTPlotController.prototype.onMouseDown = function ($event) {
// do not monitor drag events on browser context click
if (event.ctrlKey) {
return;
}
this.listenTo(this.$window, 'mouseup', this.onMouseUp, this); this.listenTo(this.$window, 'mouseup', this.onMouseUp, this);
this.listenTo(this.$window, 'mousemove', this.trackMousePosition, this); this.listenTo(this.$window, 'mousemove', this.trackMousePosition, this);
if (event.altKey) { if (this.allowPan) {
return this.startPan($event); return this.startPan($event);
} else { }
if (this.allowMarquee) {
return this.startMarquee($event); return this.startMarquee($event);
} }
}; };
@ -258,11 +261,11 @@ define([
this.$scope.lockHighlightPoint = !this.$scope.lockHighlightPoint; this.$scope.lockHighlightPoint = !this.$scope.lockHighlightPoint;
} }
if (this.pan) { if (this.allowPan) {
return this.endPan($event); return this.endPan($event);
} }
if (this.marquee) { if (this.allowMarquee) {
return this.endMarquee($event); return this.endMarquee($event);
} }
}; };
@ -286,9 +289,6 @@ define([
}; };
MCTPlotController.prototype.startMarquee = function ($event) { MCTPlotController.prototype.startMarquee = function ($event) {
this.$canvas.removeClass('plot-drag');
this.$canvas.addClass('plot-marquee');
this.trackMousePosition($event); this.trackMousePosition($event);
if (this.positionOverPlot) { if (this.positionOverPlot) {
this.freeze(); this.freeze();
@ -444,9 +444,6 @@ define([
}; };
MCTPlotController.prototype.startPan = function ($event) { MCTPlotController.prototype.startPan = function ($event) {
this.$canvas.addClass('plot-drag');
this.$canvas.removeClass('plot-marquee');
this.trackMousePosition($event); this.trackMousePosition($event);
this.freeze(); this.freeze();
this.pan = { this.pan = {
@ -489,6 +486,32 @@ define([
this.$scope.$emit('user:viewport:change:end'); this.$scope.$emit('user:viewport:change:end');
}; };
MCTPlotController.prototype.watchForMarquee = function () {
this.$canvas.removeClass('plot-drag');
this.$canvas.addClass('plot-marquee');
this.allowPan = false;
this.allowMarquee = true;
};
MCTPlotController.prototype.watchForPan = function () {
this.$canvas.addClass('plot-drag');
this.$canvas.removeClass('plot-marquee');
this.allowPan = true;
this.allowMarquee = false;
};
MCTPlotController.prototype.toggleInteractionMode = function (event) {
if (event.keyCode === 18) { // control key.
this.watchForPan();
}
};
MCTPlotController.prototype.resetInteractionMode = function (event) {
if (event.keyCode === 18) {
this.watchForMarquee();
}
};
MCTPlotController.prototype.freeze = function () { MCTPlotController.prototype.freeze = function () {
this.config.yAxis.set('frozen', true); this.config.yAxis.set('frozen', true);
this.config.xAxis.set('frozen', true); this.config.xAxis.set('frozen', true);

View File

@ -20,7 +20,12 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([], function () { define([
'lodash'
], function (
_
) {
function StackedPlotController($scope, openmct, objectService, $element, exportImageService) { function StackedPlotController($scope, openmct, objectService, $element, exportImageService) {
var tickWidth = 0, var tickWidth = 0,
composition, composition,
@ -120,13 +125,12 @@ define([], function () {
$scope.$watch('domainObject.getModel().composition', onCompositionChange); $scope.$watch('domainObject.getModel().composition', onCompositionChange);
$scope.$on('plot:tickWidth', function ($e, width) { $scope.$on('plot:tickWidth', function ($e, width) {
const plotId = $e.targetScope.domainObject.getId(); var plotId = $e.targetScope.domainObject.getId();
if (!tickWidthMap.hasOwnProperty(plotId)) { if (!tickWidthMap.hasOwnProperty(plotId)) {
return; return;
} }
tickWidthMap[plotId] = Math.max(width, tickWidthMap[plotId]); tickWidthMap[plotId] = Math.max(width, tickWidthMap[plotId]);
const newTickWidth = Math.max(...Object.values(tickWidthMap)); var newTickWidth = _.max(tickWidthMap);
if (newTickWidth !== tickWidth || width !== tickWidth) { if (newTickWidth !== tickWidth || width !== tickWidth) {
tickWidth = newTickWidth; tickWidth = newTickWidth;
$scope.$broadcast('plot:tickWidth', tickWidth); $scope.$broadcast('plot:tickWidth', tickWidth);

View File

@ -53,8 +53,7 @@ define([
'./themes/maelstrom', './themes/maelstrom',
'./themes/snow', './themes/snow',
'./URLTimeSettingsSynchronizer/plugin', './URLTimeSettingsSynchronizer/plugin',
'./notificationIndicator/plugin', './notificationIndicator/plugin'
'./newFolderAction/plugin'
], function ( ], function (
_, _,
UTCTimeSystem, UTCTimeSystem,
@ -88,8 +87,7 @@ define([
Maelstrom, Maelstrom,
Snow, Snow,
URLTimeSettingsSynchronizer, URLTimeSettingsSynchronizer,
NotificationIndicator, NotificationIndicator
NewFolderAction
) { ) {
var bundleMap = { var bundleMap = {
LocalStorage: 'platform/persistence/local', LocalStorage: 'platform/persistence/local',
@ -200,7 +198,6 @@ define([
plugins.ConditionWidget = ConditionWidgetPlugin.default; plugins.ConditionWidget = ConditionWidgetPlugin.default;
plugins.URLTimeSettingsSynchronizer = URLTimeSettingsSynchronizer.default; plugins.URLTimeSettingsSynchronizer = URLTimeSettingsSynchronizer.default;
plugins.NotificationIndicator = NotificationIndicator.default; plugins.NotificationIndicator = NotificationIndicator.default;
plugins.NewFolderAction = NewFolderAction.default;
return plugins; return plugins;
}); });

View File

@ -101,12 +101,6 @@ export default class RemoveAction {
appliesTo(objectPath) { appliesTo(objectPath) {
let parent = objectPath[1]; let parent = objectPath[1];
let parentType = parent && this.openmct.types.get(parent.type); let parentType = parent && this.openmct.types.get(parent.type);
let child = objectPath[0];
let locked = child.locked ? child.locked : parent && parent.locked;
if (locked) {
return false;
}
return parentType && return parentType &&
parentType.definition.creatable && parentType.definition.creatable &&

View File

@ -0,0 +1,7 @@
# Espresso Theme
A light colored theme for the Open MCT user interface.
## Installation
```js
openmct.install(openmct.plugins.Snow());
```

View File

@ -3,7 +3,7 @@
<div <div
class="c-tabs-view__tabs-holder c-tabs" class="c-tabs-view__tabs-holder c-tabs"
:class="{ :class="{
'is-dragging': isDragging && allowEditing, 'is-dragging': isDragging,
'is-mouse-over': allowDrop 'is-mouse-over': allowDrop
}" }"
> >
@ -22,24 +22,14 @@
<button <button
v-for="(tab,index) in tabsList" v-for="(tab,index) in tabsList"
:key="index" :key="index"
class="c-tab c-tabs-view__tab" class="c-tabs-view__tab c-tab"
:class="{ :class="[
'is-current': isCurrent(tab) {'is-current': isCurrent(tab)},
}" tab.type.definition.cssClass
]"
@click="showTab(tab, index)" @click="showTab(tab, index)"
> >
<div class="c-object-label" <span class="c-button__label">{{ tab.domainObject.name }}</span>
:class="{'is-missing': tab.domainObject.status === 'missing'}"
>
<div class="c-object-label__type-icon"
:class="tab.type.definition.cssClass"
>
<span class="is-missing__indicator"
title="This item is missing"
></span>
</div>
<span class="c-button__label c-object-label__name">{{ tab.domainObject.name }}</span>
</div>
</button> </button>
</div> </div>
<div <div
@ -48,6 +38,15 @@
class="c-tabs-view__object-holder" class="c-tabs-view__object-holder"
:class="{'c-tabs-view__object-holder--hidden': !isCurrent(tab)}" :class="{'c-tabs-view__object-holder--hidden': !isCurrent(tab)}"
> >
<div
v-if="currentTab"
class="c-tabs-view__object-name c-object-label l-browse-bar__object-name--w"
:class="currentTab.type.definition.cssClass"
>
<div class="l-browse-bar__object-name c-object-label__name">
{{ currentTab.domainObject.name }}
</div>
</div>
<object-view <object-view
v-if="internalDomainObject.keep_alive ? currentTab : isCurrent(tab)" v-if="internalDomainObject.keep_alive ? currentTab : isCurrent(tab)"
class="c-tabs-view__object" class="c-tabs-view__object"
@ -59,12 +58,6 @@
<script> <script>
import ObjectView from '../../../ui/components/ObjectView.vue'; import ObjectView from '../../../ui/components/ObjectView.vue';
import {
getSearchParam,
setSearchParam,
deleteSearchParam
} from 'utils/openmctLocation';
var unknownObjectType = { var unknownObjectType = {
definition: { definition: {
@ -78,45 +71,26 @@ export default {
components: { components: {
ObjectView ObjectView
}, },
props: {
isEditing: {
type: Boolean,
required: true
}
},
data: function () { data: function () {
let keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
return { return {
internalDomainObject: this.domainObject, internalDomainObject: this.domainObject,
currentTab: {}, currentTab: {},
currentTabIndex: undefined,
tabsList: [], tabsList: [],
setCurrentTab: true, setCurrentTab: true,
isDragging: false, isDragging: false,
allowDrop: false, allowDrop: false
searchTabKey: `tabs.pos.${keyString}`
}; };
}, },
computed: {
allowEditing() {
return !this.internalDomainObject.locked && this.isEditing;
}
},
mounted() { mounted() {
if (this.composition) { if (this.composition) {
this.composition.on('add', this.addItem); this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem); this.composition.on('remove', this.removeItem);
this.composition.on('reorder', this.onReorder); this.composition.on('reorder', this.onReorder);
this.composition.load().then(() => { this.composition.load().then(() => {
let currentTabIndexFromURL = getSearchParam(this.searchTabKey); let currentTabIndex = this.domainObject.currentTabIndex;
let currentTabIndexFromDomainObject = this.internalDomainObject.currentTabIndex;
if (currentTabIndexFromURL !== null) { if (currentTabIndex !== undefined && this.tabsList.length > currentTabIndex) {
this.setCurrentTabByIndex(currentTabIndexFromURL); this.currentTab = this.tabsList[currentTabIndex];
} else if (currentTabIndexFromDomainObject !== undefined) {
this.setCurrentTabByIndex(currentTabIndexFromDomainObject);
this.storeCurrentTabIndexInURL(currentTabIndexFromDomainObject);
} }
}); });
} }
@ -126,29 +100,20 @@ export default {
document.addEventListener('dragstart', this.dragstart); document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend); document.addEventListener('dragend', this.dragend);
}, },
beforeDestroy() {
this.persistCurrentTabIndex(this.currentTabIndex);
},
destroyed() { destroyed() {
this.composition.off('add', this.addItem); this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem); this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.onReorder); this.composition.off('reorder', this.onReorder);
this.unsubscribe(); this.unsubscribe();
this.clearCurrentTabIndexFromURL();
document.removeEventListener('dragstart', this.dragstart); document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend); document.removeEventListener('dragend', this.dragend);
}, },
methods:{ methods:{
setCurrentTabByIndex(index) {
if (this.tabsList[index]) {
this.currentTab = this.tabsList[index];
}
},
showTab(tab, index) { showTab(tab, index) {
if (index !== undefined) { if (index !== undefined) {
this.storeCurrentTabIndexInURL(index); this.storeCurrentTabIndex(index);
} }
this.currentTab = tab; this.currentTab = tab;
@ -168,10 +133,6 @@ export default {
this.setCurrentTab = false; this.setCurrentTab = false;
} }
}, },
reset() {
this.currentTab = {};
this.setCurrentTab = true;
},
removeItem(identifier) { removeItem(identifier) {
let pos = this.tabsList.findIndex(tab => let pos = this.tabsList.findIndex(tab =>
tab.domainObject.identifier.namespace === identifier.namespace && tab.domainObject.identifier.key === identifier.key tab.domainObject.identifier.namespace === identifier.namespace && tab.domainObject.identifier.key === identifier.key
@ -183,10 +144,6 @@ export default {
if (this.isCurrent(tabToBeRemoved)) { if (this.isCurrent(tabToBeRemoved)) {
this.showTab(this.tabsList[this.tabsList.length - 1], this.tabsList.length - 1); this.showTab(this.tabsList[this.tabsList.length - 1], this.tabsList.length - 1);
} }
if (!this.tabsList.length) {
this.reset();
}
}, },
onReorder(reorderPlan) { onReorder(reorderPlan) {
let oldTabs = this.tabsList.slice(); let oldTabs = this.tabsList.slice();
@ -197,7 +154,7 @@ export default {
}, },
onDrop(e) { onDrop(e) {
this.setCurrentTab = true; this.setCurrentTab = true;
this.storeCurrentTabIndexInURL(this.tabsList.length); this.storeCurrentTabIndex(this.tabsList.length);
}, },
dragstart(e) { dragstart(e) {
if (e.dataTransfer.types.includes('openmct/domain-object-path')) { if (e.dataTransfer.types.includes('openmct/domain-object-path')) {
@ -220,19 +177,8 @@ export default {
updateInternalDomainObject(domainObject) { updateInternalDomainObject(domainObject) {
this.internalDomainObject = domainObject; this.internalDomainObject = domainObject;
}, },
persistCurrentTabIndex(index) { storeCurrentTabIndex(index) {
this.openmct.objects.mutate(this.internalDomainObject, 'currentTabIndex', index); this.openmct.objects.mutate(this.internalDomainObject, 'currentTabIndex', index);
},
storeCurrentTabIndexInURL(index) {
let currentTabIndexInURL = getSearchParam(this.searchTabKey);
if (index !== currentTabIndexInURL) {
setSearchParam(this.searchTabKey, index);
this.currentTabIndex = index;
}
},
clearCurrentTabIndexFromURL() {
deleteSearchParam(this.searchTabKey);
} }
} }
} }

View File

@ -42,28 +42,20 @@ define([
let component; let component;
return { return {
show: function (element, editMode) { show: function (element) {
component = new Vue({ component = new Vue({
el: element, el: element,
components: { components: {
TabsComponent: TabsComponent.default TabsComponent: TabsComponent.default
}, },
data() {
return {
isEditing: editMode
};
},
provide: { provide: {
openmct, openmct,
domainObject, domainObject,
composition: openmct.composition.get(domainObject) composition: openmct.composition.get(domainObject)
}, },
template: '<tabs-component :isEditing="isEditing"></tabs-component>' template: '<tabs-component></tabs-component>'
}); });
}, },
onEditModeChange(editMode) {
component.isEditing = editMode;
},
destroy: function (element) { destroy: function (element) {
component.$destroy(); component.$destroy();
component = undefined; component = undefined;

View File

@ -22,12 +22,7 @@
<template> <template>
<div <div
class="c-conductor" class="c-conductor"
:class="[ :class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode']"
{ 'is-zooming': isZooming },
{ 'is-panning': isPanning },
{ 'alt-pressed': altPressed },
isFixed ? 'is-fixed-mode' : 'is-realtime-mode'
]"
> >
<form <form
ref="conductorForm" ref="conductorForm"
@ -57,7 +52,7 @@
type="text" type="text"
autocorrect="off" autocorrect="off"
spellcheck="false" spellcheck="false"
@change="validateAllBounds('startDate'); submitForm()" @change="validateAllBounds(); submitForm()"
> >
<date-picker <date-picker
v-if="isFixed && isUTCBased" v-if="isFixed && isUTCBased"
@ -97,7 +92,7 @@
autocorrect="off" autocorrect="off"
spellcheck="false" spellcheck="false"
:disabled="!isFixed" :disabled="!isFixed"
@change="validateAllBounds('endDate'); submitForm()" @change="validateAllBounds(); submitForm()"
> >
<date-picker <date-picker
v-if="isFixed && isUTCBased" v-if="isFixed && isUTCBased"
@ -127,25 +122,14 @@
<conductor-axis <conductor-axis
class="c-conductor__ticks" class="c-conductor__ticks"
:view-bounds="viewBounds" :bounds="rawBounds"
:is-fixed="isFixed" @panAxis="setViewFromBounds"
:alt-pressed="altPressed"
@endPan="endPan"
@endZoom="endZoom"
@panAxis="pan"
@zoomAxis="zoom"
/> />
</div> </div>
<div class="c-conductor__controls"> <div class="c-conductor__controls">
<!-- Mode, time system menu buttons and duration slider -->
<ConductorMode class="c-conductor__mode-select" /> <ConductorMode class="c-conductor__mode-select" />
<ConductorTimeSystem class="c-conductor__time-system-select" /> <ConductorTimeSystem class="c-conductor__time-system-select" />
<ConductorHistory
v-if="isFixed"
class="c-conductor__history-select"
:bounds="openmct.time.bounds()"
:time-system="timeSystem"
/>
</div> </div>
<input <input
type="submit" type="submit"
@ -161,7 +145,6 @@ import ConductorTimeSystem from './ConductorTimeSystem.vue';
import DatePicker from './DatePicker.vue'; import DatePicker from './DatePicker.vue';
import ConductorAxis from './ConductorAxis.vue'; import ConductorAxis from './ConductorAxis.vue';
import ConductorModeIcon from './ConductorModeIcon.vue'; import ConductorModeIcon from './ConductorModeIcon.vue';
import ConductorHistory from './ConductorHistory.vue'
const DEFAULT_DURATION_FORMATTER = 'duration'; const DEFAULT_DURATION_FORMATTER = 'duration';
@ -172,8 +155,7 @@ export default {
ConductorTimeSystem, ConductorTimeSystem,
DatePicker, DatePicker,
ConductorAxis, ConductorAxis,
ConductorModeIcon, ConductorModeIcon
ConductorHistory
}, },
data() { data() {
let bounds = this.openmct.time.bounds(); let bounds = this.openmct.time.bounds();
@ -183,7 +165,6 @@ export default {
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
return { return {
timeSystem: timeSystem,
timeFormatter: timeFormatter, timeFormatter: timeFormatter,
durationFormatter: durationFormatter, durationFormatter: durationFormatter,
offsets: { offsets: {
@ -194,68 +175,29 @@ export default {
start: timeFormatter.format(bounds.start), start: timeFormatter.format(bounds.start),
end: timeFormatter.format(bounds.end) end: timeFormatter.format(bounds.end)
}, },
viewBounds: { rawBounds: {
start: bounds.start, start: bounds.start,
end: bounds.end end: bounds.end
}, },
isFixed: this.openmct.time.clock() === undefined, isFixed: this.openmct.time.clock() === undefined,
isUTCBased: timeSystem.isUTCBased, isUTCBased: timeSystem.isUTCBased,
showDatePicker: false, showDatePicker: false
altPressed: false,
isPanning: false,
isZooming: false
} }
}, },
mounted() { mounted() {
document.addEventListener('keydown', this.handleKeyDown);
document.addEventListener('keyup', this.handleKeyUp);
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem()))); this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
this.openmct.time.on('bounds', this.setViewFromBounds); this.openmct.time.on('bounds', this.setViewFromBounds);
this.openmct.time.on('timeSystem', this.setTimeSystem); this.openmct.time.on('timeSystem', this.setTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock); this.openmct.time.on('clock', this.setViewFromClock);
this.openmct.time.on('clockOffsets', this.setViewFromOffsets) this.openmct.time.on('clockOffsets', this.setViewFromOffsets)
}, },
beforeDestroy() {
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('keyup', this.handleKeyUp);
},
methods: { methods: {
handleKeyDown(event) {
if (event.key === 'Alt') {
this.altPressed = true;
}
},
handleKeyUp(event) {
if (event.key === 'Alt') {
this.altPressed = false;
}
},
pan(bounds) {
this.isPanning = true;
this.setViewFromBounds(bounds);
},
endPan(bounds) {
this.isPanning = false;
if (bounds) {
this.openmct.time.bounds(bounds);
}
},
zoom(bounds) {
this.isZooming = true;
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
},
endZoom(bounds) {
const _bounds = bounds ? bounds : this.openmct.time.bounds();
this.isZooming = false;
this.openmct.time.bounds(_bounds);
},
setTimeSystem(timeSystem) { setTimeSystem(timeSystem) {
this.timeSystem = timeSystem
this.timeFormatter = this.getFormatter(timeSystem.timeFormat); this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
this.durationFormatter = this.getFormatter( this.durationFormatter = this.getFormatter(
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
this.isUTCBased = timeSystem.isUTCBased; this.isUTCBased = timeSystem.isUTCBased;
}, },
setOffsetsFromView($event) { setOffsetsFromView($event) {
@ -295,8 +237,8 @@ export default {
setViewFromBounds(bounds) { setViewFromBounds(bounds) {
this.formattedBounds.start = this.timeFormatter.format(bounds.start); this.formattedBounds.start = this.timeFormatter.format(bounds.start);
this.formattedBounds.end = this.timeFormatter.format(bounds.end); this.formattedBounds.end = this.timeFormatter.format(bounds.end);
this.viewBounds.start = bounds.start; this.rawBounds.start = bounds.start;
this.viewBounds.end = bounds.end; this.rawBounds.end = bounds.end;
}, },
setViewFromOffsets(offsets) { setViewFromOffsets(offsets) {
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start)); this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
@ -309,15 +251,6 @@ export default {
this.setOffsetsFromView(); this.setOffsetsFromView();
} }
}, },
getBoundsLimit() {
const configuration = this.configuration.menuOptions
.filter(option => option.timeSystem === this.timeSystem.key)
.find(option => option.limit);
const limit = configuration ? configuration.limit : undefined;
return limit;
},
clearAllValidation() { clearAllValidation() {
if (this.isFixed) { if (this.isFixed) {
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput); [this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
@ -329,52 +262,36 @@ export default {
input.setCustomValidity(''); input.setCustomValidity('');
input.title = ''; input.title = '';
}, },
validateAllBounds(ref) { validateAllBounds() {
if (!this.areBoundsFormatsValid()) {
return false;
}
let validationResult = true;
const currentInput = this.$refs[ref];
return [this.$refs.startDate, this.$refs.endDate].every((input) => { return [this.$refs.startDate, this.$refs.endDate].every((input) => {
let boundsValues = { let validationResult = true;
start: this.timeFormatter.parse(this.formattedBounds.start), let formattedDate;
end: this.timeFormatter.parse(this.formattedBounds.end)
};
const limit = this.getBoundsLimit();
if ( if (input === this.$refs.startDate) {
this.timeSystem.isUTCBased formattedDate = this.formattedBounds.start;
&& limit
&& boundsValues.end - boundsValues.start > limit
) {
if (input === currentInput) {
validationResult = "Start and end difference exceeds allowable limit";
}
} else { } else {
if (input === currentInput) { formattedDate = this.formattedBounds.end;
validationResult = this.openmct.time.validateBounds(boundsValues);
}
} }
return this.handleValidationResults(input, validationResult);
});
},
areBoundsFormatsValid() {
let validationResult = true;
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
const formattedDate = input === this.$refs.startDate
? this.formattedBounds.start
: this.formattedBounds.end
;
if (!this.timeFormatter.validate(formattedDate)) { if (!this.timeFormatter.validate(formattedDate)) {
validationResult = 'Invalid date'; validationResult = 'Invalid date';
} else {
let boundsValues = {
start: this.timeFormatter.parse(this.formattedBounds.start),
end: this.timeFormatter.parse(this.formattedBounds.end)
};
validationResult = this.openmct.time.validateBounds(boundsValues);
} }
return this.handleValidationResults(input, validationResult); if (validationResult !== true) {
input.setCustomValidity(validationResult);
input.title = validationResult;
return false;
} else {
input.setCustomValidity('');
input.title = '';
return true;
}
}); });
}, },
validateAllOffsets(event) { validateAllOffsets(event) {
@ -398,20 +315,17 @@ export default {
validationResult = this.openmct.time.validateOffsets(offsetValues); validationResult = this.openmct.time.validateOffsets(offsetValues);
} }
return this.handleValidationResults(input, validationResult); if (validationResult !== true) {
input.setCustomValidity(validationResult);
input.title = validationResult;
return false;
} else {
input.setCustomValidity('');
input.title = '';
return true;
}
}); });
}, },
handleValidationResults(input, validationResult) {
if (validationResult !== true) {
input.setCustomValidity(validationResult);
input.title = validationResult;
return false;
} else {
input.setCustomValidity('');
input.title = '';
return true;
}
},
submitForm() { submitForm() {
// Allow Vue model to catch up to user input. // Allow Vue model to catch up to user input.
// Submitting form will cause validation messages to display (but only if triggered by button click) // Submitting form will cause validation messages to display (but only if triggered by button click)
@ -424,12 +338,12 @@ export default {
}, },
startDateSelected(date) { startDateSelected(date) {
this.formattedBounds.start = this.timeFormatter.format(date); this.formattedBounds.start = this.timeFormatter.format(date);
this.validateAllBounds('startDate'); this.validateAllBounds();
this.submitForm(); this.submitForm();
}, },
endDateSelected(date) { endDateSelected(date) {
this.formattedBounds.end = this.timeFormatter.format(date); this.formattedBounds.end = this.timeFormatter.format(date);
this.validateAllBounds('endDate'); this.validateAllBounds();
this.submitForm(); this.submitForm();
} }
} }

View File

@ -24,12 +24,7 @@
ref="axisHolder" ref="axisHolder"
class="c-conductor-axis" class="c-conductor-axis"
@mousedown="dragStart($event)" @mousedown="dragStart($event)"
> ></div>
<div
class="c-conductor-axis__zoom-indicator"
:style="zoomStyle"
></div>
</div>
</template> </template>
<script> <script>
@ -48,81 +43,52 @@ const PIXELS_PER_TICK_WIDE = 200;
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { props: {
viewBounds: { bounds: {
type: Object, type: Object,
required: true required: true
},
isFixed: {
type: Boolean,
required: true
},
altPressed: {
type: Boolean,
required: true
}
},
data() {
return {
inPanMode: false,
dragStartX: undefined,
dragX: undefined,
zoomStyle: {}
}
},
computed: {
inZoomMode() {
return !this.inPanMode;
} }
}, },
watch: { watch: {
viewBounds: { bounds: {
handler() { handler(bounds) {
this.setScale(); this.setScale();
}, },
deep: true deep: true
} }
}, },
mounted() { mounted() {
let vis = d3Selection.select(this.$refs.axisHolder).append("svg:svg"); let axisHolder = this.$refs.axisHolder;
let height = axisHolder.offsetHeight;
let vis = d3Selection.select(axisHolder)
.append("svg:svg")
.attr("width", "100%")
.attr("height", height);
this.width = this.$refs.axisHolder.clientWidth;
this.xAxis = d3Axis.axisTop(); this.xAxis = d3Axis.axisTop();
this.dragging = false; this.dragging = false;
// draw x axis with labels. CSS is used to position them. // draw x axis with labels. CSS is used to position them.
this.axisElement = vis.append("g") this.axisElement = vis.append("g");
.attr("class", "axis");
this.setViewFromTimeSystem(this.openmct.time.timeSystem()); this.setViewFromTimeSystem(this.openmct.time.timeSystem());
this.setAxisDimensions();
this.setScale(); this.setScale();
//Respond to changes in conductor //Respond to changes in conductor
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem); this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
setInterval(this.resize, RESIZE_POLL_INTERVAL); setInterval(this.resize, RESIZE_POLL_INTERVAL);
}, },
destroyed() {
},
methods: { methods: {
setAxisDimensions() {
const axisHolder = this.$refs.axisHolder;
const rect = axisHolder.getBoundingClientRect();
this.left = Math.round(rect.left);
this.width = axisHolder.clientWidth;
},
setScale() { setScale() {
if (!this.width) {
return;
}
let timeSystem = this.openmct.time.timeSystem(); let timeSystem = this.openmct.time.timeSystem();
let bounds = this.bounds;
if (timeSystem.isUTCBased) { if (timeSystem.isUTCBased) {
this.xScale.domain( this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
[new Date(this.viewBounds.start), new Date(this.viewBounds.end)]
);
} else { } else {
this.xScale.domain( this.xScale.domain([bounds.start, bounds.end]);
[this.viewBounds.start, this.viewBounds.end]
);
} }
this.xAxis.scale(this.xScale); this.xAxis.scale(this.xScale);
@ -136,7 +102,7 @@ export default {
this.xAxis.ticks(this.width / PIXELS_PER_TICK); this.xAxis.ticks(this.width / PIXELS_PER_TICK);
} }
this.msPerPixel = (this.viewBounds.end - this.viewBounds.start) / this.width; this.msPerPixel = (bounds.end - bounds.start) / this.width;
}, },
setViewFromTimeSystem(timeSystem) { setViewFromTimeSystem(timeSystem) {
//The D3 scale used depends on the type of time system as d3 //The D3 scale used depends on the type of time system as d3
@ -154,8 +120,9 @@ export default {
}, },
getActiveFormatter() { getActiveFormatter() {
let timeSystem = this.openmct.time.timeSystem(); let timeSystem = this.openmct.time.timeSystem();
let isFixed = this.openmct.time.clock() === undefined;
if (this.isFixed) { if (isFixed) {
return this.getFormatter(timeSystem.timeFormat); return this.getFormatter(timeSystem.timeFormat);
} else { } else {
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
@ -167,131 +134,45 @@ export default {
}).formatter; }).formatter;
}, },
dragStart($event) { dragStart($event) {
if (this.isFixed) { let isFixed = this.openmct.time.clock() === undefined;
if (isFixed) {
this.dragStartX = $event.clientX; this.dragStartX = $event.clientX;
if (this.altPressed) {
this.inPanMode = true;
}
document.addEventListener('mousemove', this.drag); document.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.dragEnd, { document.addEventListener('mouseup', this.dragEnd, {
once: true once: true
}); });
if (this.inZoomMode) {
this.startZoom();
}
} }
}, },
drag($event) { drag($event) {
if (!this.dragging) { if (!this.dragging) {
this.dragging = true; this.dragging = true;
requestAnimationFrame(()=>{
requestAnimationFrame(() => { let deltaX = $event.clientX - this.dragStartX;
this.dragX = $event.clientX; let percX = deltaX / this.width;
this.inPanMode ? this.pan() : this.zoom(); let bounds = this.openmct.time.bounds();
let deltaTime = bounds.end - bounds.start;
let newStart = bounds.start - percX * deltaTime;
this.$emit('panAxis',{
start: newStart,
end: newStart + deltaTime
});
this.dragging = false; this.dragging = false;
}); })
} else {
console.log('Rejected drag due to RAF cap');
} }
}, },
dragEnd() { dragEnd() {
this.inPanMode ? this.endPan() : this.endZoom();
document.removeEventListener('mousemove', this.drag); document.removeEventListener('mousemove', this.drag);
this.dragStartX = undefined; this.openmct.time.bounds({
this.dragX = undefined; start: this.bounds.start,
}, end: this.bounds.end
pan() {
const panBounds = this.getPanBounds();
this.$emit('panAxis', panBounds);
},
endPan() {
const panBounds = this.dragStartX && this.dragX && this.dragStartX !== this.dragX
? this.getPanBounds()
: undefined;
this.$emit('endPan', panBounds);
this.inPanMode = false;
},
getPanBounds() {
const bounds = this.openmct.time.bounds();
const deltaTime = bounds.end - bounds.start;
const deltaX = this.dragX - this.dragStartX;
const percX = deltaX / this.width;
const panStart = bounds.start - percX * deltaTime;
return {
start: panStart,
end: panStart + deltaTime
};
},
startZoom() {
const x = this.scaleToBounds(this.dragStartX);
this.zoomStyle = {
left: `${this.dragStartX - this.left}px`
};
this.$emit('zoomAxis', {
start: x,
end: x
}); });
}, },
zoom() {
const zoomRange = this.getZoomRange();
this.zoomStyle = {
left: `${zoomRange.start - this.left}px`,
width: `${zoomRange.end - zoomRange.start}px`
};
this.$emit('zoomAxis', {
start: this.scaleToBounds(zoomRange.start),
end: this.scaleToBounds(zoomRange.end)
});
},
endZoom() {
const zoomRange = this.dragStartX && this.dragX && this.dragStartX !== this.dragX
? this.getZoomRange()
: undefined;
const zoomBounds = zoomRange
? {
start: this.scaleToBounds(zoomRange.start),
end: this.scaleToBounds(zoomRange.end)
}
: this.openmct.time.bounds();
this.zoomStyle = {};
this.$emit('endZoom', zoomBounds);
},
getZoomRange() {
const leftBound = this.left;
const rightBound = this.left + this.width;
const zoomStart = this.dragX < leftBound
? leftBound
: Math.min(this.dragX, this.dragStartX);
const zoomEnd = this.dragX > rightBound
? rightBound
: Math.max(this.dragX, this.dragStartX);
return {
start: zoomStart,
end: zoomEnd
};
},
scaleToBounds(value) {
const bounds = this.openmct.time.bounds();
const timeDelta = bounds.end - bounds.start;
const valueDelta = value - this.left;
const offset = valueDelta / this.width * timeDelta;
return bounds.start + offset;
},
resize() { resize() {
if (this.$refs.axisHolder.clientWidth !== this.width) { if (this.$refs.axisHolder.clientWidth !== this.width) {
this.setAxisDimensions(); this.width = this.$refs.axisHolder.clientWidth;
this.setScale(); this.setScale();
} }
} }

View File

@ -1,200 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2018, 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.
*****************************************************************************/
<template>
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
<button class="c-button--menu c-history-button icon-history"
@click.prevent="toggle"
>
<span class="c-button__label">History</span>
</button>
<div v-if="open"
class="c-menu c-conductor__history-menu"
>
<ul v-if="hasHistoryPresets">
<li
v-for="preset in presets"
:key="preset.label"
class="icon-clock"
@click="selectPresetBounds(preset.bounds)"
>
{{ preset.label }}
</li>
</ul>
<div
v-if="hasHistoryPresets"
class="c-menu__section-separator"
></div>
<div class="c-menu__section-hint">
Past timeframes, ordered by latest first
</div>
<ul>
<li
v-for="(timespan, index) in historyForCurrentTimeSystem"
:key="index"
class="icon-history"
@click="selectTimespan(timespan)"
>
{{ formatTime(timespan.start) }} - {{ formatTime(timespan.end) }}
</li>
</ul>
</div>
</div>
</template>
<script>
import toggleMixin from '../../ui/mixins/toggle-mixin';
const LOCAL_STORAGE_HISTORY_KEY = 'tcHistory';
const DEFAULT_RECORDS = 10;
export default {
inject: ['openmct', 'configuration'],
mixins: [toggleMixin],
props: {
bounds: {
type: Object,
required: true
},
timeSystem: {
type: Object,
required: true
}
},
data() {
return {
history: {}, // contains arrays of timespans {start, end}, array key is time system key
presets: []
}
},
computed: {
hasHistoryPresets() {
return this.timeSystem.isUTCBased && this.presets.length;
},
historyForCurrentTimeSystem() {
const history = this.history[this.timeSystem.key];
return history;
}
},
watch: {
bounds: {
handler() {
this.addTimespan();
},
deep: true
},
timeSystem: {
handler() {
this.loadConfiguration();
this.addTimespan();
},
deep: true
},
history: {
handler() {
this.persistHistoryToLocalStorage();
},
deep: true
}
},
mounted() {
this.getHistoryFromLocalStorage();
},
methods: {
getHistoryFromLocalStorage() {
if (localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY)) {
this.history = JSON.parse(localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY))
} else {
this.history = {};
this.persistHistoryToLocalStorage();
}
},
persistHistoryToLocalStorage() {
localStorage.setItem(LOCAL_STORAGE_HISTORY_KEY, JSON.stringify(this.history));
},
addTimespan() {
const key = this.timeSystem.key;
let [...currentHistory] = this.history[key] || [];
const timespan = {
start: this.bounds.start,
end: this.bounds.end
};
const isNotEqual = function (entry) {
const start = entry.start !== this.start;
const end = entry.end !== this.end;
return start || end;
};
currentHistory = currentHistory.filter(isNotEqual, timespan);
while (currentHistory.length >= this.records) {
currentHistory.pop();
}
currentHistory.unshift(timespan);
this.history[key] = currentHistory;
},
selectTimespan(timespan) {
this.openmct.time.bounds(timespan);
},
selectPresetBounds(bounds) {
const start = typeof bounds.start === 'function' ? bounds.start() : bounds.start;
const end = typeof bounds.end === 'function' ? bounds.end() : bounds.end;
this.selectTimespan({
start: start,
end: end
});
},
loadConfiguration() {
const configurations = this.configuration.menuOptions
.filter(option => option.timeSystem === this.timeSystem.key);
this.presets = this.loadPresets(configurations);
this.records = this.loadRecords(configurations);
},
loadPresets(configurations) {
const configuration = configurations.find(option => option.presets);
const presets = configuration ? configuration.presets : [];
return presets;
},
loadRecords(configurations) {
const configuration = configurations.find(option => option.records);
const records = configuration ? configuration.records : DEFAULT_RECORDS;
return records;
},
formatTime(time) {
const formatter = this.openmct.telemetry.getValueFormatter({
format: this.timeSystem.timeFormat
}).formatter;
return formatter.format(time);
}
}
}
</script>

View File

@ -110,7 +110,7 @@ export default {
if (clock === undefined) { if (clock === undefined) {
return { return {
key: 'fixed', key: 'fixed',
name: 'Fixed Timespan', name: 'Fixed Timespan Mode',
description: 'Query and explore data that falls between two fixed datetimes.', description: 'Query and explore data that falls between two fixed datetimes.',
cssClass: 'icon-tabular' cssClass: 'icon-tabular'
} }

View File

@ -13,7 +13,7 @@
text-rendering: geometricPrecision; text-rendering: geometricPrecision;
width: 100%; width: 100%;
height: 100%; height: 100%;
> g.axis { > g {
// Overall Tick holder // Overall Tick holder
transform: translateY($tickYPos); transform: translateY($tickYPos);
path { path {
@ -44,6 +44,7 @@
} }
body.desktop .is-fixed-mode & { body.desktop .is-fixed-mode & {
@include cursorGrab();
background-size: 3px 30%; background-size: 3px 30%;
background-color: $colorBodyBgSubtle; background-color: $colorBodyBgSubtle;
box-shadow: inset rgba(black, 0.4) 0 1px 1px; box-shadow: inset rgba(black, 0.4) 0 1px 1px;
@ -54,6 +55,17 @@
stroke: $colorBodyBgSubtle; stroke: $colorBodyBgSubtle;
transition: $transOut; transition: $transOut;
} }
&:hover,
&:active {
$c: $colorKeySubtle;
background-color: $c;
transition: $transIn;
svg text {
stroke: $c;
transition: $transIn;
}
}
} }
.is-realtime-mode & { .is-realtime-mode & {

View File

@ -57,65 +57,6 @@
} }
} }
&.is-fixed-mode {
.c-conductor-axis {
&__zoom-indicator {
border: 1px solid transparent;
display: none; // Hidden by default
}
}
&:not(.is-panning),
&:not(.is-zooming) {
.c-conductor-axis {
&:hover,
&:active {
cursor: col-resize;
filter: $timeConductorAxisHoverFilter;
}
}
}
&.is-panning,
&.is-zooming {
.c-conductor-input input {
// Styles for inputs while zooming or panning
background: rgba($timeConductorActiveBg, 0.4);
}
}
&.alt-pressed {
.c-conductor-axis:hover {
// When alt is being pressed and user is hovering over the axis, set the cursor
@include cursorGrab();
}
}
&.is-panning {
.c-conductor-axis {
@include cursorGrab();
background-color: $timeConductorActivePanBg;
transition: $transIn;
svg text {
stroke: $timeConductorActivePanBg;
transition: $transIn;
}
}
}
&.is-zooming {
.c-conductor-axis__zoom-indicator {
display: block;
position: absolute;
background: rgba($timeConductorActiveBg, 0.4);
border-left-color: $timeConductorActiveBg;
border-right-color: $timeConductorActiveBg;
top: 0; bottom: 0;
}
}
}
&.is-realtime-mode { &.is-realtime-mode {
.c-conductor__time-bounds { .c-conductor__time-bounds {
grid-template-columns: 20px auto 1fr auto auto; grid-template-columns: 20px auto 1fr auto auto;

View File

@ -142,19 +142,12 @@ $colorTimeHov: pullForward($colorTime, 10%);
$colorTimeSubtle: pushBack($colorTime, 20%); $colorTimeSubtle: pushBack($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor $colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov $colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(1.2);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #226074;
/************************************************** BROWSING */ /************************************************** BROWSING */
$browseFrameColor: pullForward($colorBodyBg, 10%); $browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing $browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px; $browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4); $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(1.2) contrast(1.1);
$filterItemMissing: brightness(0.6) grayscale(1);
$opacityMissing: 0.5;
$borderMissing: 1px dashed $colorAlert !important;
/************************************************** EDITING */ /************************************************** EDITING */
$editUIColor: $uiColor; // Base color $editUIColor: $uiColor; // Base color

View File

@ -146,19 +146,12 @@ $colorTimeHov: pullForward($colorTime, 10%);
$colorTimeSubtle: pushBack($colorTime, 20%); $colorTimeSubtle: pushBack($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor $colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov $colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(1.2);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #226074;
/************************************************** BROWSING */ /************************************************** BROWSING */
$browseFrameColor: pullForward($colorBodyBg, 10%); $browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing $browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px; $browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4); $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(1.2) contrast(1.1);
$filterItemMissing: contrast(0.2);
$opacityMissing: 0.5;
$borderMissing: 1px dashed $colorAlert !important;
/************************************************** EDITING */ /************************************************** EDITING */
$editUIColor: $uiColor; // Base color $editUIColor: $uiColor; // Base color

Some files were not shown because too many files have changed in this diff Show More