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.Filters(['table', 'telemetry.plot.overlay']));
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();
</script>
</html>

View File

@ -36,8 +36,6 @@ define(
}
EditPersistableObjectsPolicy.prototype.allow = function (action, context) {
var identifier;
var provider;
var domainObject = context.domainObject;
var key = action.getMetadata().key;
var category = (context || {}).category;
@ -46,9 +44,8 @@ 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') {
identifier = objectUtils.parseKeyString(domainObject.getId());
provider = this.openmct.objects.getProvider(identifier);
return provider.save !== undefined;
let newStyleObject = objectUtils.toNewFormat(domainObject, domainObject.getId());
return this.openmct.objects.isPersistable(newStyleObject);
}
return true;

View File

@ -43,7 +43,7 @@ define(
);
mockObjectAPI = jasmine.createSpyObj('objectAPI', [
'getProvider'
'isPersistable'
]);
mockAPI = {
@ -69,34 +69,31 @@ define(
});
it("Applies to edit action", function () {
mockObjectAPI.getProvider.and.returnValue({});
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled();
policy.allow(mockEditAction, testContext);
expect(mockObjectAPI.getProvider).toHaveBeenCalled();
expect(mockObjectAPI.isPersistable).toHaveBeenCalled();
});
it("Applies to properties action", function () {
mockObjectAPI.getProvider.and.returnValue({});
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled();
policy.allow(mockPropertiesAction, testContext);
expect(mockObjectAPI.getProvider).toHaveBeenCalled();
expect(mockObjectAPI.isPersistable).toHaveBeenCalled();
});
it("does not apply to other actions", function () {
mockObjectAPI.getProvider.and.returnValue({});
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled();
policy.allow(mockOtherAction, testContext);
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled();
});
it("Tests object provider for editability", function () {
mockObjectAPI.getProvider.and.returnValue({});
mockObjectAPI.isPersistable.and.returnValue(false);
expect(policy.allow(mockEditAction, testContext)).toBe(false);
expect(mockObjectAPI.getProvider).toHaveBeenCalled();
mockObjectAPI.getProvider.and.returnValue({save: function () {}});
expect(mockObjectAPI.isPersistable).toHaveBeenCalled();
mockObjectAPI.isPersistable.and.returnValue(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
// is a new object.
if (!(parent.hasCapability('editor') && parent.getCapability('editor').isEditContextRoot())) {
var identifier = objectUtils.parseKeyString(parent.getId());
var provider = this.openmct.objects.getProvider(identifier);
return provider.save !== undefined;
let newStyleObject = objectUtils.toNewFormat(parent, parent.getId());
return this.openmct.objects.isPersistable(newStyleObject);
}
return true;
};

View File

@ -33,7 +33,7 @@ define(
beforeEach(function () {
objectAPI = jasmine.createSpyObj('objectsAPI', [
'getProvider'
'isPersistable'
]);
mockOpenMCT = {
@ -51,10 +51,6 @@ define(
'isEditContextRoot'
]);
mockParent.getCapability.and.returnValue(mockEditorCapability);
objectAPI.getProvider.and.returnValue({
save: function () {}
});
persistableCompositionPolicy = new PersistableCompositionPolicy(mockOpenMCT);
});
@ -65,19 +61,22 @@ 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.getProvider.and.returnValue({});
objectAPI.isPersistable.and.returnValue(false);
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.getProvider).not.toHaveBeenCalled();
expect(objectAPI.isPersistable).not.toHaveBeenCalled();
mockEditorCapability.isEditContextRoot.and.returnValue(false);
objectAPI.isPersistable.and.returnValue(true);
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true);
expect(objectAPI.getProvider).toHaveBeenCalled();
expect(objectAPI.isPersistable).toHaveBeenCalled();
});
});

View File

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

View File

