Compare commits

..

17 Commits

Author SHA1 Message Date
Deep Tailor
80c9f4602c Merge branch 'master' into disable-editing-toggle-061120 2020-06-29 12:58:58 -07:00
Deep Tailor
36a7df0708 make reviewer suggested changes 2020-06-25 13:17:45 -07:00
Deep Tailor
79ff690d4b merge master 2020-06-25 13:00:40 -07:00
Deep Tailor
f56a4c9ddc fix tests 2020-06-25 12:52:18 -07:00
Deep Tailor
d8cfaeada4 fix lint issues 2020-06-25 11:33:51 -07:00
Deep Tailor
6b65ceb2c5 prevent styling changes when locked 2020-06-25 11:22:01 -07:00
charlesh88
24f3687552 Edit toggle refinements
- Replaced toggle switch with button;
2020-06-25 10:38:54 -07:00
charlesh88
07464ab417 More new glyphs, updated art
- New glyphs: icon-unlocked and icon-target;
- Updated art for icon-lock glyph;
2020-06-25 10:09:30 -07:00
charlesh88
42236e9069 Edit toggle refinements WIP
- Markup, CSS in BrowseBar.vue;
2020-06-25 10:07:12 -07:00
charlesh88
b2d03bb4be More new glyphs, updated art
- New glyphs: icon-unlocked and icon-target;
- Updated art for icon-lock glyph;
2020-06-25 10:05:41 -07:00
charlesh88
2bf349065a Cherrypick new glyphs from add-new-glyphs-062320 2020-06-25 10:05:22 -07:00
Deep Tailor
a93601d1f0 continued wip 2020-06-15 15:30:32 -07:00
Deep Tailor
1a6c8c64a3 disable mousedown when not editing in DisplayLayout 2020-06-15 13:28:11 -07:00
Deep Tailor
854d9d43fe wip 2020-06-15 10:35:51 -07:00
Deep Tailor
0c4e46a1ca fix lint issue 2020-06-11 14:47:14 -07:00
Deep Tailor
551bfcb4a8 prevent flexible layout drop hints from showing 2020-06-11 14:44:22 -07:00
Deep Tailor
39a78df42f working lock and unlock 2020-06-11 14:10:52 -07:00
111 changed files with 927 additions and 2817 deletions

3
.gitignore vendored
View File

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

View File

@@ -28,16 +28,6 @@ define([
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",
name: "Sine",
@@ -71,15 +61,6 @@ define([
domain: 1
}
},
{
key: "local",
name: "Time",
format: "utc",
source: "utc",
hints: {
domain: 2
}
},
{
key: "state",
source: "value",

View File

@@ -34,8 +34,8 @@
<body>
</body>
<script>
const THIRTY_SECONDS = 30 * 1000;
const THIRTY_MINUTES = THIRTY_SECONDS * 60;
const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_MINUTES = 30 * 60 * 1000;
[
'example/eventGenerator'
@@ -63,39 +63,7 @@
bounds: {
start: Date.now() - THIRTY_MINUTES,
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",
@@ -103,7 +71,7 @@
clock: 'local',
clockOffsets: {
start: - THIRTY_MINUTES,
end: THIRTY_SECONDS
end: FIVE_MINUTES
}
}
]
@@ -113,10 +81,7 @@
openmct.install(openmct.plugins.LADTable());
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
openmct.install(openmct.plugins.ObjectMigration());
openmct.install(openmct.plugins.ClearData(
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
{indicator: true}
));
openmct.install(openmct.plugins.ClearData(['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked']));
openmct.start();
</script>
</html>

View File

@@ -23,7 +23,7 @@
/*global module,process*/
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 reporters = ['progress', 'html'];
@@ -95,7 +95,6 @@ module.exports = (config) => {
stats: 'errors-only',
logLevel: 'warn'
},
singleRun: true,
browserNoActivityTimeout: 90000
singleRun: true
});
}

View File

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

View File

@@ -31,13 +31,13 @@
</mct-form>
</div>
<div class="c-overlay__button-bar">
<button class='c-button c-button--major'
<a class='c-button c-button--major'
ng-class="{ disabled: !createForm.$valid }"
ng-click="ngModel.confirm()">
OK
</button>
<button class='c-button '
</a>
<a class='c-button '
ng-click="ngModel.cancel()">
Cancel
</button>
</a>
</div>

View File

@@ -31,13 +31,13 @@
</mct-include>
</div>
<div class="c-overlay__button-bar">
<button ng-repeat="option in ngModel.dialog.options"
<a ng-repeat="option in ngModel.dialog.options"
href=''
class="s-button lg"
title="{{option.description}}"
ng-click="ngModel.confirm(option.key)"
ng-class="{ major: $first, subtle: !$first }">
{{option.name}}
</button>
</a>
</div>
</mct-container>

View File

