Merge branch 'master' into data-dropout-fixes

This commit is contained in:
Shefali Joshi 2020-07-20 15:01:11 -07:00 committed by GitHub
commit 8fa1770885
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 330 additions and 189 deletions

View File

@ -113,7 +113,10 @@
openmct.install(openmct.plugins.LADTable()); openmct.install(openmct.plugins.LADTable());
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay'])); openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
openmct.install(openmct.plugins.ObjectMigration()); openmct.install(openmct.plugins.ObjectMigration());
openmct.install(openmct.plugins.ClearData(['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'])); openmct.install(openmct.plugins.ClearData(
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
{indicator: true}
));
openmct.start(); openmct.start();
</script> </script>
</html> </html>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,60 @@
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

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

View File

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

View File

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

View File

@ -22,21 +22,24 @@
<button <button
v-for="(tab,index) in tabsList" v-for="(tab,index) in tabsList"
:key="index" :key="index"
class="c-tabs-view__tab c-tab c-object-label" class="c-tab c-tabs-view__tab"
:class="{ :class="{
'is-current': isCurrent(tab), 'is-current': isCurrent(tab)
'is-missing': tab.domainObject.status === 'missing'
}" }"
@click="showTab(tab, index)" @click="showTab(tab, index)"
> >
<div class="c-object-label__type-icon" <div class="c-object-label"
:class="tab.type.definition.cssClass" :class="{'is-missing': tab.domainObject.status === 'missing'}"
> >
<span class="is-missing__indicator" <div class="c-object-label__type-icon"
title="This item is missing" :class="tab.type.definition.cssClass"
></span> >
<span class="is-missing__indicator"
title="This item is missing"
></span>
</div>
<span class="c-button__label c-object-label__name">{{ tab.domainObject.name }}</span>
</div> </div>
<span class="c-button__label c-object-label__name">{{ tab.domainObject.name }}</span>
</button> </button>
</div> </div>
<div <div

View File