@ -20,8 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
function () {
define(["objectUtils"],
function (objectUtils) {
/**
* Defines the `persistence` capability, used to trigger the
@ -47,6 +47,7 @@ define(
identifierService,
notificationService,
$q,
openmct,
domainObject
) {
// Cache modified timestamp
@ -58,6 +59,7 @@ define(
this.persistenceService = persistenceService;
this.notificationService = notificationService;
this.$q = $q;
this.openmct = openmct;
}
/**
@ -66,7 +68,7 @@ define(
*/
function rejectIfFalsey(value, $q) {
if (!value) {
return $q.reject("Error persisting object");
return Promise.reject("Error persisting object");
} else {
return value;
}
@ -98,7 +100,7 @@ define(
dismissable: true
});
return $q.reject(error);
return Promise.reject(error);
}
/**
@ -110,30 +112,12 @@ define(
*/
PersistenceCapability.prototype.persist = function () {
var self = this,
domainObject = this.domainObject,
model = domainObject.getModel(),
modified = model.modified,
persisted = model.persisted,
persistenceService = this.persistenceService,
persistenceFn = persisted !== undefined ?
this.persistenceService.updateObject :
this.persistenceService.createObject;
domainObject = this.domainObject;
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) {
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);

View File

@ -19,7 +19,6 @@
* 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.
*/
@ -40,7 +39,8 @@ define(
model,
SPACE = "some space",
persistence,
happyPromise;
mockOpenMCT,
mockNewStyleDomainObject;
function asPromise(value, doCatch) {
return (value || {}).then ? value : {
@ -56,7 +56,6 @@ define(
}
beforeEach(function () {
happyPromise = asPromise(true);
model = { someKey: "some value", name: "domain object"};
mockPersistenceService = jasmine.createSpyObj(
@ -94,12 +93,23 @@ 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);
@ -110,51 +120,28 @@ define(
mockIdentifierService,
mockNofificationService,
mockQ,
mockOpenMCT,
mockDomainObject
);
});
describe("successful persistence", function () {
beforeEach(function () {
mockPersistenceService.updateObject.and.returnValue(happyPromise);
mockPersistenceService.createObject.and.returnValue(happyPromise);
mockOpenMCT.objects.save.and.returnValue(Promise.resolve(true));
});
it("creates unpersisted objects with the persistence service", function () {
// Verify precondition; no call made during constructor
expect(mockPersistenceService.createObject).not.toHaveBeenCalled();
expect(mockOpenMCT.objects.save).not.toHaveBeenCalled();
persistence.persist();
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
);
expect(mockOpenMCT.objects.save).toHaveBeenCalledWith(mockNewStyleDomainObject);
});
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;
@ -165,32 +152,39 @@ define(
it("does not trigger error notification on successful" +
" persistence", function () {
persistence.persist();
expect(mockQ.reject).not.toHaveBeenCalled();
let rejected = false;
return persistence.persist()
.catch(() => rejected = true)
.then(() => {
expect(rejected).toBe(false);
expect(mockNofificationService.error).not.toHaveBeenCalled();
});
});
});
describe("unsuccessful persistence", function () {
var sadPromise = {
then: function (callback) {
return asPromise(callback(0), true);
}
};
beforeEach(function () {
mockPersistenceService.createObject.and.returnValue(sadPromise);
mockOpenMCT.objects.save.and.returnValue(Promise.resolve(false));
});
it("rejects on falsey persistence result", function () {
persistence.persist();
expect(mockQ.reject).toHaveBeenCalled();
let rejected = false;
return persistence.persist()
.catch(() => rejected = true)
.then(() => {
expect(rejected).toBe(true);
});
});
it("notifies user on persistence failure", function () {
persistence.persist();
expect(mockQ.reject).toHaveBeenCalled();
let rejected = false;
return persistence.persist()
.catch(() => rejected = true)
.then(() => {
expect(rejected).toBe(true);
expect(mockNofificationService.error).toHaveBeenCalled();
});
});
});
});
}
);

View File

@ -25,10 +25,11 @@ define([
], function (
utils
) {
function ObjectServiceProvider(eventEmitter, objectService, instantiate, topic) {
function ObjectServiceProvider(eventEmitter, objectService, instantiate, topic, $injector) {
this.eventEmitter = eventEmitter;
this.objectService = objectService;
this.instantiate = instantiate;
this.$injector = $injector;
this.generalTopic = topic('mutation');
this.bridgeEventBuses();
@ -68,16 +69,53 @@ define([
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
};
ObjectServiceProvider.prototype.save = function (object) {
var key = object.key;
ObjectServiceProvider.prototype.create = async function (object) {
let model = utils.toOldFormat(object);
return object.getCapability('persistence')
.persist()
.then(function () {
return utils.toNewFormat(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();
};
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!
};
@ -118,7 +156,8 @@ define([
eventEmitter,
objectService,
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#
* @param {module:openmct.DomainObject} domainObject the domain object to
* save
* create
* @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');
};
ObjectAPI.prototype.save = function () {
throw new Error('Save 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;
};
/**
@ -276,5 +320,9 @@ define([
* @memberof module:openmct
*/
function hasAlreadyBeenPersisted(domainObject) {
return domainObject.persisted !== undefined &&
domainObject.persisted === domainObject.modified;
}
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,10 +29,13 @@ define([
ClearDataAction,
Vue
) {
return function plugin(appliesToObjects) {
return function plugin(appliesToObjects, options = {indicator: true}) {
let installIndicator = options.indicator;
appliesToObjects = appliesToObjects || [];
return function install(openmct) {
if (installIndicator) {
let component = new Vue ({
provide: {
openmct
@ -47,6 +50,7 @@ define([
};
openmct.indicators.add(indicator);
}
openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects));
};

View File

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

View File

@ -2,14 +2,17 @@
<div class="c-snapshots-h">
<div class="l-browse-bar">
<div class="l-browse-bar__start">
<div class="l-browse-bar__object-name--w icon-notebook">
<div class="l-browse-bar__object-name">
<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>
<PopupMenu v-if="snapshots.length > 0"
:popup-menu-items="popupMenuItems"
/>

View File

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

View File

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

View File

@ -118,9 +118,7 @@ mct-plot {
.gl-plot {
display: flex;
position: relative;
width: 100%;
height: 100%;
flex: 1 1 auto;
/*********************** 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
.is-missing__indicator {
display: block;
display: none ;
text-shadow: $colorBodyBg 0 0 2px;
color: $colorAlert;
font-family: symbolsfont;
@ -139,6 +139,9 @@
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) {

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@
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;
}
@ -41,6 +41,8 @@
&__content {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
&__elements {

View File

@ -76,16 +76,23 @@
[class*='minify-indicators'] {
// All styles for minified Indicators should go in here
.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() {
background: $colorIndicatorBgHov;
transition: transform 250ms ease-in 200ms; // Go-away transition
.c-indicator__label {
box-shadow: $colorIndicatorMenuBgShdw;
transform: scale(1.0);
transition: all 100ms ease-out 100ms;
overflow: visible;
transition: transform 100ms ease-out 100ms; // Appear transition
}
}
.c-indicator__label {
transition: all 250ms ease-in 200ms;
transition: transform 250ms ease-in 200ms; // Go-away transition
background: $colorIndicatorMenuBg;
color: $colorIndicatorMenuFg;
border-radius: $controlCr;
@ -95,7 +102,7 @@
position: absolute;
transform-origin: 90% 0;
transform: scale(0.0);
overflow: visible;
overflow: hidden;
z-index: 50;
&:before {