@@ -24,7 +24,7 @@
<div class="c-overlay__outer">
<button ng-click="ngModel.cancel()"
ng-if="ngModel.cancel"
class="c-click-icon c-overlay__close-button icon-x"></button>
class="c-click-icon c-overlay__close-button icon-x-in-circle"></button>
<div class="c-overlay__contents" ng-transclude></div>
</div>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,8 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(["objectUtils"],
function (objectUtils) {
define(
function () {
/**
* Defines the `persistence` capability, used to trigger the
@@ -47,7 +47,6 @@ define(["objectUtils"],
identifierService,
notificationService,
$q,
openmct,
domainObject
) {
// Cache modified timestamp
@@ -59,7 +58,6 @@ define(["objectUtils"],
this.persistenceService = persistenceService;
this.notificationService = notificationService;
this.$q = $q;
this.openmct = openmct;
}
/**
@@ -68,7 +66,7 @@ define(["objectUtils"],
*/
function rejectIfFalsey(value, $q) {
if (!value) {
return Promise.reject("Error persisting object");
return $q.reject("Error persisting object");
} else {
return value;
}
@@ -100,7 +98,7 @@ define(["objectUtils"],
dismissable: true
});
return Promise.reject(error);
return $q.reject(error);
}
/**
@@ -112,16 +110,34 @@ define(["objectUtils"],
*/
PersistenceCapability.prototype.persist = function () {
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());
return this.openmct.objects
.save(newStyleObject)
.then(function (result) {
return rejectIfFalsey(result, self.$q);
}).catch(function (error) {
return notifyOnError(error, domainObject, self.notificationService, self.$q);
});
if (persisted !== undefined && persisted === modified) {
return this.$q.when(true);
}
// Update persistence timestamp...
domainObject.useCapability("mutation", function (m) {
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
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* PersistenceCapabilitySpec. Created by vwoeltje on 11/6/14.
*/
@@ -39,8 +40,7 @@ define(
model,
SPACE = "some space",
persistence,
mockOpenMCT,
mockNewStyleDomainObject;
happyPromise;
function asPromise(value, doCatch) {
return (value || {}).then ? value : {
@@ -56,6 +56,7 @@ define(
}
beforeEach(function () {
happyPromise = asPromise(true);
model = { someKey: "some value", name: "domain object"};
mockPersistenceService = jasmine.createSpyObj(
@@ -93,23 +94,12 @@ define(
},
useCapability: jasmine.createSpy()
};
mockNewStyleDomainObject = Object.assign({}, model);
mockNewStyleDomainObject.identifier = {
namespace: "",
key: id
}
// Simulate mutation capability
mockDomainObject.useCapability.and.callFake(function (capability, mutator) {
if (capability === 'mutation') {
model = mutator(model) || model;
}
});
mockOpenMCT = {};
mockOpenMCT.objects = jasmine.createSpyObj('Object API', ['save']);
mockIdentifierService.parse.and.returnValue(mockIdentifier);
mockIdentifier.getSpace.and.returnValue(SPACE);
mockIdentifier.getKey.and.returnValue(key);
@@ -120,28 +110,51 @@ define(
mockIdentifierService,
mockNofificationService,
mockQ,
mockOpenMCT,
mockDomainObject
);
});
describe("successful persistence", 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 () {
// Verify precondition; no call made during constructor
expect(mockOpenMCT.objects.save).not.toHaveBeenCalled();
expect(mockPersistenceService.createObject).not.toHaveBeenCalled();
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 () {
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 () {
var refreshModel = {someOtherKey: "some other value"};
model.persisted = 1;
@@ -152,37 +165,30 @@ define(
it("does not trigger error notification on successful" +
" persistence", function () {
let rejected = false;
return persistence.persist()
.catch(() => rejected = true)
.then(() => {
expect(rejected).toBe(false);
expect(mockNofificationService.error).not.toHaveBeenCalled();
});
persistence.persist();
expect(mockQ.reject).not.toHaveBeenCalled();
expect(mockNofificationService.error).not.toHaveBeenCalled();
});
});
describe("unsuccessful persistence", function () {
var sadPromise = {
then: function (callback) {
return asPromise(callback(0), true);
}
};
beforeEach(function () {
mockOpenMCT.objects.save.and.returnValue(Promise.resolve(false));
mockPersistenceService.createObject.and.returnValue(sadPromise);
});
it("rejects on falsey persistence result", function () {
let rejected = false;
return persistence.persist()
.catch(() => rejected = true)
.then(() => {
expect(rejected).toBe(true);
});
persistence.persist();
expect(mockQ.reject).toHaveBeenCalled();
});
it("notifies user on persistence failure", function () {
let rejected = false;
return persistence.persist()
.catch(() => rejected = true)
.then(() => {
expect(rejected).toBe(true);
expect(mockNofificationService.error).toHaveBeenCalled();
});
persistence.persist();
expect(mockQ.reject).toHaveBeenCalled();
expect(mockNofificationService.error).toHaveBeenCalled();
});
});
});

View File

@@ -29,9 +29,9 @@
type="text" tabindex="10000"
ng-model="ngModel.input"
ng-keyup="controller.search()"/>
<button class="c-search__clear-input clear-icon icon-x-in-circle"
<a class="c-search__clear-input clear-icon icon-x-in-circle"
ng-class="{show: !(ngModel.input === '' || ngModel.input === undefined)}"
ng-click="ngModel.input = ''; controller.search()"></button>
ng-click="ngModel.input = ''; controller.search()"></a>
<!-- To prevent double triggering of clicks on click away, render
non-clickable version of the button when menu active-->
<a ng-if="!toggle.isActive()" class="menu-icon context-available"
@@ -45,16 +45,16 @@
</mct-include>
</div>
<button class="c-button c-search__btn-cancel"
<a class="c-button c-search__btn-cancel"
ng-show="!(ngModel.input === '' || ngModel.input === undefined)"
ng-click="ngModel.input = ''; ngModel.checkAll = true; menuController.checkAll(); controller.search()">
Cancel</button>
Cancel</a>
</div>
<div class="active-filter-display flex-elem holder"
ng-class="{invisible: ngModel.filtersString === '' || ngModel.filtersString === undefined || !ngModel.search}">
<button class="clear-filters icon-x-in-circle s-icon-button"
ng-click="ngModel.checkAll = true; menuController.checkAll()"></button>Filtered by: {{ ngModel.filtersString }}
<a class="clear-filters icon-x-in-circle s-icon-button"
ng-click="ngModel.checkAll = true; menuController.checkAll()"></a>Filtered by: {{ ngModel.filtersString }}
</div>
<div class="flex-elem holder results-msg" ng-model="ngModel" ng-show="!loading && ngModel.search">
@@ -72,7 +72,7 @@
ng-model="ngModel"
class="l-flex-row flex-elem grows">
</mct-representation>
<button class="load-more-button s-button vsm" ng-if="controller.areMore()" ng-click="controller.loadMore()">More Results</button>
<a class="load-more-button s-button vsm" ng-if="controller.areMore()" ng-click="controller.loadMore()">More Results</a>
</div>
</div>
</div>

View File

@@ -25,11 +25,10 @@ define([
], function (
utils
) {
function ObjectServiceProvider(eventEmitter, objectService, instantiate, topic, $injector) {
function ObjectServiceProvider(eventEmitter, objectService, instantiate, topic) {
this.eventEmitter = eventEmitter;
this.objectService = objectService;
this.instantiate = instantiate;
this.$injector = $injector;
this.generalTopic = topic('mutation');
this.bridgeEventBuses();
@@ -69,53 +68,16 @@ define([
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
};
ObjectServiceProvider.prototype.create = async function (object) {
let model = utils.toOldFormat(object);
ObjectServiceProvider.prototype.save = function (object) {
var key = object.key;
return this.getPersistenceService().createObject(
this.getSpace(utils.makeKeyString(object.identifier)),
object.identifier.key,
model
);
}
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();
return object.getCapability('persistence')
.persist()
.then(function () {
return utils.toNewFormat(object, key);
});
};
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) {
// TODO!
};
@@ -156,8 +118,7 @@ define([
eventEmitter,
objectService,
instantiate,
topic,
openmct.$injector
topic
)
);

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#
* @param {module:openmct.DomainObject} domainObject the domain object to
* create
* save
* @returns {Promise} a promise which will resolve when the domain object
* has been created, 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
* has been saved, or be rejected if it cannot be saved
*/
/**
@@ -172,41 +161,8 @@ define([
throw new Error('Delete not implemented');
};
ObjectAPI.prototype.isPersistable = function (domainObject) {
let provider = this.getProvider(domainObject.identifier);
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;
ObjectAPI.prototype.save = function () {
throw new Error('Save not implemented');
};
/**
@@ -320,9 +276,5 @@ define([
* @memberof module:openmct
*/
function hasAlreadyBeenPersisted(domainObject) {
return domainObject.persisted !== undefined &&
domainObject.persisted === domainObject.modified;
}
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

@@ -7,7 +7,7 @@
<div class="c-overlay__outer">
<button
v-if="dismissable"
class="c-click-icon c-overlay__close-button icon-x"
class="c-click-icon c-overlay__close-button icon-x-in-circle"
@click="destroy"
></button>
<div

View File

@@ -29,12 +29,13 @@
}
&__close-button {
$p: $interiorMargin + 2px;
$p: $interiorMargin;
border-radius: 100% !important;
color: $overlayColorFg;
font-size: 1.5em;
display: inline-block;
font-size: 1.25em;
position: absolute;
top: $p; right: $p;
z-index: 99;
}
&__contents {
@@ -42,7 +43,7 @@
display: flex;
flex-direction: column;
outline: none;
overflow: auto;
overflow: hidden;
}
&__top-bar {
@@ -86,10 +87,6 @@
.c-click-icon {
filter: $overlayBrightnessAdjust;
}
.c-object-label__name {
filter: $objectLabelNameFilter;
}
}
body.desktop {
@@ -103,6 +100,7 @@ body.desktop {
}
// Overlay types, styling for desktop. Appended to .l-overlay-wrapper element.
.l-overlay-large,
.l-overlay-small,
.l-overlay-fit {
.c-overlay__outer {
@@ -120,28 +118,8 @@ body.desktop {
.l-overlay-large {
// Default
$pad: $interiorMarginLg;
$tbPad: floor($pad * 0.8);
$lrPad: $pad;
.c-overlay {
&__blocker {
display: none;
}
&__outer {
@include overlaySizing($overlayOuterMarginFullscreen);
padding: $tbPad $lrPad;
}
&__close-button {
//top: $interiorMargin;
//right: $interiorMargin;
}
}
.l-browse-bar {
margin-right: 50px; // Don't cover close button
margin-bottom: $interiorMargin;
.c-overlay__outer {
@include overlaySizing($overlayOuterMarginLg);
}
}
@@ -161,4 +139,4 @@ body.desktop {
min-width: 20%;
}
}
}
}

View File

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

View File

@@ -156,29 +156,6 @@ define([
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 () {
var unsubFunc = jasmine.createSpy('unsubscribe');
telemetryProvider.subscribe.and.returnValue(unsubFunc);

View File

@@ -64,7 +64,7 @@ export default {
},
computed: {
formattedTimestamp() {
return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---';
return this.timestamp !== undefined ? this.formats[this.timestampKey].format(this.timestamp) : '---';
}
},
mounted() {
@@ -110,11 +110,11 @@ export default {
},
methods: {
updateValues(datum) {
let newTimestamp = this.getParsedTimestamp(datum),
let newTimestamp = this.formats[this.timestampKey].parse(datum),
limit;
if(this.shouldUpdate(newTimestamp)) {
this.timestamp = newTimestamp;
this.timestamp = this.formats[this.timestampKey].parse(datum);
this.value = this.formats[this.valueKey].format(datum);
limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) {
@@ -125,12 +125,9 @@ export default {
}
},
shouldUpdate(newTimestamp) {
let newTimestampInBounds = this.inBounds(newTimestamp),
noExistingTimestamp = this.timestamp === undefined,
newTimestampIsLatest = newTimestamp > this.timestamp;
return newTimestampInBounds &&
(noExistingTimestamp || newTimestampIsLatest);
return (this.timestamp === undefined) ||
(this.inBounds(newTimestamp) &&
newTimestamp > this.timestamp);
},
requestHistory() {
this.openmct
@@ -149,7 +146,6 @@ export default {
updateBounds(bounds, isTick) {
this.bounds = bounds;
if(!isTick) {
this.resetValues();
this.requestHistory();
}
},
@@ -157,34 +153,13 @@ export default {
return timestamp >= this.bounds.start && timestamp <= this.bounds.end;
},
updateTimeSystem(timeSystem) {
this.resetValues();
this.value = '---';
this.timestamp = '---';
this.valueClass = '';
this.timestampKey = timeSystem.key;
},
showContextMenu(event) {
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

@@ -29,28 +29,24 @@ define([
ClearDataAction,
Vue
) {
return function plugin(appliesToObjects, options = {indicator: true}) {
let installIndicator = options.indicator;
return function plugin(appliesToObjects) {
appliesToObjects = appliesToObjects || [];
return function install(openmct) {
if (installIndicator) {
let component = new Vue ({
provide: {
openmct
},
components: {
GlobalClearIndicator: GlobaClearIndicator.default
},
template: '<GlobalClearIndicator></GlobalClearIndicator>'
}),
indicator = {
element: component.$mount().$el
};
let component = new Vue ({
provide: {
openmct
},
components: {
GlobalClearIndicator: GlobaClearIndicator.default
},
template: '<GlobalClearIndicator></GlobalClearIndicator>'
}),
indicator = {
element: component.$mount().$el
};
openmct.indicators.add(indicator);
}
openmct.indicators.add(indicator);
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.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
if (!this.criteria) {
this.criteria = [];
}
@@ -179,12 +178,10 @@ export default class Condition extends EventEmitter {
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
newCriterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
let criterion = found.item;
criterion.unsubscribe();
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.off('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
this.criteria.splice(found.index, 1, newCriterion);
this.updateDescription();
}
@@ -197,9 +194,6 @@ export default class Condition extends EventEmitter {
criterion.off('criterionUpdated', (obj) => {
this.handleCriterionUpdated(obj);
});
criterion.off('telemetryIsStale', (obj) => {
this.handleStaleCriterion(obj);
});
criterion.destroy();
this.criteria.splice(found.index, 1);
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() {
const triggerDescription = this.getTriggerDescription();
let description = '';

View File

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

View File

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

View File

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

View File

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

View File

@@ -190,7 +190,6 @@ export default {
if (this.isEditing) {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
}
} else {
this.subscribeToConditionSet();
@@ -326,7 +325,6 @@ export default {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
}
if (this.unObserveObjects) {
@@ -339,7 +337,6 @@ export default {
subscribeToConditionSet() {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
}
if (this.conditionSetDomainObject) {
this.openmct.telemetry.request(this.conditionSetDomainObject)
@@ -497,7 +494,6 @@ export default {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
}
},
removeConditionalStyles(domainObjectStyles, itemId) {

View File

@@ -22,7 +22,7 @@
import TelemetryCriterion from './TelemetryCriterion';
import { evaluateResults } from "../utils/evaluator";
import {getLatestTimestamp, subscribeForStaleness} from '../utils/time';
import { getLatestTimestamp } from '../utils/time';
import { getOperatorText } from "@/plugins/condition/utils/operations";
export default class AllTelemetryCriterion extends TelemetryCriterion {
@@ -41,32 +41,6 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
initialize() {
this.telemetryObjects = { ...this.telemetryDomainObjectDefinition.telemetryObjects };
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() {
@@ -76,9 +50,6 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
updateTelemetryObjects(telemetryObjects) {
this.telemetryObjects = { ...telemetryObjects };
this.removeTelemetryDataCache();
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData(this.telemetryObjects || {});
}
}
removeTelemetryDataCache() {
@@ -92,7 +63,6 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
});
telemetryCacheIds.forEach(id => {
delete (this.telemetryDataCache[id]);
delete (this.stalenessSubscription[id]);
});
}
@@ -126,14 +96,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
const validatedData = this.isValid() ? data : {};
if (validatedData) {
if (this.isStalenessCheck()) {
if (this.stalenessSubscription && this.stalenessSubscription[validatedData.id]) {
this.stalenessSubscription[validatedData.id].update(validatedData);
}
this.telemetryDataCache[validatedData.id] = false;
} else {
this.telemetryDataCache[validatedData.id] = this.computeResult(validatedData);
}
this.telemetryDataCache[validatedData.id] = this.computeResult(validatedData);
}
Object.values(telemetryObjects).forEach(telemetryObject => {
@@ -199,7 +162,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
getDescription() {
const telemetryDescription = this.telemetry === 'all' ? 'all telemetry' : 'any telemetry';
let metadataValue = (this.metadata === 'dataReceived' ? '' : this.metadata);
let metadataValue = this.metadata;
let inputValue = this.input;
if (this.metadata) {
const telemetryObjects = Object.values(this.telemetryObjects);
@@ -219,9 +182,5 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
destroy() {
delete this.telemetryObjects;
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 { OPERATIONS, getOperatorText } from '../utils/operations';
import { subscribeForStaleness } from "../utils/time";
export default class TelemetryCriterion extends EventEmitter {
@@ -44,7 +43,6 @@ export default class TelemetryCriterion extends EventEmitter {
this.input = telemetryDomainObjectDefinition.input;
this.metadata = telemetryDomainObjectDefinition.metadata;
this.result = undefined;
this.stalenessSubscription = undefined;
this.initialize();
this.emitEvent('criterionUpdated', this);
@@ -53,40 +51,14 @@ export default class TelemetryCriterion extends EventEmitter {
initialize() {
this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry);
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() {
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) {
this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString];
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData()
}
}
createNormalizedDatum(telemetryDatum, endpoint) {
@@ -119,14 +91,7 @@ export default class TelemetryCriterion extends EventEmitter {
getResult(data) {
const validatedData = this.isValid() ? data : {};
if (this.isStalenessCheck()) {
if (this.stalenessSubscription) {
this.stalenessSubscription.update(validatedData);
}
this.result = false;
} else {
this.result = this.computeResult(validatedData);
}
this.result = this.computeResult(validatedData);
}
requestLAD() {
@@ -142,14 +107,12 @@ export default class TelemetryCriterion extends EventEmitter {
};
}
let telemetryObject = this.telemetryObject;
return this.openmct.telemetry.request(
this.telemetryObject,
options
).then(results => {
const latestDatum = results.length ? results[results.length - 1] : {};
const normalizedDatum = this.createNormalizedDatum(latestDatum, telemetryObject);
const normalizedDatum = this.createNormalizedDatum(latestDatum, this.telemetryObject);
return {
id: this.id,
@@ -173,7 +136,7 @@ export default class TelemetryCriterion extends EventEmitter {
let comparator = this.findOperation(this.operation);
let params = [];
params.push(data[this.metadata]);
if (this.isValidInput()) {
if (this.input instanceof Array && this.input.length) {
this.input.forEach(input => params.push(input));
}
if (typeof comparator === 'function') {
@@ -228,7 +191,7 @@ export default class TelemetryCriterion extends EventEmitter {
description = `Unknown ${this.metadata} ${getOperatorText(this.operation, this.input)}`;
} else {
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;
description = `${this.telemetryObject.name} ${metadataValue} ${getOperatorText(this.operation, inputValue)}`;
}
@@ -239,8 +202,5 @@ export default class TelemetryCriterion extends EventEmitter {
destroy() {
delete this.telemetryObject;
delete this.telemetryObjectIdAsString;
if (this.stalenessSubscription) {
delete this.stalenessSubscription;
}
}
}

View File

@@ -21,14 +21,12 @@
*****************************************************************************/
import TelemetryCriterion from "./TelemetryCriterion";
import { getMockTelemetry } from "utils/testing";
let openmct = {},
mockListener,
testCriterionDefinition,
testTelemetryObject,
telemetryCriterion,
mockTelemetry = getMockTelemetry();
telemetryCriterion;
describe("The telemetry criterion", function () {
@@ -62,7 +60,7 @@ describe("The telemetry criterion", function () {
};
openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']);
openmct.objects.makeKeyString.and.returnValue(testTelemetryObject.identifier.key);
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject', "subscribe", "getMetadata", "getValueFormatter", "request"]);
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject', "subscribe", "getMetadata", "getValueFormatter"]);
openmct.telemetry.isTelemetryObject.and.returnValue(true);
openmct.telemetry.subscribe.and.returnValue(function () {});
openmct.telemetry.getValueFormatter.and.returnValue({
@@ -111,30 +109,4 @@ describe("The telemetry criterion", function () {
});
expect(telemetryCriterion.result).toBeTrue();
});
describe('the LAD request', () => {
beforeEach(async () => {
let telemetryRequestResolve;
let telemetryRequestPromise = new Promise((resolve) => {
telemetryRequestResolve = resolve;
});
openmct.telemetry.request.and.callFake(() => {
setTimeout(() => {
telemetryRequestResolve(mockTelemetry);
}, 100);
return telemetryRequestPromise;
});
});
it("returns results for slow LAD requests", async function () {
const criteriaRequest = telemetryCriterion.requestLAD();
telemetryCriterion.destroy();
expect(telemetryCriterion.telemetryObject).toBeUndefined();
setTimeout(() => {
criteriaRequest.then((result) => {
expect(result).toBeDefined();
});
}, 300);
});
});
});

View File

@@ -25,50 +25,19 @@ import ConditionPlugin from "./plugin";
import StylesView from "./components/inspector/StylesView.vue";
import Vue from 'vue';
import {getApplicableStylesForItem} from "./utils/styleUtils";
import ConditionManager from "@/plugins/condition/ConditionManager";
describe('the plugin', function () {
let conditionSetDefinition;
let mockConditionSetDomainObject;
let mockListener;
let element;
let child;
let openmct;
let testTelemetryObject;
beforeAll(() => {
resetApplicationState(openmct);
});
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.install(new ConditionPlugin());
@@ -86,8 +55,6 @@ describe('the plugin', function () {
type: 'conditionSet'
};
mockListener = jasmine.createSpy('mockListener');
conditionSetDefinition.initialize(mockConditionSetDomainObject);
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) {
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;
}
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

@@ -457,43 +457,15 @@ export default {
this.objectViewMap = {};
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) {
if (this.isItemAlreadyTracked(child)) {
return;
}
let type;
let keyString = this.openmct.objects.makeKeyString(child.identifier);
if (this.isTelemetry(child)) {
type = 'telemetry-view';
} else {
type = 'subobject-view';
if (!this.telemetryViewMap[keyString] && !this.objectViewMap[keyString]) {
this.addItem('telemetry-view', child);
}
} else if (!this.objectViewMap[keyString]) {
this.addItem('subobject-view', child);
}
this.addItem(type, child);
},
removeChild(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
@@ -587,17 +559,14 @@ export default {
}
},
updateTelemetryFormat(item, format) {
let index = this.layoutItems.findIndex((layoutItem) => {
return layoutItem.id === item.id;
});
let index = this.layoutItems.findIndex(item);
item.format = format;
this.mutate(`configuration.items[${index}]`, item);
},
createNewDomainObject(domainObject, composition, viewType, nameExtension, model) {
let identifier = {
key: uuid(),
namespace: this.internalDomainObject.identifier.namespace
namespace: domainObject.identifier.namespace
},
type = this.openmct.types.get(viewType),
parentKeyString = this.openmct.objects.makeKeyString(this.internalDomainObject.identifier),

View File

@@ -25,8 +25,7 @@
class="l-layout__frame c-frame"
:class="{
'no-frame': !item.hasFrame,
'u-inspectable': inspectable,
'is-in-small-container': size.width < 600 || size.height < 600
'u-inspectable': inspectable
}"
:style="style"
>
@@ -62,13 +61,6 @@ export default {
}
},
computed: {
size() {
let {width, height} = this.item;
return {
width: (this.gridSize[0] * width),
height: (this.gridSize[1] * height)
};
},
style() {
let {x, y, width, height} = this.item;
return {

View File

@@ -22,7 +22,7 @@
<template>
<div
class="l-layout__frame c-frame no-frame c-line-view"
class="l-layout__frame c-frame no-frame"
:class="[styleClass]"
:style="style"
>
@@ -31,15 +31,9 @@
height="100%"
>
<line
class="c-line-view__hover-indicator"
v-bind="linePosition"
vector-effect="non-scaling-stroke"
/>
<line
class="c-line-view__line"
v-bind="linePosition"
:stroke="stroke"
vector-effect="non-scaling-stroke"
stroke-width="2"
/>
</svg>
@@ -55,8 +49,7 @@
class="c-frame-edit__handle"
:class="startHandleClass"
@mousedown="startDrag($event, 'start')"
>
</div>
></div>
<div
class="c-frame-edit__handle"
:class="endHandleClass"
@@ -75,18 +68,14 @@ const START_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--sw',
2: 'c-frame-edit__handle--se',
3: 'c-frame-edit__handle--ne',
4: 'c-frame-edit__handle--nw',
5: 'c-frame-edit__handle--nw',
6: 'c-frame-edit__handle--ne'
4: 'c-frame-edit__handle--nw'
};
const END_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--ne',
2: 'c-frame-edit__handle--nw',
3: 'c-frame-edit__handle--sw',
4: 'c-frame-edit__handle--se',
5: 'c-frame-edit__handle--sw',
6: 'c-frame-edit__handle--nw'
4: 'c-frame-edit__handle--se'
};
export default {
@@ -169,12 +158,6 @@ export default {
},
vectorQuadrant() {
let {x, y, x2, y2} = this.position;
if (x2 === x) {
return 5; // Vertical line
}
if (y2 === y) {
return 6; // Horizontal line
}
if (x2 > x) {
if (y2 < y) {
return 1;
@@ -187,27 +170,21 @@ export default {
return 3;
},
linePosition() {
let pos = {};
switch(this.vectorQuadrant) {
case 1:
case 3:
// slopes up
pos = {x1: '0%', y1: '100%', x2: '100%', y2: '0%'};
break;
case 5:
// vertical
pos = {x1: '0%', y1: '0%', x2: '0%', y2: '100%'};
break;
case 6:
// horizontal
pos = {x1: '0%', y1: '0%', x2: '100%', y2: '0%'};
break;
default:
// slopes down
pos = {x1: '0%', y1: '0%', x2: '100%', y2: '100%'};
break;
}
return pos;
return this.vectorQuadrant % 2 !== 0
// odd vectorQuadrant slopes up
? {
x1: '0%',
y1: '100%',
x2: '100%',
y2: '0%'
}
// even vectorQuadrant slopes down
: {
x1: '0%',
y1: '0%',
x2: '100%',
y2: '100%'
};
}
},
watch: {
@@ -232,7 +209,8 @@ export default {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(this.$el, this.context, this.initSelect);
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
destroyed() {
if (this.removeSelectable) {
@@ -246,17 +224,12 @@ export default {
document.body.addEventListener('mousemove', this.continueDrag);
document.body.addEventListener('mouseup', this.endDrag);
this.startPosition = [event.pageX, event.pageY];
let {x, y, x2, y2} = this.item;
this.dragPosition = {x, y, x2, y2};
if (x === x2 || y === y2) {
if (y > y2 || x < x2) {
if (this.dragging === 'start') {
this.dragging = 'end';
} else if (this.dragging === 'end') {
this.dragging = 'start';
}
}
}
this.dragPosition = {
x: this.item.x,
y: this.item.y,
x2: this.item.x2,
y2: this.item.y2
};
event.preventDefault();
},
continueDrag(event) {
@@ -290,7 +263,7 @@ export default {
},
calculateDragPosition(pxDeltaX, pxDeltaY) {
let gridDeltaX = Math.round(pxDeltaX / this.gridSize[0]);
let gridDeltaY = Math.round(pxDeltaY / this.gridSize[1]);
let gridDeltaY = Math.round(pxDeltaY / this.gridSize[0]); // TODO: should this be gridSize[1]?
let {x, y, x2, y2} = this.item;
let dragPosition = {x, y, x2, y2};

View File

@@ -31,16 +31,10 @@
<div
v-if="domainObject"
class="c-telemetry-view"
:class="{
styleClass,
'is-missing': domainObject.status === 'missing'
}"
:class="styleClass"
:style="styleObject"
@contextmenu.prevent="showContextMenu"
>
<div class="is-missing__indicator"
title="This item is missing"
></div>
<div
v-if="showLabel"
class="c-telemetry-view__label"

View File

@@ -1,60 +0,0 @@
.c-box-view {
border-width: $drawingObjBorderW !important;
display: flex;
align-items: stretch;
.c-frame & {
@include abs();
}
}
.c-line-view {
&.c-frame {
box-shadow: none !important;
}
.c-frame-edit {
border: none;
}
.c-handle-info {
background: rgba(#999, 0.2);
padding: 2px;
position: absolute;
top: 5px; left: 5px;
white-space: nowrap;
}
svg {
// Prevent clipping when line is horizontal and vertical
min-height: 1px;
min-width: 1px;
// Must use !important to counteract setting in normalize.min.css
overflow: visible;
}
&__line {
stroke-linecap: round;
stroke-width: $drawingObjBorderW;
}
&__hover-indicator {
display: none;
opacity: 0.5;
stroke: $editFrameColorHov;
stroke-width: $drawingObjBorderW + 4;
}
.is-editing & {
// Needed to allow line to be moved
$w: 4px;
min-width: $w;
min-height: $w;
&:hover {
[class*='__hover-indicator'] {
display: inline;
}
}
}
}

View File

@@ -0,0 +1,8 @@
.c-box-view {
display: flex;
align-items: stretch;
.c-frame & {
@include abs();
}
}

View File

@@ -7,10 +7,6 @@
> *:first-child {
flex: 1 1 auto;
}
&.is-in-small-container {
//background: rgba(blue, 0.1);
}
}
.c-frame-edit__move {
@@ -65,7 +61,7 @@
.l-layout {
/******************* 0 - 1 ITEM SELECTED */
&:not(.is-multi-selected) {
> .l-layout__frame {
> .l-layout__frame[s-selected] {
> .c-so-view.has-complex-content {
> .c-so-view__local-controls {
transition: transform $transOutTime ease-in-out;
@@ -93,7 +89,7 @@
$lrOffset: 25%;
@include grippy($editFrameMovebarColorFg);
content: '';
display: none;
display: block;
position: absolute;
top: $tbOffset;
right: $lrOffset;
@@ -123,13 +119,6 @@
}
}
}
> .l-layout__frame[s-selected] {
> .c-so-view.has-complex-content {
+ .c-frame-edit__move:before {
display: block;
}
}
}
}
/******************* > 1 ITEMS SELECTED */

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,37 @@
/******************************* LIST ITEM */
.c-list-item {
&__type-icon {
color: $colorItemTreeIcon;
&__name a {
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();
}
&.is-alias {
// Object is an alias to an original.
[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;
&:hover {
background: $colorItemTreeHoverBg;
filter: $filterHov;
background: $colorListItemBgHov;
transition: $transIn;
}
}

View File

@@ -31,11 +31,11 @@
<div class="c-imagery__control-bar">
<div class="c-imagery__timestamp">{{ getTime() }}</div>
<div class="h-local-controls flex-elem">
<button
<a
class="c-button icon-pause pause-play"
:class="{'is-paused': paused()}"
@click="paused(!paused())"
></button>
></a>
</div>
</div>
</div>

View File

@@ -42,7 +42,6 @@
height: 135px;
overflow-x: auto;
overflow-y: hidden;
padding-bottom: $interiorMarginSm;
&.is-paused {
background: rgba($colorPausedBg, 0.4);
@@ -100,7 +99,7 @@
.c-imagery {
.h-local-controls--overlay-content {
position: absolute;
left: $interiorMargin; top: $interiorMargin;
right: $interiorMargin; top: $interiorMargin;
z-index: 2;
background: $colorLocalControlOvrBg;
border-radius: $basicCr;

View File

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

View File

@@ -24,14 +24,14 @@ import uuid from 'uuid';
export default class NewFolderAction {
constructor(openmct) {
this.name = 'Add New Folder';
this.name = 'New Folder';
this.key = 'newFolder';
this.description = 'Create a new folder';
this.cssClass = 'icon-folder-new';
this.cssClass = 'icon-folder';
this._openmct = openmct;
this._dialogForm = {
name: "Add New Folder",
name: "New Folder Name",
sections: [
{
rows: [
@@ -39,9 +39,7 @@ export default class NewFolderAction {
key: "name",
control: "textfield",
name: "Folder Name",
pattern: "\\S+",
required: true,
cssClass: "l-input-lg"
required: false
}
]
}
@@ -55,7 +53,7 @@ export default class NewFolderAction {
dialogService = this._openmct.$injector.get('dialogService'),
folderType = this._openmct.types.get('folder');
dialogService.getUserInput(this._dialogForm, {name: 'Unnamed Folder'}).then((userInput) => {
dialogService.getUserInput(this._dialogForm, {}).then((userInput) => {
let name = userInput.name,
identifier = {
key: uuid(),

View File

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

View File

@@ -21,34 +21,24 @@
-->
<div class="gl-plot plot-legend-{{legend.get('position')}} {{legend.get('expanded')? 'plot-legend-expanded' : 'plot-legend-collapsed'}}">
<div class="c-plot-legend gl-plot-legend"
ng-class="{
'hover-on-plot': !!highlights.length,
'is-legend-hidden': legend.get('hideLegendWhenSmall')
}"
>
ng-class="{ 'hover-on-plot': !!highlights.length }"
ng-show="legend.get('position') !== 'hidden'">
<div class="c-plot-legend__view-control gl-plot-legend__view-control c-disclosure-triangle is-enabled"
ng-class="{ 'c-disclosure-triangle--expanded': legend.get('expanded') }"
ng-click="legend.set('expanded', !legend.get('expanded'));">
</div>
<div class="c-plot-legend__wrapper"
ng-class="{ 'is-cursor-locked': !!lockHighlightPoint }">
<div class="c-plot-legend__wrapper">
<!-- COLLAPSED PLOT LEGEND -->
<div class="plot-wrapper-collapsed-legend"
ng-class="{'is-cursor-locked': !!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>
ng-class="{'icon-cursor-lock': !!lockHighlightPoint}">
<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">
<span class="plot-series-color-swatch"
ng-style="{ 'background-color': series.get('color').asHexString() }">
</span>
<span class="is-missing__indicator" title="This item is missing"></span>
<span class="plot-series-name">{{ series.get('name') }}</span>
</div>
<div class="plot-series-value hover-value-enabled value-to-display-{{ legend.get('valueToShowWhenCollapsed') }} {{ series.closest.mctLimitState.cssClass }}"
@@ -65,10 +55,7 @@
</div>
<!-- EXPANDED PLOT 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>
<div class="plot-wrapper-expanded-legend">
<table>
<thead>
<tr>
@@ -89,17 +76,12 @@
</th>
</tr>
</thead>
<tr ng-repeat="series in series"
class="plot-legend-item"
ng-class="{
'is-missing': series.domainObject.status === 'missing'
}"
>
<td class="plot-series-swatch-and-name">
<tr ng-repeat="series in series" class="plot-legend-item">
<td class="plot-series-swatch-and-name"
ng-class="{'icon-cursor-lock': !!lockHighlightPoint}">
<span class="plot-series-color-swatch"
ng-style="{ 'background-color': series.get('color').asHexString() }">
</span>
<span class="is-missing__indicator" title="This item is missing"></span>
<span class="plot-series-name">{{ series.get('name') }}</span>
</td>
@@ -135,7 +117,7 @@
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
<div class="gl-plot-axis-area gl-plot-y has-local-controls"
ng-style="{
width: (tickWidth + 20) + 'px'
width: (tickWidth + 30) + 'px'
}">
<div class="gl-plot-label gl-plot-y-label"
@@ -152,6 +134,7 @@
{{option.name}}
</option>
</select>
<mct-ticks axis="yAxis">
<div ng-repeat="tick in ticks track by tick.value"
@@ -165,15 +148,17 @@
</div>
<div class="gl-plot-wrapper-display-area-and-x-axis"
ng-style="{
left: (tickWidth + 20) + 'px'
}">
left: (tickWidth + 30) + 'px'
}">
<div class="l-state-indicators">
<span class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."></span>
<span class="l-state-indicators__alert-cursor-lock icon-cursor-lock"
title="Telemetry point selection is locked. Click anywhere in the plot to unlock."
ng-if="lockHighlightPoint"></span>
</div>
<div class="gl-plot-display-area has-local-controls has-cursor-guides">
<div class="l-state-indicators">
<span class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."></span>
</div>
<mct-ticks axis="xAxis">
<div class="gl-plot-hash hash-v"
ng-repeat="tick in ticks track by tick.value"

View File

@@ -44,7 +44,7 @@
</li>
<li class="grid-row">
<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">{{ {
'none': 'None',
'linear': 'Linear interpolation',
@@ -56,7 +56,7 @@
<div class="grid-cell label"
title="Whether markers are displayed, and their size.">Markers</div>
<div class="grid-cell value">
{{ series.markerOptionsDisplayText() }}
{{series.get('markers') ? "Enabled: " + series.get('markerSize') + "px" : "Disabled"}}
</div>
</li>
<li class="grid-row">
@@ -114,11 +114,6 @@
title="The position of the legend relative to the plot display area.">Position</div>
<div class="grid-cell value capitalize">{{ config.legend.get('position') }}</div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="Hide the legend when the plot is small">Hide when plot small</div>
<div class="grid-cell value">{{ config.legend.get('hideLegendWhenSmall') ? "Yes" : "No" }}</div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="Show the legend expanded by default">Expand by Default</div>

View File

@@ -52,7 +52,7 @@
</li>
<li class="grid-row">
<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">
<select ng-model="form.interpolate">
<option value="none">None</option>
@@ -64,27 +64,12 @@
<li class="grid-row">
<div class="grid-cell label"
title="Whether markers are displayed.">Markers</div>
<div class="grid-cell value">
<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>
<div class="grid-cell value"><input type="checkbox" ng-model="form.markers"/></div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="Display markers visually denoting points in alarm.">Alarm Markers</div>
<div class="grid-cell value">
<input type="checkbox" ng-model="form.alarmMarkers"/>
</div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.alarmMarkers"/></div>
</li>
<li class="grid-row" ng-show="form.markers || form.alarmMarkers">
<div class="grid-cell label"
@@ -174,6 +159,7 @@
title="The position of the legend relative to the plot display area.">Position</div>
<div class="grid-cell value">
<select ng-model="form.position">
<option value="hidden">Hidden</option>
<option value="top">Top</option>
<option value="right">Right</option>
<option value="bottom">Bottom</option>
@@ -181,11 +167,6 @@
</select>
</div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="Hide the legend when the plot is small">Hide when plot small</div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.hideLegendWhenSmall"/></div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="Show the legend expanded by default">Expand by default</div>

View File

@@ -371,8 +371,7 @@ function (
chartElement.getBuffer(),
chartElement.color().asRGBAArray(),
chartElement.count,
chartElement.series.get('markerSize'),
chartElement.series.get('markerShape')
chartElement.series.get('markerSize')
);
};
@@ -396,10 +395,9 @@ function (
this.offset.yVal(highlight.point, highlight.series)
]),
color = highlight.series.get('color').asRGBAArray(),
pointCount = 1,
shape = highlight.series.get('markerShape');
pointCount = 1;
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE, shape);
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE);
};
MCTChartController.prototype.drawRectangles = function () {

View File

@@ -48,7 +48,6 @@ define([
return {
position: 'top',
expandByDefault: false,
hideLegendWhenSmall: false,
valueToShowWhenCollapsed: 'nearestValue',
showTimestampWhenExpanded: true,
showValueWhenExpanded: true,

View File

@@ -25,14 +25,12 @@ define([
'lodash',
'../configuration/Model',
'../lib/extend',
'EventEmitter',
'../draw/MarkerShapes'
'EventEmitter'
], function (
_,
Model,
extend,
EventEmitter,
MARKER_SHAPES
EventEmitter
) {
/**
@@ -58,7 +56,6 @@ define([
* `linear` (points are connected via straight lines), or
* `stepAfter` (points are connected by steps).
* `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.
* `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
@@ -104,7 +101,6 @@ define([
xKey: options.collection.plot.xAxis.get('key'),
yKey: range.key,
markers: true,
markerShape: 'point',
markerSize: 2.0,
alarmMarkers: true
};
@@ -414,18 +410,6 @@ define([
} else {
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

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

View File

@@ -23,64 +23,30 @@
define([
'EventEmitter',
'../lib/eventHelpers',
'./MarkerShapes'
'../lib/eventHelpers'
], function (
EventEmitter,
eventHelpers,
MARKER_SHAPES
eventHelpers
) {
// WebGL shader sources (for drawing plain colors)
const FRAGMENT_SHADER = `
precision mediump float;
uniform vec4 uColor;
uniform int uMarkerShape;
void main(void) {
gl_FragColor = uColor;
if (uMarkerShape > 1) {
vec2 clipSpacePointCoord = 2.0 * gl_PointCoord - 1.0;
if (uMarkerShape == 2) { // circle
float distance = length(clipSpacePointCoord);
if (distance > 1.0) {
discard;
}
} 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;
}
`;
var FRAGMENT_SHADER = [
"precision mediump float;",
"uniform vec4 uColor;",
"void main(void) {",
"gl_FragColor = uColor;",
"}"
].join('\n'),
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;",
"}"
].join('\n');
/**
* Create a draw api utilizing WebGL.
@@ -124,7 +90,6 @@ define([
this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
this.gl.shaderSource(this.vertexShader, VERTEX_SHADER);
this.gl.compileShader(this.vertexShader);
this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
this.gl.shaderSource(this.fragmentShader, FRAGMENT_SHADER);
this.gl.compileShader(this.fragmentShader);
@@ -140,7 +105,6 @@ define([
// shader programs (to pass values into shaders at draw-time)
this.aVertexPosition = this.gl.getAttribLocation(this.program, "aVertexPosition");
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.uOrigin = this.gl.getUniformLocation(this.program, "uOrigin");
this.uPointSize = this.gl.getUniformLocation(this.program, "uPointSize");
@@ -150,6 +114,9 @@ define([
// Create a buffer to holds points which will be drawn
this.buffer = this.gl.createBuffer();
// Use a line width of 2.0 for legibility
this.gl.lineWidth(2.0);
// Enable blending, for smoothness
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
@@ -171,18 +138,14 @@ define([
((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) {
return;
}
const shapeCode = MARKER_SHAPES[shape] ? MARKER_SHAPES[shape].drawWebGL : 0;
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
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.uniform4fv(this.uColor, color);
this.gl.uniform1i(this.uMarkerShape, shapeCode)
this.gl.drawArrays(drawType, 0, points);
};
@@ -247,12 +210,12 @@ define([
* 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) {
return;
}
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

@@ -32,11 +32,6 @@ define([
modelProp: 'position',
objectPath: 'configuration.legend.position'
},
{
modelProp: 'hideLegendWhenSmall',
coerce: Boolean,
objectPath: 'configuration.legend.hideLegendWhenSmall'
},
{
modelProp: 'expandByDefault',
coerce: Boolean,

View File

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

View File

@@ -71,6 +71,8 @@ define([
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this);
this.watchForMarquee();
};
MCTPlotController.prototype.initialize = function () {
@@ -81,6 +83,11 @@ define([
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, 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.tickWidth = 0;
@@ -236,16 +243,12 @@ define([
};
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, 'mousemove', this.trackMousePosition, this);
if (event.altKey) {
if (this.allowPan) {
return this.startPan($event);
} else {
}
if (this.allowMarquee) {
return this.startMarquee($event);
}
};
@@ -258,11 +261,11 @@ define([
this.$scope.lockHighlightPoint = !this.$scope.lockHighlightPoint;
}
if (this.pan) {
if (this.allowPan) {
return this.endPan($event);
}
if (this.marquee) {
if (this.allowMarquee) {
return this.endMarquee($event);
}
};
@@ -286,9 +289,6 @@ define([
};
MCTPlotController.prototype.startMarquee = function ($event) {
this.$canvas.removeClass('plot-drag');
this.$canvas.addClass('plot-marquee');
this.trackMousePosition($event);
if (this.positionOverPlot) {
this.freeze();
@@ -444,9 +444,6 @@ define([
};
MCTPlotController.prototype.startPan = function ($event) {
this.$canvas.addClass('plot-drag');
this.$canvas.removeClass('plot-marquee');
this.trackMousePosition($event);
this.freeze();
this.pan = {
@@ -489,6 +486,32 @@ define([
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 () {
this.config.yAxis.set('frozen', true);
this.config.xAxis.set('frozen', true);

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

@@ -17,19 +17,6 @@
margin-right: $interiorMarginSm;
opacity: 0.7;
}
&__label {
flex: 1 1 auto;
}
&__close-btn {
flex: 0 0 auto;
pointer-events: all;
}
> * + * {
margin-left: $interiorMargin;
}
}
&__object-holder {

View File

@@ -3,7 +3,7 @@
<div
class="c-tabs-view__tabs-holder c-tabs"
:class="{
'is-dragging': isDragging && allowEditing,
'is-dragging': isDragging,
'is-mouse-over': allowDrop
}"
>
@@ -19,21 +19,18 @@
>
Drag objects here to add them to this view.
</div>
<div
<button
v-for="(tab,index) in tabsList"
:key="index"
class="c-tab c-tabs-view__tab"
:class="{
'is-current': isCurrent(tab)
}"
class="c-tabs-view__tab c-tab"
:class="[
{'is-current': isCurrent(tab)},
tab.type.definition.cssClass
]"
@click="showTab(tab, index)"
>
<span class="c-button__label c-tabs-view__tab__label">{{ tab.domainObject.name }}</span>
<button v-if="isEditing"
class="icon-x c-click-icon c-tabs-view__tab__close-btn"
@click="showRemoveDialog(index)"
></button>
</div>
<span class="c-button__label">{{ tab.domainObject.name }}</span>
</button>
</div>
<div
v-for="(tab, index) in tabsList"
@@ -41,6 +38,15 @@
class="c-tabs-view__object-holder"
: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
v-if="internalDomainObject.keep_alive ? currentTab : isCurrent(tab)"
class="c-tabs-view__object"
@@ -52,13 +58,6 @@
<script>
import ObjectView from '../../../ui/components/ObjectView.vue';
import RemoveAction from '../../remove/RemoveAction.js';
import {
getSearchParam,
setSearchParam,
deleteSearchParam
} from 'utils/openmctLocation';
var unknownObjectType = {
definition: {
@@ -72,114 +71,53 @@ export default {
components: {
ObjectView
},
props: {
isEditing: {
type: Boolean,
required: true
}
},
data: function () {
let keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
return {
internalDomainObject: this.domainObject,
currentTab: {},
currentTabIndex: undefined,
tabsList: [],
setCurrentTab: true,
isDragging: false,
allowDrop: false,
searchTabKey: `tabs.pos.${keyString}`
allowDrop: false
};
},
computed: {
allowEditing() {
return !this.internalDomainObject.locked && this.isEditing;
}
},
mounted() {
if (this.composition) {
this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem);
this.composition.on('reorder', this.onReorder);
this.composition.load().then(() => {
let currentTabIndexFromURL = getSearchParam(this.searchTabKey);
let currentTabIndexFromDomainObject = this.internalDomainObject.currentTabIndex;
let currentTabIndex = this.domainObject.currentTabIndex;
if (currentTabIndexFromURL !== null) {
this.setCurrentTabByIndex(currentTabIndexFromURL);
} else if (currentTabIndexFromDomainObject !== undefined) {
this.setCurrentTabByIndex(currentTabIndexFromDomainObject);
this.storeCurrentTabIndexInURL(currentTabIndexFromDomainObject);
if (currentTabIndex !== undefined && this.tabsList.length > currentTabIndex) {
this.currentTab = this.tabsList[currentTabIndex];
}
});
}
this.unsubscribe = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
this.RemoveAction = new RemoveAction(this.openmct);
document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend);
},
beforeDestroy() {
this.persistCurrentTabIndex(this.currentTabIndex);
},
destroyed() {
this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.onReorder);
this.unsubscribe();
this.clearCurrentTabIndexFromURL();
document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend);
},
methods:{
setCurrentTabByIndex(index) {
if (this.tabsList[index]) {
this.currentTab = this.tabsList[index];
}
},
showTab(tab, index) {
if (index !== undefined) {
this.storeCurrentTabIndexInURL(index);
this.storeCurrentTabIndex(index);
}
this.currentTab = tab;
},
showRemoveDialog(index) {
if(!this.tabsList[index]) {
return;
}
let activeTab = this.tabsList[index];
let childDomainObject = activeTab.domainObject
let prompt = this.openmct.overlays.dialog({
iconClass: 'alert',
message: `This action will remove this tab from the Tabs Layout. Do you want to continue?`,
buttons: [
{
label: 'Ok',
emphasis: 'true',
callback: () => {
this.removeFromComposition(childDomainObject);
prompt.dismiss();
}
},
{
label: 'Cancel',
callback: () => {
prompt.dismiss();
}
}
]
});
},
removeFromComposition(childDomainObject) {
this.composition.remove(childDomainObject);
},
addItem(domainObject) {
let type = this.openmct.types.get(domainObject.type) || unknownObjectType,
tabItem = {
@@ -195,10 +133,6 @@ export default {
this.setCurrentTab = false;
}
},
reset() {
this.currentTab = {};
this.setCurrentTab = true;
},
removeItem(identifier) {
let pos = this.tabsList.findIndex(tab =>
tab.domainObject.identifier.namespace === identifier.namespace && tab.domainObject.identifier.key === identifier.key
@@ -210,10 +144,6 @@ export default {
if (this.isCurrent(tabToBeRemoved)) {
this.showTab(this.tabsList[this.tabsList.length - 1], this.tabsList.length - 1);
}
if (!this.tabsList.length) {
this.reset();
}
},
onReorder(reorderPlan) {
let oldTabs = this.tabsList.slice();
@@ -224,7 +154,7 @@ export default {
},
onDrop(e) {
this.setCurrentTab = true;
this.storeCurrentTabIndexInURL(this.tabsList.length);
this.storeCurrentTabIndex(this.tabsList.length);
},
dragstart(e) {
if (e.dataTransfer.types.includes('openmct/domain-object-path')) {
@@ -247,19 +177,8 @@ export default {
updateInternalDomainObject(domainObject) {
this.internalDomainObject = domainObject;
},
persistCurrentTabIndex(index) {
storeCurrentTabIndex(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;
return {
show: function (element, editMode) {
show: function (element) {
component = new Vue({
el: element,
components: {
TabsComponent: TabsComponent.default
},
data() {
return {
isEditing: editMode
};
},
provide: {
openmct,
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) {
component.$destroy();
component = undefined;

View File

@@ -54,16 +54,6 @@
width: 100%;
}
}
&__filter {
.c-table__search {
padding-top: 0;
padding-bottom: 0;
}
.is-in-small-container & {
display: none;
}
}
}
&__headers__label {
@@ -96,10 +86,6 @@
height: 0; // Fixes Chrome 73 overflow bug
overflow-x: auto;
overflow-y: scroll;
.is-editing & {
pointer-events: none;
}
}
/******************************* TABLES */
@@ -152,7 +138,7 @@
}
}
/******************************* SPECIFIC CASE WRAPPERS */
/******************************* EDITING */
.is-editing {
.c-telemetry-table__headers__labels {
th[draggable],
@@ -172,10 +158,8 @@
}
}
.is-paused {
.c-table__body-w {
border: 1px solid rgba($colorPausedBg, 0.8);
}
.paused {
border: 1px solid #ff9900;
}
/******************************* LEGACY */

View File

@@ -20,16 +20,13 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-table-wrapper"
:class="{ 'is-paused': paused }"
>
<div class="c-table-wrapper">
<!-- main contolbar start-->
<div v-if="!marking.useAlternateControlBar"
class="c-table-control-bar c-control-bar"
>
<button
v-if="allowExport"
v-show="!markedRows.length"
class="c-button icon-download labeled"
title="Export this view's data"
@click="exportAllDataAsCSV()"
@@ -128,7 +125,7 @@
class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
:class="{
'loading': loading,
'is-paused' : paused
'paused' : paused
}"
>
<div :style="{ 'max-width': widthWithScroll, 'min-width': '150px'}">

View File

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

View File

@@ -24,12 +24,7 @@
ref="axisHolder"
class="c-conductor-axis"
@mousedown="dragStart($event)"
>
<div
class="c-conductor-axis__zoom-indicator"
:style="zoomStyle"
></div>
</div>
></div>
</template>
<script>
@@ -48,81 +43,52 @@ const PIXELS_PER_TICK_WIDE = 200;
export default {
inject: ['openmct'],
props: {
viewBounds: {
bounds: {
type: Object,
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: {
viewBounds: {
handler() {
bounds: {
handler(bounds) {
this.setScale();
},
deep: true
}
},
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.dragging = false;
// draw x axis with labels. CSS is used to position them.
this.axisElement = vis.append("g")
.attr("class", "axis");
this.axisElement = vis.append("g");
this.setViewFromTimeSystem(this.openmct.time.timeSystem());
this.setAxisDimensions();
this.setScale();
//Respond to changes in conductor
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
setInterval(this.resize, RESIZE_POLL_INTERVAL);
},
destroyed() {
},
methods: {
setAxisDimensions() {
const axisHolder = this.$refs.axisHolder;
const rect = axisHolder.getBoundingClientRect();
this.left = Math.round(rect.left);
this.width = axisHolder.clientWidth;
},
setScale() {
if (!this.width) {
return;
}
let timeSystem = this.openmct.time.timeSystem();
let bounds = this.bounds;
if (timeSystem.isUTCBased) {
this.xScale.domain(
[new Date(this.viewBounds.start), new Date(this.viewBounds.end)]
);
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
} else {
this.xScale.domain(
[this.viewBounds.start, this.viewBounds.end]
);
this.xScale.domain([bounds.start, bounds.end]);
}
this.xAxis.scale(this.xScale);
@@ -136,7 +102,7 @@ export default {
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) {
//The D3 scale used depends on the type of time system as d3
@@ -154,8 +120,9 @@ export default {
},
getActiveFormatter() {
let timeSystem = this.openmct.time.timeSystem();
let isFixed = this.openmct.time.clock() === undefined;
if (this.isFixed) {
if (isFixed) {
return this.getFormatter(timeSystem.timeFormat);
} else {
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
@@ -167,131 +134,45 @@ export default {
}).formatter;
},
dragStart($event) {
if (this.isFixed) {
let isFixed = this.openmct.time.clock() === undefined;
if (isFixed) {
this.dragStartX = $event.clientX;
if (this.altPressed) {
this.inPanMode = true;
}
document.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.dragEnd, {
once: true
});
if (this.inZoomMode) {
this.startZoom();
}
}
},
drag($event) {
if (!this.dragging) {
this.dragging = true;
requestAnimationFrame(() => {
this.dragX = $event.clientX;
this.inPanMode ? this.pan() : this.zoom();
requestAnimationFrame(()=>{
let deltaX = $event.clientX - this.dragStartX;
let percX = deltaX / this.width;
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;
});
})
} else {
console.log('Rejected drag due to RAF cap');
}
},
dragEnd() {
this.inPanMode ? this.endPan() : this.endZoom();
document.removeEventListener('mousemove', this.drag);
this.dragStartX = undefined;
this.dragX = undefined;
},
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
this.openmct.time.bounds({
start: this.bounds.start,
end: this.bounds.end
});
},
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() {
if (this.$refs.axisHolder.clientWidth !== this.width) {
this.setAxisDimensions();
this.width = this.$refs.axisHolder.clientWidth;
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) {
return {
key: 'fixed',
name: 'Fixed Timespan',
name: 'Fixed Timespan Mode',
description: 'Query and explore data that falls between two fixed datetimes.',
cssClass: 'icon-tabular'
}

View File

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

View File

@@ -84,10 +84,6 @@ $filterHov: brightness(1.3); // Tree, location items
$colorSelectedBg: pushBack($colorKey, 10%);
$colorSelectedFg: pullForward($colorBodyFg, 20%);
// Object labels
$objectLabelTypeIconOpacity: 0.7;
$objectLabelNameFilter: brightness(1.3);
// Layout
$shellMainPad: 4px 0;
$shellPanePad: $interiorMargin, 7px;
@@ -98,7 +94,7 @@ $sideBarHeaderBg: rgba($colorBodyFg, 0.2);
$sideBarHeaderFg: rgba($colorBodyFg, 0.7);
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #888;
$colorStatusFg: #999;
$colorStatusDefault: #ccc;
$colorStatusInfo: #60ba7b;
$colorStatusInfoFilter: invert(58%) sepia(44%) saturate(405%) hue-rotate(85deg) brightness(102%) contrast(92%);
@@ -146,19 +142,12 @@ $colorTimeHov: pullForward($colorTime, 10%);
$colorTimeSubtle: pushBack($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(1.2);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #226074;
/************************************************** BROWSING */
$browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$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 */
$editUIColor: $uiColor; // Base color
@@ -209,9 +198,9 @@ $colorBtnMajorBg: $colorKey;
$colorBtnMajorBgHov: $colorKeyHov;
$colorBtnMajorFg: $colorKeyFg;
$colorBtnMajorFgHov: pushBack($colorBtnMajorFg, 10%);
$colorBtnCautionBg: $colorStatusAlert;
$colorBtnCautionBg: #f16f6f;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnBg;
$colorBtnCautionFg: $colorBtnFg;
$colorBtnActiveBg: $colorOk;
$colorBtnActiveFg: $colorOkFg;
$colorBtnSelectedBg: $colorSelectedBg;
@@ -339,7 +328,7 @@ $shdwItemText: none;
$colorTabBorder: pullForward($colorBodyBg, 10%);
$colorTabBodyBg: $colorBodyBg;
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
$colorTabHeaderBg: rgba($colorBodyFg, 0.15);
$colorTabHeaderBg: rgba($colorBodyFg, 0.2);
$colorTabHeaderFg: $colorBodyFg;
$colorTabHeaderBorder: $colorBodyBg;
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
@@ -357,7 +346,7 @@ $stylePlotHash: dashed;
$colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
$legendTableHeadBg: $colorTabHeaderBg;
$legendTableHeadBg: rgba($colorBodyFg, 0.15);
// Tree
$colorTreeBg: transparent;

View File

@@ -88,10 +88,6 @@ $filterHov: brightness(1.3); // Tree, location items
$colorSelectedBg: pushBack($colorKey, 10%);
$colorSelectedFg: pullForward($colorBodyFg, 20%);
// Object labels
$objectLabelTypeIconOpacity: 0.7;
$objectLabelNameFilter: brightness(1.3);
// Layout
$shellMainPad: 4px 0;
$shellPanePad: $interiorMargin, 7px;
@@ -150,19 +146,12 @@ $colorTimeHov: pullForward($colorTime, 10%);
$colorTimeSubtle: pushBack($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(1.2);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #226074;
/************************************************** BROWSING */
$browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$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 */
$editUIColor: $uiColor; // Base color
@@ -213,9 +202,9 @@ $colorBtnMajorBg: $colorKey;
$colorBtnMajorBgHov: $colorKeyHov;
$colorBtnMajorFg: $colorKeyFg;
$colorBtnMajorFgHov: pushBack($colorBtnMajorFg, 10%);
$colorBtnCautionBg: $colorStatusAlert;
$colorBtnCautionBg: #f16f6f;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnBg;
$colorBtnCautionFg: $colorBtnFg;
$colorBtnActiveBg: $colorOk;
$colorBtnActiveFg: $colorOkFg;
$colorBtnSelectedBg: $colorSelectedBg;
@@ -343,7 +332,7 @@ $shdwItemText: none;
$colorTabBorder: pullForward($colorBodyBg, 10%);
$colorTabBodyBg: $colorBodyBg;
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
$colorTabHeaderBg: rgba($colorBodyFg, 0.15);
$colorTabHeaderBg: rgba($colorBodyFg, 0.2);
$colorTabHeaderFg: $colorBodyFg;
$colorTabHeaderBorder: $colorBodyBg;
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);

View File

@@ -80,14 +80,10 @@ $uiColor: #289fec; // Resize bars, splitter bars, etc.
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: #999;
$colorAHov: $colorKey;
$filterHov: brightness(0.8) contrast(2); // Tree, location items
$filterHov: brightness(1.3); // Tree, location items
$colorSelectedBg: pushBack($colorKey, 40%);
$colorSelectedFg: pullForward($colorBodyFg, 10%);
// Object labels
$objectLabelTypeIconOpacity: 0.5;
$objectLabelNameFilter: brightness(0.5);
// Layout
$shellMainPad: 4px 0;
$shellPanePad: $interiorMargin, 7px;
@@ -136,7 +132,7 @@ $colorPausedFg: #fff;
// Base variations
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
$colorBodyBgSubtleHov: pushBack($colorKey, 50%);
$colorKeySubtle: pushBack($colorKey, 20%);
$colorKeySubtle: pushBack($colorKey, 10%);
// Time Colors
$colorTime: #618cff;
@@ -146,19 +142,12 @@ $colorTimeHov: pushBack($colorTime, 5%);
$colorTimeSubtle: pushBack($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(0.8);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #A0CDE1;
/************************************************** BROWSING */
$browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(0.9);
$filterItemMissing: contrast(0.2);
$opacityMissing: 0.4;
$borderMissing: 1px dashed $colorAlert !important;
/************************************************** EDITING */
$editUIColor: $uiColor; // Base color
@@ -228,7 +217,7 @@ $colorDisclosureCtrlHov: rgba($colorBodyFg, 0.7);
$btnStdH: 24px;
$colorCursorGuide: rgba(black, 0.6);
$shdwCursorGuide: rgba(white, 0.4) 0 0 2px;
$colorLocalControlOvrBg: rgba($colorBodyBg, 0.8);
$colorLocalControlOvrBg: rgba($colorBodyFg, 0.8);
$colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due to usage of SVG bg in selects
$colorSelectFg: $colorBtnFg;
$colorSelectArw: lighten($colorBtnBg, 20%);

View File

@@ -40,10 +40,11 @@ $inputTextP: $inputTextPTopBtm $inputTextPLeftRight;
$menuLineH: 1.5rem;
$treeItemIndent: 16px;
$treeTypeIconW: 18px;
$overlayOuterMarginLg: 5%;
$overlayOuterMarginFullscreen: 0%;
$overlayOuterMarginDialog: 20%;
$overlayInnerMargin: 25px;
$mainViewPad: 0px;
$mainViewPad: 2px;
/*************** Items */
$itemPadLR: 5px;
$gridItemDesk: 175px;
@@ -56,11 +57,11 @@ $tabularTdPadTB: 2px;
$plotYBarW: 60px;
$plotYLabelMinH: 20px;
$plotYLabelW: 10px;
$plotXBarH: 32px;
$plotXBarH: 35px;
$plotLegendH: 20px;
$plotLegendWidthCollapsed: 20%;
$plotLegendWidthExpanded: 50%;
$plotSwatchD: 12px;
$plotSwatchD: 10px;
$plotDisplayArea: (0, 0, $plotXBarH, $plotYBarW); // 1: Top, 2: right, 3: bottom, 4: left
$plotMinH: 95px;
$controlBarH: 25px;
@@ -89,8 +90,6 @@ $messageIconD: 80px;
$messageListIconD: 32px;
/*************** Tables */
$tableResizeColHitareaD: 6px;
/*************** Misc */
$drawingObjBorderW: 3px;
/************************** MOBILE */
$mobileMenuIconD: 24px; // Used
@@ -203,8 +202,6 @@ $glyph-icon-history: '\ea31';
$glyph-icon-arrow-nav-to-parent: '\ea32';
$glyph-icon-crosshair-in-circle: '\ea33';
$glyph-icon-target: '\ea34';
$glyph-icon-items-collapse: '\ea35';
$glyph-icon-items-expand: '\ea36';
$glyph-icon-activity: '\eb00';
$glyph-icon-activity-mode: '\eb01';
$glyph-icon-autoflow-tabular: '\eb02';

View File

@@ -57,6 +57,11 @@ button {
line-height: 90%;
padding: 3px 10px;
@include hover() {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
@include desktop() {
font-size: 6px;
}
@@ -406,17 +411,7 @@ select {
.c-tab {
// Used in Tab View, generic tabs
$notchSize: 7px;
$clipPath:
polygon(
0% 0%,
calc(100% - #{$notchSize}) 0%,
100% #{$notchSize},
100% calc(100% - #{$notchSize}),
100% 100%,
0% 100%
);
background: rgba($colorBtnBg, 0.7);
background: $colorBtnBg;
color: $colorBtnFg;
cursor: pointer;
display: flex;
@@ -425,15 +420,21 @@ select {
margin: 1px 1px 0 0;
padding: $interiorMargin $interiorMarginLg;
white-space: nowrap;
clip-path: $clipPath;
-webkit-clip-path: $clipPath; // Safari
> * + * {
margin-left: $interiorMargin;
}
--notchSize: 7px;
clip-path:
polygon(
0% 0%,
calc(100% - var(--notchSize)) 0%,
100% var(--notchSize),
100% calc(100% - var(--notchSize)),
100% 100%,
0% 100%
);
@include hover() {
filter: $filterHov;
background: $colorBtnBgHov;
}
&.is-current {
@@ -461,17 +462,9 @@ select {
text-shadow: $shdwMenuText;
padding: $interiorMarginSm;
box-shadow: $shdwMenu;
display: flex;
flex-direction: column;
display: block;
position: absolute;
z-index: 100;
> * {
flex: 0 0 auto;
//+ * {
// margin-top: $interiorMarginSm;
//}
}
}
@mixin menuInner() {
@@ -509,23 +502,6 @@ select {
.c-menu {
@include menuOuter();
@include menuInner();
&__section-hint {
$m: $interiorMargin;
margin: $m 0;
padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
opacity: 0.6;
font-size: 0.9em;
font-style: italic;
}
&__section-separator {
$m: $interiorMargin;
border-top: 1px solid $colorInteriorBorder;
margin: $m 0;
padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
}
}
.c-super-menu {

View File

@@ -206,6 +206,10 @@ body.desktop .has-local-controls {
&:hover {
box-shadow: $browseSelectableShdwHov;
}
&[s-selected] {
border: $browseSelectedBorder;
}
}
/**************************** EDITING */

View File

@@ -138,8 +138,6 @@
.icon-arrow-nav-to-parent { @include glyphBefore($glyph-icon-arrow-nav-to-parent); }
.icon-crosshair-in-circle { @include glyphBefore($glyph-icon-crosshair-in-circle); }
.icon-target { @include glyphBefore($glyph-icon-target); }
.icon-items-collapse { @include glyphBefore($glyph-icon-items-collapse); }
.icon-items-expand { @include glyphBefore($glyph-icon-items-expand); }
.icon-activity { @include glyphBefore($glyph-icon-activity); }
.icon-activity-mode { @include glyphBefore($glyph-icon-activity-mode); }
.icon-autoflow-tabular { @include glyphBefore($glyph-icon-autoflow-tabular); }

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -29,26 +29,21 @@ mct-plot {
.gl-plot.child-frame {
&:hover {
background: rgba($editUIColorBg, 0.1);
box-shadow: inset rgba($editUIColorBg, 0.3) 0 0 0 1px;
box-shadow: inset rgba($editUIColorBg, 0.8) 0 0 0 1px;
}
&[s-selected] {
background: rgba($editUIColorBg, 0.2);
box-shadow: inset rgba($editUIColorBg, 0.8) 0 0 0 1px;
border: 1px solid $editUIColorFg !important;
color: $editUIColorFg !important;
box-shadow: $editFrameSelectedShdw;
z-index: 2;
}
}
.plot-wrapper-axis-and-display-area {
pointer-events: none;
}
}
.c-plot,
.gl-plot {
overflow: hidden;
.s-status-taking-snapshot & {
.s-status-taking-snapshot & {
.c-control-bar {
display: none;
}
@@ -56,35 +51,29 @@ mct-plot {
display: none;
}
}
/*********************** MISSING ITEM INDICATORS */
.is-missing__indicator {
display: none;
}
.is-missing {
@include isMissing();
.is-missing__indicator {
font-size: 0.8em;
}
}
}
.c-plot {
//$p: $mainViewPad;
@include abs($mainViewPad);
//position: absolute;
//top: $p; right: $p; bottom: $p; left: $p;
display: flex;
flex-direction: column;
.c-control-bar {
> * + * {
margin-top: $interiorMargin;
}
.l-control-bar {
flex: 0 0 auto;
margin-bottom: $interiorMargin;
}
.l-view-section {
display: flex;
flex: 1 1 auto;
flex-direction: column;
overflow: hidden;
}
&--stacked {
@@ -111,23 +100,39 @@ mct-plot {
}
}
}
.is-in-small-container & {
.c-control-bar {
display: none;
}
}
}
.gl-plot {
display: flex;
flex: 1 1 auto;
position: relative;
width: 100%;
height: 100%;
min-height: $plotMinH;
/*********************** AXIS AND DISPLAY AREA */
.plot-wrapper-axis-and-display-area {
position: relative;
flex: 1 1 auto;
min-height: $plotMinH;
.l-state-indicators {
color: $colorPausedBg;
position: absolute;
display: block;
font-size: 1.5em;
pointer-events: none;
top: $interiorMarginSm;
left: $interiorMarginSm;
z-index: 2;
> * + * {
margin-left: $interiorMarginSm;
}
.t-alert-unsynced {
display: none;
}
}
}
.gl-plot-wrapper-display-area-and-x-axis {
@@ -208,7 +213,9 @@ mct-plot {
&.gl-plot-y-label {
display: block;
left: 0; top: 0; right: auto; bottom: 0;
padding-left: 5px;
text-orientation: mixed;
//overflow: hidden;
writing-mode: vertical-lr;
&:before {
// Icon denoting configurability
@@ -273,7 +280,7 @@ mct-plot {
align-items: center;
position: absolute;
top: $m;
left: $m;
right: $m;
z-index: 9;
&__reset {
@@ -286,28 +293,6 @@ mct-plot {
top: $m;
right: $m;
}
.c-button {
box-shadow: $colorLocalControlOvrBg 0 0 0 2px;
}
}
.l-state-indicators {
color: $colorPausedBg;
position: absolute;
cursor: help;
font-size: 1.2em;
bottom: $interiorMarginSm;
left: $interiorMarginSm;
z-index: 2;
> * + * {
margin-left: $interiorMarginSm;
}
.t-alert-unsynced {
display: none;
}
}
}
@@ -356,11 +341,11 @@ mct-plot {
.gl-plot-tick {
&.gl-plot-x-tick-label {
top: $interiorMarginSm;
top: $interiorMargin;
}
&.gl-plot-y-tick-label {
right: $interiorMarginSm;
left: auto;
right: $interiorMargin;
left: $interiorMargin;
}
}
@@ -383,6 +368,12 @@ mct-plot {
z-index: -10;
.l-view-section {
//$m: $interiorMargin;
//top: $m !important;
//right: $m;
//bottom: $m;
//left: $m;
.s-status-timeconductor-unsynced .holder-plot {
.t-object-alert.t-alert-unsynced {
display: none;
@@ -438,18 +429,13 @@ mct-plot {
/****************** _LEGEND.SCSS */
.gl-plot-legend,
.c-plot-legend {
overflow: hidden;
&__wrapper {
// Holds view-control and both collapsed and expanded legends
flex: 1 1 auto;
height: 100%;
overflow: auto;
padding: 2px;
}
&__view-control {
padding-top: 4px;
padding-top: 2px;
margin-right: $interiorMarginSm;
}
@@ -457,22 +443,8 @@ mct-plot {
@include propertiesHeader();
margin-bottom: $interiorMarginSm;
}
.is-in-small-container & {
&.is-legend-hidden {
display: none;
}
}
}
.c-plot--stacked {
.is-legend-hidden {
// Always show the legend in a stacked plot
display: flex !important;
}
}
.gl-plot-legend {
display: flex;
align-items: flex-start;
@@ -508,21 +480,15 @@ mct-plot {
/***************** GENERAL STYLES, ALL STATES */
.plot-legend-item {
// General styles for legend items, both expanded and collapsed legend states
> * + * {
margin-left: $interiorMarginSm;
}
.plot-series-color-swatch {
border-radius: 30%; //$smallCr;
border-radius: $smallCr;
border: 1px solid $colorBodyBg;
display: inline-block;
flex: 0 0 auto;
height: $plotSwatchD;
width: $plotSwatchD;
}
.plot-series-name {
display: inline;
@include ellipsize();
}
.plot-series-value {
@@ -530,27 +496,11 @@ mct-plot {
}
}
.plot-series-swatch-and-name {
display: flex;
flex: 0 1 auto;
align-items: center;
> * + * {
margin-left: $interiorMarginSm;
}
}
.plot-wrapper-expanded-legend {
flex: 1 1 auto;
}
.plot-legend-top .gl-plot-legend { margin-bottom: $interiorMargin; }
.plot-legend-bottom .gl-plot-legend { margin-top: $interiorMargin; }
.plot-legend-left .gl-plot-legend { margin-right: $interiorMargin; }
.plot-legend-right .gl-plot-legend { margin-left: $interiorMargin; }
.gl-plot,
.c-plot {
.gl-plot {
&.plot-legend-collapsed .plot-wrapper-expanded-legend { display: none; }
&.plot-legend-expanded .plot-wrapper-collapsed-legend { display: none; }
@@ -568,14 +518,21 @@ mct-plot {
.plot-legend-item {
display: flex;
align-items: center;
justify-content: stretch;
&:not(:first-child) {
margin-left: $interiorMarginLg;
}
.plot-series-swatch-and-name,
.plot-series-value {
@include ellipsize();
flex: 1 1 auto;
}
.plot-series-swatch-and-name {
margin-right: $interiorMarginSm;
}
.plot-series-value {
text-align: left;
}
@@ -585,7 +542,7 @@ mct-plot {
/***************** GENERAL STYLES, EXPANDED */
&.plot-legend-expanded {
.gl-plot-legend {
max-height: 70%;
// max-height: 70%;
}
.plot-wrapper-expanded-legend {
@@ -596,10 +553,8 @@ mct-plot {
/***************** TOP OR BOTTOM */
&.plot-legend-top,
&.plot-legend-bottom,
&.plot-legend-hidden {
&.plot-legend-bottom {
// General styles when legend is on the top or bottom
// -hidden included for legacy plots
flex-direction: column;
&.plot-legend-collapsed {
@@ -608,11 +563,6 @@ mct-plot {
display: flex;
flex: 1 1 auto;
overflow: hidden;
> .plot-legend-item + .plot-legend-item {
// Space between plot items
margin-left: $interiorMarginLg;
}
}
}
}
@@ -621,7 +571,6 @@ mct-plot {
&.plot-legend-left,
&.plot-legend-right {
// General styles when legend is on left or right
.gl-plot-legend {
max-height: inherit;
}
@@ -645,18 +594,12 @@ mct-plot {
min-width: 0;
flex: 1 1 auto;
overflow-y: auto;
> * + * {
// Space between plot items
margin-top: $interiorMarginSm;
}
}
.plot-legend-item {
margin-bottom: $interiorMarginSm;
margin-bottom: 1px;
margin-left: 0;
flex-wrap: nowrap;
flex-wrap: wrap;
.plot-series-swatch-and-name {
@include ellipsize();
flex: 0 1 auto;
min-width: 20%;
}
@@ -680,31 +623,6 @@ mct-plot {
}
}
/***************** STACKED PLOT LEGEND OVERRIDES */
.c-plot--stacked {
// Always show the legend on top, ignore any position setting
.c-plot,
.gl-plot {
flex-direction: column !important;
.c-plot-legend,
.gl-plot-legend {
margin: 0;
margin-bottom: $interiorMargin;
order: 1 !important;
width: 100% !important;
.plot-wrapper-collapsed-legend {
flex-direction: row !important;
}
}
.plot-wrapper-axis-and-display-area {
order: 2 !important;
}
}
}
/***************** CURSOR GUIDES */
[class*='c-cursor-guide'] {
box-shadow: $shdwCursorGuide;
@@ -735,24 +653,3 @@ mct-plot {
display: inline-block !important;
}
}
/*********************** CURSOR LOCK INDICATOR */
[class*='c-state-indicator__alert-cursor-lock'] {
display: none;
}
[class*='is-cursor-locked'] {
background: rgba($colorInfo, 0.1);
[class*='c-state-indicator__alert-cursor-lock'] {
@include userSelectNone();
color: $colorInfo;
display: block;
margin-right: $interiorMarginSm;
&[class*='--verbose'] {
padding: $interiorMarginSm;
}
}
}

View File

@@ -893,7 +893,7 @@ body.desktop {
.grid-row {
.grid-cell {
padding: 3px $interiorMarginLg 3px 0;
&[title]:not([title=""]) {
&[title] {
// When a cell has a title, assume it's helpful text
cursor: help;
}

View File

@@ -117,33 +117,6 @@
}
}
@mixin isMissing($absPos: false) {
// Common styles to be applied to tree items, object labels, grid and list item views
//opacity: 0.7;
//pointer-events: none; // Don't think we can do this, as disables title hover on icon element
.is-missing__indicator {
display: none ;
text-shadow: $colorBodyBg 0 0 2px;
color: $colorAlert;
font-family: symbolsfont;
&:before {
content: $glyph-icon-alert-triangle;
}
}
@if $absPos {
.is-missing__indicator {
position: absolute;
z-index: 3;
}
}
&.is-missing .is-missing__indicator,
.is-missing .is-missing__indicator { display: block !important; }
}
@mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) {
background-image: linear-gradient(-45deg,
rgba($c, $a) 25%, transparent 25%,
@@ -443,18 +416,28 @@
}
@include hover() {
filter: $filterHov;
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
&[class*="--major"],
&[class*='is-active']{
background: $colorBtnMajorBg;
color: $colorBtnMajorFg;
@include hover() {
background: $colorBtnMajorBgHov;
color: $colorBtnMajorFgHov;
}
}
&[class*='--caution'] {
background: $colorBtnCautionBg !important;
color: $colorBtnCautionFg !important;
background: $colorBtnCautionBg;
color: $colorBtnCautionFg;
&:hover {
background: $colorBtnCautionBgHov;
}
}
}

View File

@@ -49,6 +49,8 @@ table {
td {
vertical-align: top;
}
a { color: $colorBtnMajorBg; }
}
.is-editing {
@@ -90,28 +92,14 @@ div.c-table {
flex: 1 1 auto;
}
.is-in-small-container & {
&:not(.is-paused) {
.c-table-control-bar {
display: none;
}
}
.c-table-control-bar {
.c-click-icon,
.c-button {
&__label {
display: none;
}
}
}
> * + * {
margin-top: $interiorMarginSm;
}
}
.c-table-control-bar {
display: flex;
flex: 0 0 auto;
//margin-bottom: $interiorMarginSm; // This approach to allow margin to go away when control bar is hidden
padding: $interiorMarginSm 0;
> * + * {
margin-left: $interiorMarginSm;

File diff suppressed because it is too large Load Diff

View File

@@ -106,8 +106,6 @@
<glyph unicode="&#xea32;" glyph-name="icon-arrow-up-to-parent" horiz-adv-x="1056" d="M643.427 6.739c-81.955 0.697-148.179 67.065-148.642 149.010v395.872l296.871-247.393v197.914l-395.828 329.857-395.828-328.62v-197.502l296.871 246.156v-396.241c0-190.905 155.239-346.556 346.144-346.968l412.321-0.825 0.412 197.914z" />
<glyph unicode="&#xea33;" glyph-name="icon-crosshair-in-circle" d="M512 832c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM783.6 48.4c-54.634-54.8-125.77-93.12-205.322-106.874l-2.278-0.326v250.8h-128v-250.8c-161.302 28.062-286.738 153.497-314.468 312.5l-0.332 2.3h250.8v128h-250.8c28.062 161.302 153.497 286.738 312.5 314.468l2.3 0.332v-250.8h128v250.8c161.302-28.062 286.738-153.497 314.468-312.5l0.332-2.3h-250.8v-128h250.8c-14.080-81.83-52.4-152.966-107.191-207.591l-0.009-0.009z" />
<glyph unicode="&#xea34;" glyph-name="icon-target" d="M512 448c70.692 0 128-57.308 128-128s-57.308-128-128-128c-70.692 0-128 57.308-128 128v0c0.114 70.647 57.353 127.886 127.989 128h0.011zM512 576c-141.385 0-256-114.615-256-256s114.615-256 256-256c141.385 0 256 114.615 256 256v0c-0.114 141.339-114.661 255.886-255.989 256h-0.011zM512 704c211.87-0.128 383.575-171.912 383.575-383.8 0-211.967-171.833-383.8-383.8-383.8s-383.8 171.833-383.8 383.8c0 105.99 42.963 201.945 112.425 271.4v0c69.21 69.437 164.944 112.401 270.713 112.401 0.312 0 0.624 0 0.936-0.001h-0.048zM512 832c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512z" />
<glyph unicode="&#xea35;" glyph-name="icon-items-collapse" d="M45.2 173.2h229.6l-274.8-274.6 90.6-90.6 274.6 274.8v-229.6h128v448h-448v-128zM1024 741.4l-90.6 90.6-274.6-274.8v229.6h-128v-448h448v128h-229.6l274.8 274.6z" />
<glyph unicode="&#xea36;" glyph-name="icon-items-expand" d="M448-64h-229.4l274.6 274.8-90.4 90.4-274.8-274.6v229.4h-128v-448h448v128zM530.8 429.2l90.4-90.4 274.8 274.6v-229.4h128v448h-448v-128h229.4l-274.6-274.8z" />
<glyph unicode="&#xeb00;" glyph-name="icon-activity" d="M576 768h-256l320-320h-290.256c-44.264 76.516-126.99 128-221.744 128h-128v-512h128c94.754 0 177.48 51.484 221.744 128h290.256l-320-320h256l448 448-448 448z" />
<glyph unicode="&#xeb01;" glyph-name="icon-activity-mode" d="M512 832c-214.8 0-398.8-132.4-474.8-320h90.8c56.8 0 108-24.8 143-64h241l-192 192h256l320-320-320-320h-256l192 192h-241c-35-39.2-86.2-64-143-64h-90.8c76-187.6 259.8-320 474.8-320 282.8 0 512 229.2 512 512s-229.2 512-512 512z" />
<glyph unicode="&#xeb02;" glyph-name="icon-autoflow-tabular" d="M192 832c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h64v1024h-64zM384 832h256v-1024h-256v1024zM832 832h-64v-704h256v512c0 105.6-86.4 192-192 192z" />

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -3,7 +3,7 @@
@import "../plugins/condition/components/conditionals.scss";
@import "../plugins/conditionWidget/components/condition-widget.scss";
@import "../plugins/condition/components/inspector/conditional-styles.scss";
@import "../plugins/displayLayout/components/box-and-line-views";
@import "../plugins/displayLayout/components/box-view.scss";
@import "../plugins/displayLayout/components/display-layout.scss";
@import "../plugins/displayLayout/components/edit-marquee.scss";
@import "../plugins/displayLayout/components/image-view.scss";
@@ -13,6 +13,7 @@
@import "../plugins/filters/components/filters-view.scss";
@import "../plugins/filters/components/global-filters.scss";
@import "../plugins/flexibleLayout/components/flexible-layout.scss";
@import "../plugins/folderView/components/grid-item.scss";
@import "../plugins/folderView/components/grid-view.scss";
@import "../plugins/folderView/components/list-item.scss";
@import "../plugins/folderView/components/list-view.scss";

View File

@@ -24,24 +24,13 @@
class="c-so-view has-local-controls"
:class="{
'c-so-view--no-frame': !hasFrame,
'has-complex-content': complexContent,
'is-missing': domainObject.status === 'missing'
'has-complex-content': complexContent
}"
>
<div class="c-so-view__header">
<div class="c-object-label"
:class="{
classList,
'is-missing': domainObject.status === 'missing'
}"
:class="[cssClass, classList]"
>
<div class="c-object-label__type-icon"
:class="cssClass"
>
<span class="is-missing__indicator"
title="This item is missing"
></span>
</div>
<div class="c-object-label__name">
{{ domainObject && domainObject.name }}
</div>
@@ -57,9 +46,6 @@
@click="expand"
></button>
</div>
<div class="is-missing__indicator"
title="This item is missing"
></div>
<object-view
ref="objectView"
class="c-so-view__object-view"
@@ -142,11 +128,8 @@ export default {
getOverlayElement(childElement) {
const fragment = new DocumentFragment();
const header = this.getPreviewHeader();
const wrapper = document.createElement('div');
wrapper.classList.add('l-preview-window__object-view');
wrapper.append(childElement);
fragment.append(header);
fragment.append(wrapper);
fragment.append(childElement);
return fragment;
},

View File

@@ -1,10 +1,7 @@
<template>
<a
class="c-tree__item__label c-object-label"
:class="{
classList,
'is-missing': observedObject.status === 'missing'
}"
:class="classList"
draggable="true"
:href="objectLink"
@dragstart="dragStart"
@@ -13,14 +10,8 @@
<div
class="c-tree__item__type-icon c-object-label__type-icon"
:class="typeClass"
>
<span class="is-missing__indicator"
title="This item is missing"
></span>
</div>
<div class="c-tree__item__name c-object-label__name">
{{ observedObject.name }}
</div>
></div>
<div class="c-tree__item__name c-object-label__name">{{ observedObject.name }}</div>
</a>
</template>

View File

@@ -53,9 +53,7 @@ export default {
mounted() {
this.currentObject = this.object;
this.updateView();
this.$el.addEventListener('dragover', this.onDragOver, {
capture: true
});
this.$el.addEventListener('dragover', this.onDragOver);
this.$el.addEventListener('drop', this.editIfEditable, {
capture: true
});
@@ -271,7 +269,6 @@ export default {
if (provider &&
provider.canEdit &&
provider.canEdit(this.currentObject) &&
this.isEditingAllowed() &&
!this.openmct.editor.isEditing()) {
this.openmct.editor.edit();
}
@@ -304,7 +301,7 @@ export default {
objectPath= this.currentObjectPath || this.objectPath,
parentObject = objectPath[1];
return [browseObject, parentObject, this.currentObject].every(object => object && !object.locked);
return [browseObject, parentObject, this.currentObject].every(object => !object.locked);
}
}
}

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