@ -411,7 +411,17 @@ select {
.c-tab { .c-tab {
// Used in Tab View, generic tabs // Used in Tab View, generic tabs
background: $colorBtnBg; $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);
color: $colorBtnFg; color: $colorBtnFg;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
@ -420,16 +430,8 @@ select {
margin: 1px 1px 0 0; margin: 1px 1px 0 0;
padding: $interiorMargin $interiorMarginLg; padding: $interiorMargin $interiorMarginLg;
white-space: nowrap; white-space: nowrap;
--notchSize: 7px; clip-path: $clipPath;
clip-path: -webkit-clip-path: $clipPath; // Safari
polygon(
0% 0%,
calc(100% - var(--notchSize)) 0%,
100% var(--notchSize),
100% calc(100% - var(--notchSize)),
100% 100%,
0% 100%
);
> * + * { > * + * {
margin-left: $interiorMargin; margin-left: $interiorMargin;

View File

@ -118,9 +118,7 @@ mct-plot {
.gl-plot { .gl-plot {
display: flex; display: flex;
position: relative; flex: 1 1 auto;
width: 100%;
height: 100%;
/*********************** AXIS AND DISPLAY AREA */ /*********************** AXIS AND DISPLAY AREA */
.plot-wrapper-axis-and-display-area { .plot-wrapper-axis-and-display-area {

View File

@ -123,7 +123,7 @@
//pointer-events: none; // Don't think we can do this, as disables title hover on icon element //pointer-events: none; // Don't think we can do this, as disables title hover on icon element
.is-missing__indicator { .is-missing__indicator {
display: block; display: none ;
text-shadow: $colorBodyBg 0 0 2px; text-shadow: $colorBodyBg 0 0 2px;
color: $colorAlert; color: $colorAlert;
font-family: symbolsfont; font-family: symbolsfont;
@ -139,6 +139,9 @@
z-index: 3; 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) { @mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) {

View File

@ -53,7 +53,9 @@ export default {
mounted() { mounted() {
this.currentObject = this.object; this.currentObject = this.object;
this.updateView(); this.updateView();
this.$el.addEventListener('dragover', this.onDragOver); this.$el.addEventListener('dragover', this.onDragOver, {
capture: true
});
this.$el.addEventListener('drop', this.editIfEditable, { this.$el.addEventListener('drop', this.editIfEditable, {
capture: true capture: true
}); });
@ -269,6 +271,7 @@ export default {
if (provider && if (provider &&
provider.canEdit && provider.canEdit &&
provider.canEdit(this.currentObject) && provider.canEdit(this.currentObject) &&
this.isEditingAllowed() &&
!this.openmct.editor.isEditing()) { !this.openmct.editor.isEditing()) {
this.openmct.editor.edit(); this.openmct.editor.edit();
} }

View File

@ -74,11 +74,9 @@
height: 0; // Chrome 73 overflow bug fix height: 0; // Chrome 73 overflow bug fix
overflow: auto; overflow: auto;
.u-angular-object-view-wrapper { .u-fills-container {
.u-fills-container { // Expand component types that fill a container
// Expand component types that fill a container @include abs();
@include abs();
}
} }
} }
@ -91,8 +89,5 @@
} }
.u-angular-object-view-wrapper { .u-angular-object-view-wrapper {
flex: 1 1 auto; display: contents;
height: 100%;
width: 100%;
overflow: hidden;
} }

View File

@ -3,7 +3,7 @@
// Used mostly in trees and lists // Used mostly in trees and lists
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1 1 auto; flex: 0 1 auto;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
@ -19,7 +19,6 @@
display: block; display: block;
flex: 0 0 auto; flex: 0 0 auto;
font-size: 1.1em; font-size: 1.1em;
//margin-right: $interiorMargin;
} }
&.is-missing { &.is-missing {

View File

@ -4,7 +4,7 @@
flex-direction: column; flex-direction: column;
> * { > * {
// Thi is on purpose: want extra margin on top object-name element // This is on purpose: want extra margin on top object-name element
margin-top: $interiorMargin; margin-top: $interiorMargin;
} }
@ -41,6 +41,8 @@
&__content { &__content {
flex: 1 1 auto; flex: 1 1 auto;
display: flex;
flex-direction: column;
} }
&__elements { &__elements {

View File

@ -76,16 +76,23 @@
[class*='minify-indicators'] { [class*='minify-indicators'] {
// All styles for minified Indicators should go in here // All styles for minified Indicators should go in here
.c-indicator:not(.no-minify) { .c-indicator:not(.no-minify) {
border: 1px solid transparent; // Hack to make minified sizing work in Safari. Have no idea why this works.
overflow: visible;
transition: transform;
@include hover() { @include hover() {
background: $colorIndicatorBgHov; background: $colorIndicatorBgHov;
transition: transform 250ms ease-in 200ms; // Go-away transition
.c-indicator__label { .c-indicator__label {
box-shadow: $colorIndicatorMenuBgShdw; box-shadow: $colorIndicatorMenuBgShdw;
transform: scale(1.0); transform: scale(1.0);
transition: all 100ms ease-out 100ms; overflow: visible;
transition: transform 100ms ease-out 100ms; // Appear transition
} }
} }
.c-indicator__label { .c-indicator__label {
transition: all 250ms ease-in 200ms; transition: transform 250ms ease-in 200ms; // Go-away transition
background: $colorIndicatorMenuBg; background: $colorIndicatorMenuBg;
color: $colorIndicatorMenuFg; color: $colorIndicatorMenuFg;
border-radius: $controlCr; border-radius: $controlCr;
@ -95,7 +102,7 @@
position: absolute; position: absolute;
transform-origin: 90% 0; transform-origin: 90% 0;
transform: scale(0.0); transform: scale(0.0);
overflow: visible; overflow: hidden;
z-index: 50; z-index: 50;
&:before { &:before {